ntswf 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -44,7 +44,7 @@ class Decider
44
44
  end
45
45
 
46
46
  config = {domain: 'my_domain', unit: 'my_app'} # ...
47
- loop { Decider.new(config).process_decision_task }
47
+ Decider.new(config).process_decisions
48
48
  ```
49
49
 
50
50
  ### Activity worker
@@ -62,7 +62,7 @@ class Worker
62
62
  end
63
63
 
64
64
  config = {domain: 'my_domain', unit: 'my_worker'} # ...
65
- loop { Worker.new(config).process_activity_task }
65
+ Worker.new(config).process_activities
66
66
  ```
67
67
 
68
68
  ### Setup helpers
@@ -5,6 +5,11 @@ module Ntswf
5
5
  module ActivityWorker
6
6
  include Ntswf::Worker
7
7
 
8
+ # Start the worker loop for activity tasks.
9
+ def process_activities
10
+ loop { in_subprocess :process_activity_task }
11
+ end
12
+
8
13
  def process_activity_task
9
14
  announce("polling for activity task #{activity_task_list}")
10
15
  domain.activity_tasks.poll_for_single_task(activity_task_list) do |task|
data/lib/ntswf/base.rb CHANGED
@@ -9,6 +9,7 @@ module Ntswf
9
9
  # @option config [String] :decision_task_list The task list name for decisions
10
10
  # @option config [String] :domain The SWF domain name
11
11
  # @option config [Numeric] :execution_version Value allowing clients to reject future execution versions
12
+ # @option config [Numeric] :subprocess_retries (0) see {Worker#in_subprocess}
12
13
  # @option config [String] :secret_access_key AWS credential
13
14
  # @option config [String] :unit This worker/client's activity task list key
14
15
  def initialize(config)
@@ -74,16 +75,16 @@ module Ntswf
74
75
  ";"
75
76
  end
76
77
 
78
+ def activity_type
79
+ @activity_type ||= domain.activity_types[activity_name, workflow_version]
80
+ end
81
+
77
82
  protected
78
83
 
79
84
  def activity_name
80
85
  "#{default_unit}-activity"
81
86
  end
82
87
 
83
- def activity_type
84
- @activity_type ||= domain.activity_types[activity_name, workflow_version]
85
- end
86
-
87
88
  def announce(s)
88
89
  $0 = s
89
90
  log(s)
@@ -5,6 +5,11 @@ module Ntswf
5
5
  module DecisionWorker
6
6
  include Ntswf::Worker
7
7
 
8
+ # Start the worker loop for decision tasks.
9
+ def process_decisions
10
+ loop { in_subprocess :process_decision_task }
11
+ end
12
+
8
13
  # Process a decision task.
9
14
  # The following task values are interpreted:
10
15
  # input:: see {Ntswf::Client#start_execution}
data/lib/ntswf/worker.rb CHANGED
@@ -6,5 +6,33 @@ module Ntswf
6
6
 
7
7
  # *reason* value to force task reschedule, may be set if the worker is unable process the task
8
8
  RETRY = "Retry"
9
+
10
+ # Run a method in a separate thread.
11
+ # This will ensure the call lives on if the master process is terminated.
12
+ # If the *:subprocess_retries* configuration is set {StandardError}s during the
13
+ # method call will be retried accordingly.
14
+ def in_subprocess(method)
15
+ $stdout.sync = true
16
+ if child = fork
17
+ srand
18
+ now = Time.now
19
+ announce("#{method}: forked #{child} at #{now} (#{now.to_i})")
20
+ Process.wait(child)
21
+ else
22
+ with_retry(@config.subprocess_retries || 0) { send method }
23
+ exit!
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def with_retry(allowed_failures)
30
+ yield
31
+ rescue StandardError => e
32
+ raise if allowed_failures.zero?
33
+ allowed_failures -= 1
34
+ log("retrying. exception raised: #{e.message}\n #{e.backtrace.join("\n ")}")
35
+ retry
36
+ end
9
37
  end
10
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ntswf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-23 00:00:00.000000000 Z
12
+ date: 2014-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -58,8 +58,6 @@ files:
58
58
  - lib/ntswf/client.rb
59
59
  - lib/ntswf/base.rb
60
60
  - lib/ntswf.rb
