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 +2 -2
- data/lib/ntswf/activity_worker.rb +5 -0
- data/lib/ntswf/base.rb +5 -4
- data/lib/ntswf/decision_worker.rb +5 -0
- data/lib/ntswf/worker.rb +28 -0
- metadata +3 -7
- data/spec/activity_worker_spec.rb +0 -26
- data/spec/decision_worker_spec.rb +0 -214
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
|
-
|
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
|
-
|
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.
|
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-
|
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
|