ntswf 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
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