61
- - spec/decision_worker_spec.rb
62
- - spec/activity_worker_spec.rb
63
61
  homepage: https://github.com/infopark/ntswf
64
62
  licenses:
65
63
  - LGPLv3
@@ -85,7 +83,5 @@ rubygems_version: 1.8.23
85
83
  signing_key:
86
84
  specification_version: 3
87
85
  summary: Not That Simple Workflow
88
- test_files:
89
- - spec/decision_worker_spec.rb
90
- - spec/activity_worker_spec.rb
86
+ test_files: []
91
87
  has_rdoc:
@@ -1,26 +0,0 @@
1
- require "ntswf"
2
-
3
- describe Ntswf::ActivityWorker do
4
- class Worker
5
- include Ntswf::ActivityWorker
6
- end
7
-
8
- before { Worker.any_instance.stub(announce: nil, log: nil) }
9
-
10
- let(:config) { { unit: "test", activity_task_lists: { "test" => "task_list" } } }
11
- let(:worker) { Worker.new config }
12
- let(:activity_task) { mock input: "{}" }
13
-
14
- describe "processing an activity task" do
15
- before do
16
- AWS::SimpleWorkflow::ActivityTaskCollection.any_instance.stub(:poll_for_single_task).
17
- and_yield activity_task
18
- end
19
-
20
- context "given foreign activity type" do
21
- before { activity_task.stub activity_type: :some_random_thing }
22
- subject { worker.process_activity_task { :accepted } }
23
- it { should eq :accepted }
24
- end
25
- end
26
- end
@@ -1,214 +0,0 @@
1
- require "ntswf"
2
- require "json"
3
-
4
- describe Ntswf::DecisionWorker do
5
- class Worker
6
- include Ntswf::DecisionWorker
7
- end
8
-
9
- let(:atl_config) { { "test" => "atl" } }
10
- let(:dtl_config) { "dtl" }
11
- let(:unit) { "testt" }
12
- let(:config) { { unit: unit, decision_task_list: dtl_config, activity_task_lists: atl_config } }
13
- let(:worker) { Worker.new config }
14
-
15
- let(:options) { {} }
16
- let(:input) { options.merge('params' => {'test' => 'value'}).to_json }
17
- let(:reason) { nil }
18
- let(:result) { nil }
19
- let(:attributes_hash) { { input: input, reason: reason, result: result } }
20
- let(:attributes) { double("Attributes", attributes_hash.merge(to_h: attributes_hash)) }
21
- let(:workflow_execution) { double("Workflow Execution", workflow_type: double(name: 'test-wf')) }
22
- let(:event) do
23
- double("Event", attributes: attributes, event_type: event_type, workflow_execution:
24
- workflow_execution)
25
- end
26
- let(:task) { double("Task", new_events: [event], events: [event]).as_null_object }
27
-
28
- before { worker.stub(announce: nil, log: nil, activity_type: "test_activity") }
29
-
30
- describe "processing a decision task" do
31
- it "should only query for the configured task list" do
32
- AWS::SimpleWorkflow::DecisionTaskCollection.any_instance.
33
- should_receive(:poll_for_single_task).with("dtl")
34
- worker.process_decision_task
35
- end
36
-
37
- describe "handling event" do
38
- before do
39
- AWS::SimpleWorkflow::DecisionTaskCollection.any_instance.stub(
40
- :poll_for_single_task).and_yield(task)
41
- end
42
-
43
- describe "ActivityTaskTimedOut" do
44
- let(:event_type) {"ActivityTaskTimedOut"}
45
-
46
- it "should cancel the execution" do
47
- task.should_receive :cancel_workflow_execution
48
- worker.process_decision_task
49
- end
50
-
51
- it "should notify" do
52
- worker.should_receive :notify
53
- worker.process_decision_task
54
- end
55
- end
56
-
57
- describe "ActivityTaskCompleted" do
58
- let(:event_type) {"ActivityTaskCompleted"}
59
-
60
- context "when requesting re-execution per seconds_until_retry" do
61
- let(:result) { {seconds_until_retry: 321}.to_json }
62
-
63
- it "schedules a timer event" do
64
- task.should_receive(:start_timer).with(321)
65
- worker.process_decision_task
66
- end
67
- end
68
-
69
- context "when not requesting re-execution" do
70
- let(:result) { {outcome: "some_data"}.to_json }
71
-
72
- it "schedules a workflow completed event" do
73
- task.should_receive(:complete_workflow_execution).with(result: result)
74
- worker.process_decision_task
75
- end
76
- end
77
- end
78
-
79
- describe "WorkflowExecutionStarted" do
80
- let(:event_type) {"WorkflowExecutionStarted"}
81
-
82
- it "should schedule an activity task avoiding defaults" do
83
- task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
84
- heartbeat_timeout: :none,
85
- input: anything,
86
- schedule_to_close_timeout: anything,
87
- schedule_to_start_timeout: anything,
88
- start_to_close_timeout: anything,
89
- task_list: "atl",
90
- ))
91
- worker.process_decision_task
92
- end
93
-
94
- context "given no app in charge" do
95
- let(:input) { ["legacy_stuff", {}].to_json }
96
-
97
- it "should schedule an activity task for a guessed task list" do
98
- task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
99
- task_list: "atl"))
100
- worker.process_decision_task
101
- end
102
- end
103
- end
104
-
105
- describe "ActivityTaskFailed" do
106
- let(:event_type) {"ActivityTaskFailed"}
107
-
108
- context "without retry" do
109
- let(:reason) { "Error" }
110
-
111
- it "should fail" do
112
- task.should_receive(:fail_workflow_execution)
113
- worker.process_decision_task
114
- end
115
-
116
- it "should not re-schedule the task" do
117
- task.should_not_receive(:schedule_activity_task)
118
- worker.process_decision_task
119
- end
120
- end
121
-
122
- context "with retry" do
123
- let(:reason) { "Retry" }
124
-
125
- it "should not fail" do
126
- task.should_not_receive(:fail_workflow_execution)
127
- worker.process_decision_task
128
- end
129
-
130
- it "should re-schedule the task" do
131
- task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
132
- heartbeat_timeout: :none,
133
- input: input,
134
- schedule_to_close_timeout: anything,
135
- schedule_to_start_timeout: anything,
136
- start_to_close_timeout: anything,
137
- task_list: "atl",
138
- ))
139
- worker.process_decision_task
140
- end
141
- end
142
- end
143
-
144
- describe "TimerFired" do
145
- let(:event_type) {"TimerFired"}
146
- let(:attributes_hash) do
147
- {
148
- child_policy: 1,
149
- execution_start_to_close_timeout: 2,
150
- input: input,
151
- tag_list: ["tag"],
152
- task_list: "list",
153
- task_start_to_close_timeout: 3,
154
- }
155
- end
156
-
157
- context "given an interval option" do
158
- let(:options) { {interval: 1234} }
159
-
160
- it "should continue wiht mandatory attributes" do
161
- task.should_receive(:continue_as_new_workflow_execution).with(hash_including(
162
- attributes_hash))
163
- worker.process_decision_task
164
- end
165
- end
166
-
167
- context "given no interval" do
168
- it "should re-schedule, assuming seconds_until_retry was set" do
169
- task.should_receive(:schedule_activity_task).with("test_activity", hash_including(
170
- heartbeat_timeout: :none,
171
- input: input,
172
- schedule_to_close_timeout: anything,
173
- schedule_to_start_timeout: anything,
174
- start_to_close_timeout: anything,
175
- task_list: "atl",
176
- ))
177
- worker.process_decision_task
178
- end
179
- end
180
- end
181
-
182
- context "given an interval" do
183
- let(:options) { {interval: 1234} }
184
-
185
- events = %w(
186
- ActivityTaskCompleted
187
- ActivityTaskFailed
188
- ActivityTaskTimedOut
189
- )
190
-
191
- events.each do |event|
192
- describe event do
193
- let(:event_type) { event }
194
-
195
- it "should start a timer" do
196
- task.should_receive(:start_timer).with(1234)
197
- worker.process_decision_task
198
- end
199
- end
200
- end
201
-
202
- describe "string options for compatibility" do
203
- let(:event_type) { "ActivityTaskCompleted" }
204
- let(:input) { ["interval", {}].to_json }
205
-
206
- it "should not be interpreted" do
207
- task.should_not_receive :start_timer
208
- worker.process_decision_task
209
- end
210
- end
211
- end
212
- end
213
- end
214
- end