aws-flow 1.3.0 → 2.0.0
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.
- checksums.yaml +15 -0
- data/aws-flow.gemspec +1 -0
- data/lib/aws/decider/activity.rb +8 -6
- data/lib/aws/decider/async_decider.rb +1 -0
- data/lib/aws/decider/async_retrying_executor.rb +3 -3
- data/lib/aws/decider/decider.rb +16 -14
- data/lib/aws/decider/executor.rb +35 -22
- data/lib/aws/decider/flow_defaults.rb +28 -14
- data/lib/aws/decider/generic_client.rb +3 -4
- data/lib/aws/decider/options.rb +91 -117
- data/lib/aws/decider/state_machines.rb +1 -0
- data/lib/aws/decider/utilities.rb +15 -0
- data/lib/aws/decider/version.rb +1 -1
- data/lib/aws/decider/worker.rb +14 -8
- data/lib/aws/decider/workflow_client.rb +16 -11
- data/lib/aws/runner.rb +43 -39
- data/spec/aws/decider/integration/activity_spec.rb +345 -0
- data/spec/aws/{integration → decider/integration}/integration_spec.rb +818 -1183
- data/spec/aws/decider/integration/setup.rb +3 -0
- data/spec/aws/decider/unit/activity_spec.rb +233 -0
- data/spec/aws/decider/unit/async_retrying_executor_spec.rb +131 -0
- data/spec/aws/{unit → decider/unit}/decider_spec.rb +171 -718
- data/spec/aws/decider/unit/executor_spec.rb +123 -0
- data/spec/aws/decider/unit/flow_defaults_spec.rb +62 -0
- data/spec/aws/decider/unit/misc_spec.rb +101 -0
- data/spec/aws/decider/unit/options_spec.rb +289 -0
- data/spec/aws/decider/unit/retry_spec.rb +217 -0
- data/spec/aws/{unit → decider/unit}/rubyflow.rb +0 -0
- data/spec/aws/decider/unit/setup.rb +3 -0
- data/spec/aws/decider/unit/worker_spec.rb +325 -0
- data/spec/aws/decider/unit/workflow_client_spec.rb +83 -0
- data/spec/aws/{unit → flow}/async_backtrace_spec.rb +0 -0
- data/spec/aws/{unit → flow}/async_scope_spec.rb +0 -0
- data/spec/aws/{unit → flow}/begin_rescue_ensure_spec.rb +1 -0
- data/spec/aws/{unit → flow}/external_task_spec.rb +0 -0
- data/spec/aws/{unit → flow}/factories.rb +0 -0
- data/spec/aws/{unit → flow}/fiber_condition_variable_spec.rb +0 -0
- data/spec/aws/{unit → flow}/fiber_spec.rb +0 -0
- data/spec/aws/{unit → flow}/flow_spec.rb +0 -0
- data/spec/aws/{unit → flow}/future_spec.rb +0 -0
- data/spec/aws/{unit → flow}/simple_dfa_spec.rb +0 -0
- data/spec/aws/{integration → runner/integration}/runner_integration_spec.rb +16 -43
- data/spec/aws/{unit → runner/unit}/runner_unit_spec.rb +18 -18
- data/spec/spec_helper.rb +264 -2
- metadata +37 -28
- data/spec/aws/unit/executor_spec.rb +0 -49
- data/spec/aws/unit/options_spec.rb +0 -293
- data/spec/aws/unit/preinclude_tests.rb +0 -149
@@ -0,0 +1,217 @@
|
|
1
|
+
require_relative 'setup'
|
2
|
+
|
3
|
+
$error = "--- !ruby/exception:ArgumentError\nmessage: asdfasdf\n---\n- helloworld_activity.rb:21:in `say_hello'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/activity_definition.rb:69:in\n `execute'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/task_poller.rb:144:in\n `execute'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/task_poller.rb:287:in\n `process_single_task'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/task_poller.rb:344:in\n `block in poll_and_process_single_task'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/executor.rb:75:in\n `call'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/executor.rb:75:in\n `block in execute'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/executor.rb:68:in\n `fork'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/executor.rb:68:in\n `execute'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/task_poller.rb:344:in\n `poll_and_process_single_task'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/worker.rb:439:in\n `run_once'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/worker.rb:412:in\n `block in start'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/worker.rb:411:in\n `loop'\n- /home/paritom/.rvm/gems/ruby-1.9.3-p429/gems/aws-flow-1.2.0/lib/aws/decider/worker.rb:411:in\n `start'\n- helloworld_activity.rb:27:in `<main>'\n"
|
4
|
+
|
5
|
+
def get_history_array
|
6
|
+
[
|
7
|
+
"WorkflowExecutionStarted",
|
8
|
+
"DecisionTaskScheduled",
|
9
|
+
"DecisionTaskStarted",
|
10
|
+
"DecisionTaskCompleted",
|
11
|
+
["ActivityTaskScheduled", {activity_id: "Activity1"}],
|
12
|
+
"ActivityTaskStarted",
|
13
|
+
["ActivityTaskFailed", {scheduled_event_id: 5, :activity_id => "Activity1", cause: AWS::Flow::ActivityFailureException, details: $error } ],
|
14
|
+
"DecisionTaskScheduled",
|
15
|
+
"DecisionTaskStarted",
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
describe "ExponentialRetry" do
|
21
|
+
|
22
|
+
context "ActivityRetry" do
|
23
|
+
# The following tests for github issue # 57
|
24
|
+
before(:all) do
|
25
|
+
class RetryTestActivity
|
26
|
+
extend AWS::Flow::Activities
|
27
|
+
activity :retryable do
|
28
|
+
{
|
29
|
+
:version => "1.0",
|
30
|
+
:default_task_list => 'default',
|
31
|
+
:default_task_schedule_to_start_timeout => 20,
|
32
|
+
:default_task_start_to_close_timeout => 20,
|
33
|
+
:exponential_retry => {
|
34
|
+
:maximum_attempts => 3,
|
35
|
+
:retry_expiration_interval_seconds => 10,
|
36
|
+
:backoff_coefficient => 4,
|
37
|
+
:should_jitter => false
|
38
|
+
},
|
39
|
+
}
|
40
|
+
end
|
41
|
+
def retryable
|
42
|
+
raise ArgumentError.new("TEST SIMULATION")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class RetryTestWorkflow
|
47
|
+
extend AWS::Flow::Workflows
|
48
|
+
workflow(:start) { { version: "1.0" } }
|
49
|
+
activity_client(:client) { { from_class: "RetryTestActivity" } }
|
50
|
+
def start
|
51
|
+
client.retryable
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
it "tests part 1 - timer gets scheduled after activity fails" do
|
58
|
+
|
59
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
60
|
+
def get_decision_task
|
61
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
62
|
+
TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil), FakeEvents.new(get_history_array))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
workflow_type_object = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
67
|
+
domain = FakeDomain.new(workflow_type_object)
|
68
|
+
|
69
|
+
swf_client = FakeServiceClient.new
|
70
|
+
task_list = "default"
|
71
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
72
|
+
options.workflow_name = "RetryTestWorkflow"
|
73
|
+
options.execution_start_to_close_timeout = 120
|
74
|
+
options.task_list = "default"
|
75
|
+
options.task_start_to_close_timeout = 30
|
76
|
+
end
|
77
|
+
|
78
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, RetryTestWorkflow)
|
79
|
+
my_workflow = my_workflow_factory.get_client
|
80
|
+
workflow_execution = my_workflow.start_execution
|
81
|
+
worker.start
|
82
|
+
swf_client.trace.first[:decisions].first[:decision_type].should == "StartTimer"
|
83
|
+
swf_client.trace.first[:decisions].first[:start_timer_decision_attributes].should == {:timer_id=>"Timer1", :start_to_fire_timeout=>"2"}
|
84
|
+
end
|
85
|
+
|
86
|
+
it "tests part 2 - activity gets retried the first time after timer fires" do
|
87
|
+
|
88
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
89
|
+
def get_decision_task
|
90
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
91
|
+
TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
|
92
|
+
FakeEvents.new(get_history_array().push(*[
|
93
|
+
"DecisionTaskCompleted",
|
94
|
+
["TimerStarted", {decision_task_completed_event_id: 10, timer_id: "Timer1", start_to_fire_timeout: 2 }],
|
95
|
+
["TimerFired", {timer_id: "Timer1", started_event_id: 11}],
|
96
|
+
"DecisionTaskScheduled",
|
97
|
+
"DecisionTaskStarted",
|
98
|
+
])))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
workflow_type_object = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
103
|
+
domain = FakeDomain.new(workflow_type_object)
|
104
|
+
|
105
|
+
swf_client = FakeServiceClient.new
|
106
|
+
task_list = "default"
|
107
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
108
|
+
options.workflow_name = "RetryTestWorkflow"
|
109
|
+
options.execution_start_to_close_timeout = 120
|
110
|
+
options.task_list = "default"
|
111
|
+
options.task_start_to_close_timeout = 30
|
112
|
+
end
|
113
|
+
|
114
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, RetryTestWorkflow)
|
115
|
+
my_workflow = my_workflow_factory.get_client
|
116
|
+
workflow_execution = my_workflow.start_execution
|
117
|
+
worker.start
|
118
|
+
swf_client.trace.first[:decisions].first[:decision_type].should == "ScheduleActivityTask"
|
119
|
+
swf_client.trace.first[:decisions].first[:schedule_activity_task_decision_attributes][:activity_id] == "Activity2"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "tests part 3 - timer gets scheduled for retry after activity fails again" do
|
123
|
+
|
124
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
125
|
+
def get_decision_task
|
126
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
127
|
+
TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
|
128
|
+
FakeEvents.new(get_history_array().push(*[
|
129
|
+
"DecisionTaskCompleted",
|
130
|
+
["TimerStarted", {decision_task_completed_event_id: 10, timer_id: "Timer1", start_to_fire_timeout: 2 }],
|
131
|
+
["TimerFired", {timer_id: "Timer1", started_event_id: 11}],
|
132
|
+
"DecisionTaskScheduled",
|
133
|
+
"DecisionTaskStarted",
|
134
|
+
"DecisionTaskCompleted",
|
135
|
+
["ActivityTaskScheduled", {activity_id: "Activity2"}],
|
136
|
+
"ActivityTaskStarted",
|
137
|
+
["ActivityTaskFailed", {scheduled_event_id: 16, :activity_id => "Activity2", cause: ActivityTaskFailedException, details: $error} ],
|
138
|
+
"DecisionTaskScheduled",
|
139
|
+
"DecisionTaskStarted",
|
140
|
+
])))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
workflow_type_object = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
145
|
+
domain = FakeDomain.new(workflow_type_object)
|
146
|
+
|
147
|
+
swf_client = FakeServiceClient.new
|
148
|
+
task_list = "default"
|
149
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
150
|
+
options.workflow_name = "RetryTestWorkflow"
|
151
|
+
options.execution_start_to_close_timeout = 120
|
152
|
+
options.task_list = "default"
|
153
|
+
options.task_start_to_close_timeout = 30
|
154
|
+
end
|
155
|
+
|
156
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, RetryTestWorkflow)
|
157
|
+
my_workflow = my_workflow_factory.get_client
|
158
|
+
workflow_execution = my_workflow.start_execution
|
159
|
+
worker.start
|
160
|
+
swf_client.trace.first[:decisions].first[:decision_type].should == "StartTimer"
|
161
|
+
swf_client.trace.first[:decisions].first[:start_timer_decision_attributes].should == {:timer_id=>"Timer2", :start_to_fire_timeout=>"8"}
|
162
|
+
end
|
163
|
+
|
164
|
+
it "tests part 4 - the workflow fails because retry_expiration_interval_seconds is breached by exponential retry" do
|
165
|
+
|
166
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
167
|
+
def get_decision_task
|
168
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
169
|
+
TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
|
170
|
+
FakeEvents.new(get_history_array().push(*[
|
171
|
+
"DecisionTaskCompleted",
|
172
|
+
["TimerStarted", {decision_task_completed_event_id: 10, timer_id: "Timer1", start_to_fire_timeout: 2 }],
|
173
|
+
["TimerFired", {timer_id: "Timer1", started_event_id: 11}],
|
174
|
+
"DecisionTaskScheduled",
|
175
|
+
"DecisionTaskStarted",
|
176
|
+
"DecisionTaskCompleted",
|
177
|
+
["ActivityTaskScheduled", {activity_id: "Activity2"}],
|
178
|
+
"ActivityTaskStarted",
|
179
|
+
["ActivityTaskFailed", {scheduled_event_id: 16, :activity_id => "Activity2", cause: AWS::Flow::ActivityFailureException, details: $error } ],
|
180
|
+
"DecisionTaskScheduled",
|
181
|
+
"DecisionTaskStarted",
|
182
|
+
"DecisionTaskCompleted",
|
183
|
+
["TimerStarted", {decision_task_completed_event_id: 21, timer_id: "Timer2", start_to_fire_timeout: 8 }],
|
184
|
+
["TimerFired", {timer_id: "Timer2", started_event_id: 22}],
|
185
|
+
"DecisionTaskScheduled",
|
186
|
+
"DecisionTaskStarted",
|
187
|
+
"DecisionTaskCompleted",
|
188
|
+
["ActivityTaskScheduled", {activity_id: "Activity3"}],
|
189
|
+
"ActivityTaskStarted",
|
190
|
+
["ActivityTaskFailed", {scheduled_event_id: 27, :activity_id => "Activity3", cause: ActivityTaskFailedException, details: $error } ],
|
191
|
+
"DecisionTaskScheduled",
|
192
|
+
"DecisionTaskStarted",
|
193
|
+
])))
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
workflow_type_object = FakeWorkflowType.new(nil, "RetryTestWorkflow.start", "1.0")
|
198
|
+
domain = FakeDomain.new(workflow_type_object)
|
199
|
+
|
200
|
+
swf_client = FakeServiceClient.new
|
201
|
+
task_list = "default"
|
202
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
203
|
+
options.workflow_name = "RetryTestWorkflow"
|
204
|
+
options.execution_start_to_close_timeout = 120
|
205
|
+
options.task_list = "default"
|
206
|
+
options.task_start_to_close_timeout = 30
|
207
|
+
end
|
208
|
+
|
209
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, RetryTestWorkflow)
|
210
|
+
my_workflow = my_workflow_factory.get_client
|
211
|
+
workflow_execution = my_workflow.start_execution
|
212
|
+
worker.start
|
213
|
+
swf_client.trace.first[:decisions].first[:decision_type].should == "FailWorkflowExecution"
|
214
|
+
swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details].should =~ /ArgumentError/
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
File without changes
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require_relative 'setup'
|
2
|
+
|
3
|
+
class TestWorkflow
|
4
|
+
extend Workflows
|
5
|
+
workflow :start do
|
6
|
+
{
|
7
|
+
:default_execution_start_to_close_timeout => 30, :version => "1"
|
8
|
+
}
|
9
|
+
end
|
10
|
+
def start; end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestActivity
|
14
|
+
extend Activity
|
15
|
+
|
16
|
+
activity :run_activity1 do |o|
|
17
|
+
o.default_task_heartbeat_timeout = "3600"
|
18
|
+
o.default_task_list = "activity_task_list"
|
19
|
+
o.default_task_schedule_to_close_timeout = "3600"
|
20
|
+
o.default_task_schedule_to_start_timeout = "3600"
|
21
|
+
o.default_task_start_to_close_timeout = "3600"
|
22
|
+
o.version = "1"
|
23
|
+
end
|
24
|
+
def run_activity1
|
25
|
+
"first regular activity"
|
26
|
+
end
|
27
|
+
def run_activity2
|
28
|
+
"second regular activity"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class TestActivityWorker < ActivityWorker
|
33
|
+
|
34
|
+
attr_accessor :executor
|
35
|
+
def initialize(service, domain, task_list, forking_executor, *args, &block)
|
36
|
+
super(service, domain, task_list, *args, &block)
|
37
|
+
@executor = forking_executor
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class FakeTaskPoller < WorkflowTaskPoller
|
42
|
+
def get_decision_task
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def dumb_fib(n)
|
47
|
+
n < 1 ? 1 : dumb_fib(n - 1) + dumb_fib(n - 2)
|
48
|
+
end
|
49
|
+
|
50
|
+
describe GenericWorker do
|
51
|
+
context "#resolve_default_task_list" do
|
52
|
+
worker = GenericWorker.new(nil, nil, "worker_task_list")
|
53
|
+
worker.resolve_default_task_list("USE_WORKER_TASK_LIST").should == "worker_task_list"
|
54
|
+
worker.resolve_default_task_list("passed_in_task_list").should == "passed_in_task_list"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe WorkflowWorker do
|
59
|
+
context "#register" do
|
60
|
+
it "ensures that worker uses the right task list to register type" do
|
61
|
+
class DefaultTasklistTestWorkflow
|
62
|
+
extend Workflows
|
63
|
+
workflow :workflow do
|
64
|
+
{
|
65
|
+
version: "1.0",
|
66
|
+
default_task_list: "USE_WORKER_TASK_LIST"
|
67
|
+
}
|
68
|
+
end
|
69
|
+
workflow :workflow2 do
|
70
|
+
{
|
71
|
+
version: "1.0",
|
72
|
+
default_task_list: "my_own_task_list"
|
73
|
+
}
|
74
|
+
end
|
75
|
+
workflow :workflow3 do
|
76
|
+
{
|
77
|
+
version: "1.0",
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
workflow_options = {
|
82
|
+
default_task_start_to_close_timeout: "30",
|
83
|
+
default_child_policy: "TERMINATE",
|
84
|
+
domain: "UnitTestDomain",
|
85
|
+
name: "DefaultTasklistTestWorkflow.workflow",
|
86
|
+
version: "1.0",
|
87
|
+
default_task_list: {
|
88
|
+
name: "task_list"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
workflow2_options = {
|
92
|
+
default_task_start_to_close_timeout: "30",
|
93
|
+
default_child_policy: "TERMINATE",
|
94
|
+
domain: "UnitTestDomain",
|
95
|
+
name: "DefaultTasklistTestWorkflow.workflow2",
|
96
|
+
version: "1.0",
|
97
|
+
default_task_list: {
|
98
|
+
name: "my_own_task_list"
|
99
|
+
}
|
100
|
+
}
|
101
|
+
workflow3_options = {
|
102
|
+
default_task_start_to_close_timeout: "30",
|
103
|
+
default_child_policy: "TERMINATE",
|
104
|
+
domain: "UnitTestDomain",
|
105
|
+
name: "DefaultTasklistTestWorkflow.workflow3",
|
106
|
+
version: "1.0",
|
107
|
+
default_task_list: {
|
108
|
+
name: "task_list"
|
109
|
+
}
|
110
|
+
}
|
111
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125).to receive(:register_workflow_type).with(workflow_options)
|
112
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125).to receive(:register_workflow_type).with(workflow2_options)
|
113
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125).to receive(:register_workflow_type).with(workflow3_options)
|
114
|
+
|
115
|
+
worker = AWS::Flow::WorkflowWorker.new(
|
116
|
+
AWS::SimpleWorkflow.new.client,
|
117
|
+
AWS::SimpleWorkflow::Domain.new("UnitTestDomain"),
|
118
|
+
"task_list",
|
119
|
+
DefaultTasklistTestWorkflow
|
120
|
+
)
|
121
|
+
|
122
|
+
worker.register
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "will test whether WorkflowWorker shuts down cleanly when an interrupt is received" do
|
127
|
+
task_list = "TestWorkflow_tasklist"
|
128
|
+
service = FakeServiceClient.new
|
129
|
+
workflow_type_object = double("workflow_type", :name => "TestWorkflow.start", :start_execution => "" )
|
130
|
+
domain = FakeDomain.new(workflow_type_object)
|
131
|
+
workflow_worker = WorkflowWorker.new(service, domain, task_list)
|
132
|
+
workflow_worker.add_workflow_implementation(TestWorkflow)
|
133
|
+
pid = fork do
|
134
|
+
loop do
|
135
|
+
workflow_worker.run_once(true, FakeTaskPoller.new(service, domain, nil, task_list, nil))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
# Send an interrupt to the child process
|
139
|
+
Process.kill("INT", pid)
|
140
|
+
# Adding a sleep to let things get setup correctly (not ideal but going with
|
141
|
+
# this for now)
|
142
|
+
sleep 5
|
143
|
+
return_pid, status = Process.wait2(pid, Process::WNOHANG)
|
144
|
+
Process.kill("KILL", pid) if return_pid.nil?
|
145
|
+
return_pid.should_not be nil
|
146
|
+
status.success?.should be_true
|
147
|
+
end
|
148
|
+
|
149
|
+
it "will test whether WorkflowWorker dies cleanly when two interrupts are received" do
|
150
|
+
class FakeTaskPoller
|
151
|
+
def poll_and_process_single_task
|
152
|
+
dumb_fib(5000)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
task_list = "TestWorkflow_tasklist"
|
156
|
+
service = FakeServiceClient.new
|
157
|
+
workflow_type_object = double("workflow_type", :name => "TestWorkflow.start", :start_execution => "" )
|
158
|
+
domain = FakeDomain.new(workflow_type_object)
|
159
|
+
workflow_worker = WorkflowWorker.new(service, domain, task_list)
|
160
|
+
workflow_worker.add_workflow_implementation(TestWorkflow)
|
161
|
+
pid = fork do
|
162
|
+
loop do
|
163
|
+
workflow_worker.run_once(true, FakeTaskPoller.new(service, domain, nil, task_list, nil))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
# Send an interrupt to the child process
|
167
|
+
sleep 3
|
168
|
+
2.times { Process.kill("INT", pid); sleep 2 }
|
169
|
+
return_pid, status = Process.wait2(pid, Process::WNOHANG)
|
170
|
+
|
171
|
+
Process.kill("KILL", pid) if return_pid.nil?
|
172
|
+
return_pid.should_not be nil
|
173
|
+
status.success?.should be_false
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
describe ActivityWorker do
|
179
|
+
|
180
|
+
context "#register" do
|
181
|
+
it "ensures that worker uses the right task list to register type" do
|
182
|
+
class DefaultTasklistTestActivity
|
183
|
+
extend Activities
|
184
|
+
activity :activity do
|
185
|
+
{
|
186
|
+
version: "1.0",
|
187
|
+
default_task_list: "USE_WORKER_TASK_LIST"
|
188
|
+
}
|
189
|
+
end
|
190
|
+
activity :activity2 do
|
191
|
+
{
|
192
|
+
version: "1.0",
|
193
|
+
default_task_list: "my_own_task_list"
|
194
|
+
}
|
195
|
+
end
|
196
|
+
activity :activity3 do
|
197
|
+
{
|
198
|
+
version: "1.0"
|
199
|
+
}
|
200
|
+
end
|
201
|
+
end
|
202
|
+
activity_options = {
|
203
|
+
domain: "UnitTestDomain",
|
204
|
+
name: "DefaultTasklistTestActivity.activity",
|
205
|
+
version: "1.0",
|
206
|
+
default_task_heartbeat_timeout: "NONE",
|
207
|
+
default_task_schedule_to_close_timeout: "NONE",
|
208
|
+
default_task_schedule_to_start_timeout: "NONE",
|
209
|
+
default_task_start_to_close_timeout: "NONE",
|
210
|
+
default_task_list: {
|
211
|
+
name: "task_list"
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
activity2_options = {
|
216
|
+
domain: "UnitTestDomain",
|
217
|
+
name: "DefaultTasklistTestActivity.activity2",
|
218
|
+
version: "1.0",
|
219
|
+
default_task_heartbeat_timeout: "NONE",
|
220
|
+
default_task_schedule_to_close_timeout: "NONE",
|
221
|
+
default_task_schedule_to_start_timeout: "NONE",
|
222
|
+
default_task_start_to_close_timeout: "NONE",
|
223
|
+
default_task_list: {
|
224
|
+
name: "my_own_task_list"
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
activity3_options = {
|
229
|
+
domain: "UnitTestDomain",
|
230
|
+
name: "DefaultTasklistTestActivity.activity3",
|
231
|
+
version: "1.0",
|
232
|
+
default_task_heartbeat_timeout: "NONE",
|
233
|
+
default_task_schedule_to_close_timeout: "NONE",
|
234
|
+
default_task_schedule_to_start_timeout: "NONE",
|
235
|
+
default_task_start_to_close_timeout: "NONE",
|
236
|
+
default_task_list: {
|
237
|
+
name: "task_list"
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125).to receive(:register_activity_type).with(activity_options)
|
242
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125).to receive(:register_activity_type).with(activity2_options)
|
243
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125).to receive(:register_activity_type).with(activity3_options)
|
244
|
+
|
245
|
+
worker = AWS::Flow::ActivityWorker.new(
|
246
|
+
AWS::SimpleWorkflow.new.client,
|
247
|
+
AWS::SimpleWorkflow::Domain.new("UnitTestDomain"),
|
248
|
+
"task_list",
|
249
|
+
DefaultTasklistTestActivity
|
250
|
+
)
|
251
|
+
|
252
|
+
worker.register
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class FakeDomain
|
257
|
+
def activity_tasks
|
258
|
+
sleep 30
|
259
|
+
end
|
260
|
+
end
|
261
|
+
it "will test whether the ActivityWorker shuts down cleanly when an interrupt is received" do
|
262
|
+
|
263
|
+
task_list = "TestWorkflow_tasklist"
|
264
|
+
service = FakeServiceClient.new
|
265
|
+
workflow_type_object = double("workflow_type", :name => "TestWorkflow.start", :start_execution => "" )
|
266
|
+
domain = FakeDomain.new(workflow_type_object)
|
267
|
+
forking_executor = ForkingExecutor.new
|
268
|
+
activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor) { {:logger => FakeLogger.new} }
|
269
|
+
activity_worker.add_activities_implementation(TestActivity)
|
270
|
+
# Starts the activity worker in a forked process. Also, attaches an at_exit
|
271
|
+
# handler to the process. When the process exits, the handler checks whether
|
272
|
+
# the executor's internal is_shutdown variable is set correctly or not.
|
273
|
+
pid = fork do
|
274
|
+
at_exit {
|
275
|
+
activity_worker.executor.is_shutdown.should == true
|
276
|
+
}
|
277
|
+
activity_worker.start true
|
278
|
+
end
|
279
|
+
# Send an interrupt to the child process
|
280
|
+
Process.kill("INT", pid)
|
281
|
+
# Adding a sleep to let things get setup correctly (not ideal but going with
|
282
|
+
# this for now)
|
283
|
+
sleep 5
|
284
|
+
return_pid, status = Process.wait2(pid, Process::WNOHANG)
|
285
|
+
Process.kill("KILL", pid) if return_pid.nil?
|
286
|
+
return_pid.should_not be nil
|
287
|
+
|
288
|
+
status.success?.should be_true
|
289
|
+
end
|
290
|
+
|
291
|
+
# This method will take a long time to run, allowing us to test our shutdown
|
292
|
+
# scenarios
|
293
|
+
|
294
|
+
|
295
|
+
it "will test whether the ActivityWorker shuts down immediately if two or more interrupts are received" do
|
296
|
+
task_list = "TestWorkflow_tasklist"
|
297
|
+
service = FakeServiceClient.new
|
298
|
+
workflow_type_object = double("workflow_type", :name => "TestWorkflow.start", :start_execution => "" )
|
299
|
+
domain = FakeDomain.new(workflow_type_object)
|
300
|
+
forking_executor = ForkingExecutor.new
|
301
|
+
activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor) { {:logger => FakeLogger.new} }
|
302
|
+
|
303
|
+
activity_worker.add_activities_implementation(TestActivity)
|
304
|
+
# Starts the activity worker in a forked process. Also, executes a task
|
305
|
+
# using the forking executor of the activity worker. The executor will
|
306
|
+
# create a child process to run that task. The task (dumb_fib) is
|
307
|
+
# purposefully designed to be long running so that we can test our shutdown
|
308
|
+
# scenario.
|
309
|
+
pid = fork do
|
310
|
+
activity_worker.executor.execute {
|
311
|
+
dumb_fib(1000)
|
312
|
+
}
|
313
|
+
activity_worker.start true
|
314
|
+
end
|
315
|
+
# Adding a sleep to let things get setup correctly (not idea but going with
|
316
|
+
# this for now)
|
317
|
+
sleep 3
|
318
|
+
# Send 2 interrupts to the child process
|
319
|
+
2.times { Process.kill("INT", pid); sleep 3 }
|
320
|
+
status = Process.waitall
|
321
|
+
status[0][1].success?.should be_false
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|