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.
Files changed (48) hide show
  1. checksums.yaml +15 -0
  2. data/aws-flow.gemspec +1 -0
  3. data/lib/aws/decider/activity.rb +8 -6
  4. data/lib/aws/decider/async_decider.rb +1 -0
  5. data/lib/aws/decider/async_retrying_executor.rb +3 -3
  6. data/lib/aws/decider/decider.rb +16 -14
  7. data/lib/aws/decider/executor.rb +35 -22
  8. data/lib/aws/decider/flow_defaults.rb +28 -14
  9. data/lib/aws/decider/generic_client.rb +3 -4
  10. data/lib/aws/decider/options.rb +91 -117
  11. data/lib/aws/decider/state_machines.rb +1 -0
  12. data/lib/aws/decider/utilities.rb +15 -0
  13. data/lib/aws/decider/version.rb +1 -1
  14. data/lib/aws/decider/worker.rb +14 -8
  15. data/lib/aws/decider/workflow_client.rb +16 -11
  16. data/lib/aws/runner.rb +43 -39
  17. data/spec/aws/decider/integration/activity_spec.rb +345 -0
  18. data/spec/aws/{integration → decider/integration}/integration_spec.rb +818 -1183
  19. data/spec/aws/decider/integration/setup.rb +3 -0
  20. data/spec/aws/decider/unit/activity_spec.rb +233 -0
  21. data/spec/aws/decider/unit/async_retrying_executor_spec.rb +131 -0
  22. data/spec/aws/{unit → decider/unit}/decider_spec.rb +171 -718
  23. data/spec/aws/decider/unit/executor_spec.rb +123 -0
  24. data/spec/aws/decider/unit/flow_defaults_spec.rb +62 -0
  25. data/spec/aws/decider/unit/misc_spec.rb +101 -0
  26. data/spec/aws/decider/unit/options_spec.rb +289 -0
  27. data/spec/aws/decider/unit/retry_spec.rb +217 -0
  28. data/spec/aws/{unit → decider/unit}/rubyflow.rb +0 -0
  29. data/spec/aws/decider/unit/setup.rb +3 -0
  30. data/spec/aws/decider/unit/worker_spec.rb +325 -0
  31. data/spec/aws/decider/unit/workflow_client_spec.rb +83 -0
  32. data/spec/aws/{unit → flow}/async_backtrace_spec.rb +0 -0
  33. data/spec/aws/{unit → flow}/async_scope_spec.rb +0 -0
  34. data/spec/aws/{unit → flow}/begin_rescue_ensure_spec.rb +1 -0
  35. data/spec/aws/{unit → flow}/external_task_spec.rb +0 -0
  36. data/spec/aws/{unit → flow}/factories.rb +0 -0
  37. data/spec/aws/{unit → flow}/fiber_condition_variable_spec.rb +0 -0
  38. data/spec/aws/{unit → flow}/fiber_spec.rb +0 -0
  39. data/spec/aws/{unit → flow}/flow_spec.rb +0 -0
  40. data/spec/aws/{unit → flow}/future_spec.rb +0 -0
  41. data/spec/aws/{unit → flow}/simple_dfa_spec.rb +0 -0
  42. data/spec/aws/{integration → runner/integration}/runner_integration_spec.rb +16 -43
  43. data/spec/aws/{unit → runner/unit}/runner_unit_spec.rb +18 -18
  44. data/spec/spec_helper.rb +264 -2
  45. metadata +37 -28
  46. data/spec/aws/unit/executor_spec.rb +0 -49
  47. data/spec/aws/unit/options_spec.rb +0 -293
  48. 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
@@ -0,0 +1,3 @@
1
+ require 'spec_helper'
2
+
3
+ include Test::Unit
@@ -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
+