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 +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
|