aws-flow 1.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.
- data/Gemfile +8 -0
- data/LICENSE.TXT +15 -0
- data/NOTICE.TXT +14 -0
- data/Rakefile +39 -0
- data/aws-flow-core/Gemfile +9 -0
- data/aws-flow-core/LICENSE.TXT +15 -0
- data/aws-flow-core/NOTICE.TXT +14 -0
- data/aws-flow-core/Rakefile +27 -0
- data/aws-flow-core/aws-flow-core.gemspec +12 -0
- data/aws-flow-core/lib/aws/flow.rb +26 -0
- data/aws-flow-core/lib/aws/flow/async_backtrace.rb +134 -0
- data/aws-flow-core/lib/aws/flow/async_scope.rb +195 -0
- data/aws-flow-core/lib/aws/flow/begin_rescue_ensure.rb +386 -0
- data/aws-flow-core/lib/aws/flow/fiber.rb +77 -0
- data/aws-flow-core/lib/aws/flow/flow_utils.rb +50 -0
- data/aws-flow-core/lib/aws/flow/future.rb +109 -0
- data/aws-flow-core/lib/aws/flow/implementation.rb +151 -0
- data/aws-flow-core/lib/aws/flow/simple_dfa.rb +85 -0
- data/aws-flow-core/lib/aws/flow/tasks.rb +405 -0
- data/aws-flow-core/test/aws/async_backtrace_spec.rb +41 -0
- data/aws-flow-core/test/aws/async_scope_spec.rb +118 -0
- data/aws-flow-core/test/aws/begin_rescue_ensure_spec.rb +665 -0
- data/aws-flow-core/test/aws/external_task_spec.rb +197 -0
- data/aws-flow-core/test/aws/factories.rb +52 -0
- data/aws-flow-core/test/aws/fiber_condition_variable_spec.rb +163 -0
- data/aws-flow-core/test/aws/fiber_spec.rb +78 -0
- data/aws-flow-core/test/aws/flow_spec.rb +255 -0
- data/aws-flow-core/test/aws/future_spec.rb +210 -0
- data/aws-flow-core/test/aws/rubyflow.rb +22 -0
- data/aws-flow-core/test/aws/simple_dfa_spec.rb +63 -0
- data/aws-flow-core/test/aws/spec_helper.rb +36 -0
- data/aws-flow.gemspec +13 -0
- data/lib/aws/decider.rb +67 -0
- data/lib/aws/decider/activity.rb +408 -0
- data/lib/aws/decider/activity_definition.rb +111 -0
- data/lib/aws/decider/async_decider.rb +673 -0
- data/lib/aws/decider/async_retrying_executor.rb +153 -0
- data/lib/aws/decider/data_converter.rb +40 -0
- data/lib/aws/decider/decider.rb +511 -0
- data/lib/aws/decider/decision_context.rb +60 -0
- data/lib/aws/decider/exceptions.rb +178 -0
- data/lib/aws/decider/executor.rb +149 -0
- data/lib/aws/decider/flow_defaults.rb +70 -0
- data/lib/aws/decider/generic_client.rb +178 -0
- data/lib/aws/decider/history_helper.rb +173 -0
- data/lib/aws/decider/implementation.rb +82 -0
- data/lib/aws/decider/options.rb +607 -0
- data/lib/aws/decider/state_machines.rb +373 -0
- data/lib/aws/decider/task_handler.rb +76 -0
- data/lib/aws/decider/task_poller.rb +207 -0
- data/lib/aws/decider/utilities.rb +187 -0
- data/lib/aws/decider/worker.rb +324 -0
- data/lib/aws/decider/workflow_client.rb +374 -0
- data/lib/aws/decider/workflow_clock.rb +104 -0
- data/lib/aws/decider/workflow_definition.rb +101 -0
- data/lib/aws/decider/workflow_definition_factory.rb +53 -0
- data/lib/aws/decider/workflow_enabled.rb +26 -0
- data/test/aws/decider_spec.rb +1299 -0
- data/test/aws/factories.rb +45 -0
- data/test/aws/integration_spec.rb +3108 -0
- data/test/aws/spec_helper.rb +23 -0
- metadata +138 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
module AWS
|
17
|
+
module Flow
|
18
|
+
|
19
|
+
# @!visibility private
|
20
|
+
module DecisionStateMachineDFA
|
21
|
+
attr_accessor :transitions, :symbols, :states, :start_state
|
22
|
+
|
23
|
+
def init(start_state)
|
24
|
+
include InstanceMethods
|
25
|
+
@start_state = start_state
|
26
|
+
@symbols = []
|
27
|
+
@states = []
|
28
|
+
@transitions = {}
|
29
|
+
@states << start_state
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_start_state
|
33
|
+
@start_state
|
34
|
+
end
|
35
|
+
|
36
|
+
def self_transitions(symbol)
|
37
|
+
states.each do |state|
|
38
|
+
add_transition(state, symbol, state) unless @transitions[[state, symbol]]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_transitions
|
43
|
+
# Turns out, you are your own ancestor
|
44
|
+
ancestors.slice(1..-1).map {|x| x.transitions if x.respond_to? :transitions}.compact.
|
45
|
+
inject({}) {|x, y| x.merge(y)}.merge(@transitions)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_transition(state, symbol, next_state, function = nil)
|
49
|
+
@symbols << symbol unless @symbols.include? symbol
|
50
|
+
[state, next_state].each do |this_state|
|
51
|
+
@states << this_state unless @states.include? this_state
|
52
|
+
end
|
53
|
+
@transitions[[state, symbol]] = [next_state, function]
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_transitions(list_of_transitions)
|
57
|
+
list_of_transitions.each {|transition| add_transition(*transition)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def uncovered_transitions
|
61
|
+
@states.product(@symbols) - @transitions.keys
|
62
|
+
end
|
63
|
+
|
64
|
+
module InstanceMethods
|
65
|
+
attr_accessor :current_state
|
66
|
+
|
67
|
+
def consume(symbol)
|
68
|
+
@state_history ||= [self.class.get_start_state]
|
69
|
+
@state_history << @current_state
|
70
|
+
@state_history << symbol
|
71
|
+
transition_tuple = self.class.get_transitions[[@current_state, symbol]]
|
72
|
+
raise "This is not a legal transition, attempting to consume #{symbol} while at state #{current_state}" unless transition_tuple
|
73
|
+
@current_state, func_to_call = transition_tuple
|
74
|
+
@state_history << @current_state
|
75
|
+
func_to_call.call(self) if func_to_call
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @!visibility private
|
81
|
+
class CompleteWorkflowStateMachine
|
82
|
+
extend DecisionStateMachineDFA
|
83
|
+
attr_reader :id
|
84
|
+
def consume(symbol)
|
85
|
+
return @decision = nil if symbol == :handle_initiation_failed_event
|
86
|
+
return if symbol == :handle_decision_task_started_event
|
87
|
+
raise "UnsupportedOperation"
|
88
|
+
end
|
89
|
+
# Creates a new CompleteWorkflowStateMachine
|
90
|
+
#
|
91
|
+
# @param id
|
92
|
+
# The decider id.
|
93
|
+
#
|
94
|
+
# @param attributes
|
95
|
+
#
|
96
|
+
|
97
|
+
def done?
|
98
|
+
! @decision.nil?
|
99
|
+
end
|
100
|
+
def initialize(id, decision)
|
101
|
+
@id = id
|
102
|
+
@decision = decision
|
103
|
+
@current_state = :created
|
104
|
+
end
|
105
|
+
init(:created)
|
106
|
+
|
107
|
+
def get_decision
|
108
|
+
return @decision
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# @!visibility private
|
114
|
+
class DecisionStateMachineBase
|
115
|
+
extend DecisionStateMachineDFA
|
116
|
+
attr_reader :id
|
117
|
+
|
118
|
+
def initialize(id)
|
119
|
+
@id = id
|
120
|
+
@current_state = :created
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def handle_started_event(event)
|
125
|
+
@state_history << :handle_started_event
|
126
|
+
end
|
127
|
+
|
128
|
+
init(:created)
|
129
|
+
|
130
|
+
add_transitions [
|
131
|
+
[:cancellation_decision_sent, :handle_cancellation_event, :completed],
|
132
|
+
[:cancellation_decision_sent, :handle_cancellation_initiated_event, :cancellation_decision_sent],
|
133
|
+
[:cancellation_decision_sent, :handle_completion_event, :completed_after_cancellation_decision_sent],
|
134
|
+
[:cancelled_after_initiated, :handle_completion_event, :cancelled_after_initiated],
|
135
|
+
[:cancelled_before_initiated, :handle_initiated_event, :cancelled_after_initiated],
|
136
|
+
[:cancelled_before_initiated, :handle_initiation_failed_event, :completed],
|
137
|
+
[:completed_after_cancellation_decision_sent, :handle_cancellation_failure_event, :completed],
|
138
|
+
[:created, :cancel, :completed, lambda { |immediate_cancellation_callback| immediate_cancellation_callback.run }],
|
139
|
+
[:created, :handle_decision_task_started_event, :decision_sent],
|
140
|
+
[:decision_sent, :cancel, :cancelled_before_initiated],
|
141
|
+
[:decision_sent, :handle_initiated_event, :initiated],
|
142
|
+
[:decision_sent, :handle_initiation_failed_event, :decision_sent],
|
143
|
+
[:initiated, :cancel, :cancelled_after_initiated],
|
144
|
+
[:initiated, :handle_completion_event, :completed],
|
145
|
+
[:started, :handle_decision_task_started_event, :started],
|
146
|
+
]
|
147
|
+
self_transitions(:handle_decision_task_started_event)
|
148
|
+
|
149
|
+
def done?
|
150
|
+
@current_state == :completed || @current_state == :completed_after_cancellation_decision_sent
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# @!visibility private
|
156
|
+
class ActivityDecisionStateMachine < DecisionStateMachineBase
|
157
|
+
|
158
|
+
attr_reader :attributes
|
159
|
+
# Creates a new `ActivityDecisionStateMachine`.
|
160
|
+
#
|
161
|
+
# @param [DecisionID] decision_id
|
162
|
+
#
|
163
|
+
# @param attributes
|
164
|
+
#
|
165
|
+
def initialize(decision_id, attributes)
|
166
|
+
@attributes = attributes
|
167
|
+
super(decision_id)
|
168
|
+
end
|
169
|
+
init(:created)
|
170
|
+
add_transitions [
|
171
|
+
[:cancelled_after_initiated, :handle_decision_task_started_event, :cancellation_decision_sent],
|
172
|
+
[:cancellation_decision_sent, :handle_cancellation_failure_event, :initiated]
|
173
|
+
]
|
174
|
+
def get_decision
|
175
|
+
case @current_state
|
176
|
+
when :created
|
177
|
+
return create_schedule_activity_task_decision
|
178
|
+
when :cancelled_after_initiated
|
179
|
+
return create_request_cancel_activity_task_decision
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def create_schedule_activity_task_decision
|
184
|
+
options = @attributes[:options]
|
185
|
+
attribute_type = :schedule_activity_task_decision_attributes
|
186
|
+
result = { :decision_type => "ScheduleActivityTask",
|
187
|
+
attribute_type =>
|
188
|
+
{
|
189
|
+
:activity_type =>
|
190
|
+
{
|
191
|
+
:name => @attributes[:activity_type].name.to_s,
|
192
|
+
:version => options.version.to_s
|
193
|
+
},
|
194
|
+
:activity_id => @attributes[:decision_id].to_s,
|
195
|
+
}
|
196
|
+
}
|
197
|
+
task_list = options.task_list ? {:task_list => {:name => options.task_list}} : {}
|
198
|
+
to_add = options.get_options([:heartbeat_timeout, :schedule_to_close_timeout, :schedule_to_start_timeout, :start_to_close_timeout, :input], task_list)
|
199
|
+
result[attribute_type].merge!(to_add)
|
200
|
+
result
|
201
|
+
end
|
202
|
+
|
203
|
+
def create_request_cancel_activity_task_decision
|
204
|
+
{ :decision_type => "RequestCancelActivityTask",
|
205
|
+
:request_cancel_activity_task_decision_attributes => {:activity_id => @attributes[:decision_id]} }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# @!visibility private
|
211
|
+
class TimerDecisionStateMachine < DecisionStateMachineBase
|
212
|
+
attr_accessor :cancelled
|
213
|
+
def initialize(decision_id, attributes)
|
214
|
+
@attributes = attributes
|
215
|
+
super(decision_id)
|
216
|
+
end
|
217
|
+
|
218
|
+
def create_start_timer_decision
|
219
|
+
{
|
220
|
+
:decision_type => "StartTimer",
|
221
|
+
:start_timer_decision_attributes =>
|
222
|
+
{
|
223
|
+
:timer_id => @attributes[:timer_id].to_s,
|
224
|
+
# TODO find out what the "control" field is, and what it is for
|
225
|
+
:start_to_fire_timeout => @attributes[:start_to_fire_timeout]
|
226
|
+
}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
def create_cancel_timer_decision
|
231
|
+
{
|
232
|
+
:decision_type => "CancelTimer",
|
233
|
+
:cancel_timer_decision_attributes => {
|
234
|
+
:timer_id => @attributes[:timer_id].to_s,
|
235
|
+
}
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
def get_decision
|
240
|
+
case @current_state
|
241
|
+
when :created
|
242
|
+
return create_start_timer_decision
|
243
|
+
when :cancelled_after_initiated
|
244
|
+
return create_cancel_timer_decision
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def done?
|
249
|
+
@current_state == :completed || @cancelled
|
250
|
+
end
|
251
|
+
|
252
|
+
init(:created)
|
253
|
+
add_transitions [
|
254
|
+
[:cancelled_after_initiated, :handle_decision_task_started_event, :cancellation_decision_sent],
|
255
|
+
[:cancellation_decision_sent, :handle_cancellation_failure_event, :initiated],
|
256
|
+
]
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
# @!visibility private
|
261
|
+
class SignalDecisionStateMachine < DecisionStateMachineBase
|
262
|
+
def initialize(decision_id, attributes)
|
263
|
+
@attributes = attributes
|
264
|
+
super(decision_id)
|
265
|
+
end
|
266
|
+
|
267
|
+
def get_decision
|
268
|
+
case @current_state
|
269
|
+
when :created
|
270
|
+
return create_signal_external_workflow_execution_decison
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def create_signal_external_workflow_execution_decison
|
275
|
+
extra_options = {}
|
276
|
+
[:input, :control, :run_id].each do |type|
|
277
|
+
extra_options[type] = @attributes.send(type) if @attributes.send(type)
|
278
|
+
end
|
279
|
+
result = {
|
280
|
+
:decision_type => "SignalExternalWorkflowExecution",
|
281
|
+
:signal_external_workflow_execution_decision_attributes =>
|
282
|
+
{
|
283
|
+
:signal_name => @attributes.signal_name,
|
284
|
+
:workflow_id => @attributes.workflow_id
|
285
|
+
}
|
286
|
+
}
|
287
|
+
if ! extra_options.empty?
|
288
|
+
result[:signal_external_workflow_execution_decision_attributes].merge! extra_options
|
289
|
+
end
|
290
|
+
result
|
291
|
+
end
|
292
|
+
init(:created)
|
293
|
+
add_transitions [
|
294
|
+
[:created, :handle_decision_task_started_event, :decision_sent],
|
295
|
+
[:created, :cancel, :created],
|
296
|
+
[:initiated, :cancel, :completed, lambda {|immediate_cancellation_callback| immediate_cancellation_callback.run }],
|
297
|
+
[:decision_sent, :handle_initiated_event, :initiated],
|
298
|
+
[:cancelled_before_initiated, :handle_initiated_event, :cancelled_before_initiated],
|
299
|
+
[:decision_sent, :handle_completion_event, :completed],
|
300
|
+
[:initiated, :handle_completion_event, :completed],
|
301
|
+
[:cancelled_before_initiated, :handle_completion_event, :completed],
|
302
|
+
[:completed, :handle_completion_event, :completed]
|
303
|
+
]
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
# @!visibility private
|
309
|
+
class ChildWorkflowDecisionStateMachine < DecisionStateMachineBase
|
310
|
+
attr_accessor :run_id, :attributes
|
311
|
+
def initialize(decision_id, attributes)
|
312
|
+
@attributes = attributes
|
313
|
+
super(decision_id)
|
314
|
+
end
|
315
|
+
|
316
|
+
def create_start_child_workflow_execution_decision
|
317
|
+
options = @attributes[:options]
|
318
|
+
workflow_name = options.workflow_name || options.prefix_name
|
319
|
+
attribute_name = :start_child_workflow_execution_decision_attributes
|
320
|
+
result = {
|
321
|
+
:decision_type => "StartChildWorkflowExecution",
|
322
|
+
attribute_name =>
|
323
|
+
{
|
324
|
+
:workflow_type =>
|
325
|
+
{
|
326
|
+
:name => "#{workflow_name}.#{options.execution_method}",
|
327
|
+
:version => options.version
|
328
|
+
},
|
329
|
+
:workflow_id => @attributes[:workflow_id].to_s,
|
330
|
+
:task_list => {
|
331
|
+
:name => options.task_list
|
332
|
+
},
|
333
|
+
# :control => @attributes[:control]
|
334
|
+
:tag_list => @attributes[:tag_list]
|
335
|
+
}
|
336
|
+
}
|
337
|
+
#TODO Figure out what control is
|
338
|
+
to_add = options.get_options([:execution_start_to_close_timeout, :task_start_to_close_timeout, :child_policy, :tag_list, :input])
|
339
|
+
result[attribute_name].merge!(to_add)
|
340
|
+
result
|
341
|
+
end
|
342
|
+
|
343
|
+
def create_request_cancel_external_workflow_execution_decision
|
344
|
+
result = {
|
345
|
+
:decision_type => "RequestCancelExternalWorkflowExecution",
|
346
|
+
:request_cancel_external_workflow_execution_decision_attributes => {
|
347
|
+
:workflow_id => @attributes[:workflow_id].to_s,
|
348
|
+
:run_id => @run_id.to_s,
|
349
|
+
}
|
350
|
+
}
|
351
|
+
end
|
352
|
+
|
353
|
+
def get_decision
|
354
|
+
case @current_state
|
355
|
+
when :created
|
356
|
+
return create_start_child_workflow_execution_decision
|
357
|
+
when :cancelled_after_started
|
358
|
+
return create_request_cancel_external_workflow_execution_decision
|
359
|
+
end
|
360
|
+
end
|
361
|
+
init(:created)
|
362
|
+
add_transitions [
|
363
|
+
[:cancelled_after_started, :handle_decision_task_started_event, :cancellation_decision_sent],
|
364
|
+
[:initiated, :handle_started_event, :started],
|
365
|
+
[:cancelled_after_initiated, :handle_started_event, :cancelled_after_started],
|
366
|
+
[:cancellation_decision_sent, :handle_cancellation_failure_event, :started],
|
367
|
+
[:started, :cancel, :cancelled_after_started],
|
368
|
+
[:started, :handle_completion_event, :completed],
|
369
|
+
[:cancelled_after_started, :handle_completion_event, :completed],
|
370
|
+
]
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
module AWS
|
17
|
+
module Flow
|
18
|
+
|
19
|
+
|
20
|
+
# A decision task handler to work with a {WorkflowTaskPoller}. Create a DecisionTaskHandler and pass it to
|
21
|
+
# WorkflowTaskPoller on {WorkflowTaskPoller#initialize construction}.
|
22
|
+
class DecisionTaskHandler
|
23
|
+
|
24
|
+
# Creates a new DecisionTaskHandler
|
25
|
+
#
|
26
|
+
# @param workflow_definition_map
|
27
|
+
#
|
28
|
+
# @param options
|
29
|
+
# An optional logger.
|
30
|
+
#
|
31
|
+
def initialize(workflow_definition_map, options=nil)
|
32
|
+
@workflow_definition_map = workflow_definition_map
|
33
|
+
@logger = options.logger if options
|
34
|
+
@logger ||= Utilities::LogFactory.make_logger(self, "debug")
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Handles a decision task
|
39
|
+
#
|
40
|
+
# @param decision_task_iterator
|
41
|
+
#
|
42
|
+
def handle_decision_task(decision_task_iterator)
|
43
|
+
history_helper = HistoryHelper.new(decision_task_iterator)
|
44
|
+
@logger.debug "made history_helper"
|
45
|
+
decider = create_async_decider(history_helper)
|
46
|
+
@logger.debug "made async_decider"
|
47
|
+
decider.decide
|
48
|
+
@logger.debug "decided"
|
49
|
+
decisions = decider.get_decisions
|
50
|
+
response = {:task_token => decider.task_token}
|
51
|
+
context_data = decider.decision_helper.workflow_context_data
|
52
|
+
response[:execution_context] = context_data.to_s unless context_data.nil?
|
53
|
+
response[:decisions] = decisions unless decisions.nil?
|
54
|
+
return response
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates a new asynchronous decider.
|
58
|
+
#
|
59
|
+
# @param history_helper
|
60
|
+
#
|
61
|
+
# @return [AsyncDecider] the created AsyncDecider.
|
62
|
+
#
|
63
|
+
def create_async_decider(history_helper)
|
64
|
+
task = history_helper.get_decision_task
|
65
|
+
workflow_type = task.workflow_type
|
66
|
+
# TODO put in context correctly
|
67
|
+
workflow_definition_factory = @workflow_definition_map[workflow_type]
|
68
|
+
raise "No such definition for #{workflow_type}" if workflow_definition_factory.nil?
|
69
|
+
AsyncDecider.new(workflow_definition_factory, history_helper, DecisionHelper.new)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
require 'tmpdir'
|
17
|
+
|
18
|
+
module AWS
|
19
|
+
module Flow
|
20
|
+
|
21
|
+
class WorkflowTaskPoller
|
22
|
+
|
23
|
+
|
24
|
+
# Creates a new WorkflowTaskPoller
|
25
|
+
#
|
26
|
+
# @param service
|
27
|
+
# The Amazon SWF service object on which this task poller will operate.
|
28
|
+
#
|
29
|
+
# @param [String] domain
|
30
|
+
# The name of the domain containing the task lists to poll.
|
31
|
+
#
|
32
|
+
# @param [DecisionTaskHandler] handler
|
33
|
+
# A {DecisionTaskHandler} to handle polled tasks. The poller will call the {DecisionTaskHandler#handle_decision_task} method.
|
34
|
+
#
|
35
|
+
# @param [Array] task_list
|
36
|
+
# Specifies the task list to poll for decision tasks.
|
37
|
+
#
|
38
|
+
# @param [Object] options
|
39
|
+
# Options to use for the logger.
|
40
|
+
#
|
41
|
+
def initialize(service, domain, handler, task_list, options=nil)
|
42
|
+
@service = service
|
43
|
+
@handler = handler
|
44
|
+
@domain = domain
|
45
|
+
@task_list = task_list
|
46
|
+
@logger = options.logger if options
|
47
|
+
@logger ||= Utilities::LogFactory.make_logger(self, "debug")
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
# Retrieves any decision tasks that are ready.
|
53
|
+
def get_decision_tasks
|
54
|
+
@domain.decision_tasks.poll_for_single_task(@task_list)
|
55
|
+
end
|
56
|
+
|
57
|
+
def poll_and_process_single_task
|
58
|
+
# TODO waitIfSuspended
|
59
|
+
begin
|
60
|
+
@logger.debug "Starting a new task...\n\n\n"
|
61
|
+
tasks = get_decision_tasks
|
62
|
+
return false if tasks.nil?
|
63
|
+
@logger.debug "We have this many tasks #{tasks}"
|
64
|
+
@logger.debug "debugging on #{tasks}\n"
|
65
|
+
task_completed_request = @handler.handle_decision_task(tasks)
|
66
|
+
@logger.debug "task to be responded to with #{task_completed_request}\n"
|
67
|
+
if !task_completed_request[:decisions].empty? && (task_completed_request[:decisions].first.keys.include?(:fail_workflow_execution_decision_attributes))
|
68
|
+
fail_hash = task_completed_request[:decisions].first[:fail_workflow_execution_decision_attributes]
|
69
|
+
reason = fail_hash[:reason]
|
70
|
+
details = fail_hash[:details]
|
71
|
+
@logger.debug "#{reason}, #{details}"
|
72
|
+
end
|
73
|
+
@service.respond_decision_task_completed(task_completed_request)
|
74
|
+
rescue AWS::SimpleWorkflow::Errors::UnknownResourceFault => e
|
75
|
+
# Log stuff
|
76
|
+
@logger.debug "Error in the poller, #{e}"
|
77
|
+
@logger.debug "The error class in #{e.class}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ActivityTaskPoller
|
83
|
+
def initialize(service, domain, task_list, activity_definition_map, options=nil)
|
84
|
+
@service = service
|
85
|
+
@domain = domain
|
86
|
+
@task_list = task_list
|
87
|
+
@activity_definition_map = activity_definition_map
|
88
|
+
@logger = options.logger if options
|
89
|
+
@logger ||= Utilities::LogFactory.make_logger(self, "debug")
|
90
|
+
max_workers = options.execution_workers if options
|
91
|
+
max_workers = 20 if (max_workers.nil? || max_workers.zero?)
|
92
|
+
@executor = ForkingExecutor.new(:max_workers => max_workers, :logger => @logger)
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute(task)
|
97
|
+
activity_type = task.activity_type
|
98
|
+
begin
|
99
|
+
context = ActivityExecutionContext.new(@service, @domain, task)
|
100
|
+
activity_implementation = @activity_definition_map[activity_type]
|
101
|
+
raise "This activity worker was told to work on activity type #{activity_type.name}, but this activity worker only knows how to work on #{@activity_definition_map.keys.map(&:name).join' '}" unless activity_implementation
|
102
|
+
output = activity_implementation.execute(task.input, context)
|
103
|
+
@logger.debug "Responding on task_token #{task.task_token} for task #{task}"
|
104
|
+
if ! activity_implementation.execution_options.manual_completion
|
105
|
+
@service.respond_activity_task_completed(:task_token => task.task_token, :result => output)
|
106
|
+
end
|
107
|
+
rescue ActivityFailureException => e
|
108
|
+
respond_activity_task_failed_with_retry(task.task_token, e.message, e.details)
|
109
|
+
end
|
110
|
+
#TODO all the completion stuffs
|
111
|
+
end
|
112
|
+
|
113
|
+
def respond_activity_task_failed_with_retry(task_token, reason, details)
|
114
|
+
#TODO Set up this variable
|
115
|
+
if @failure_retrier.nil?
|
116
|
+
respond_activity_task_failed(task_token, reason, details)
|
117
|
+
#TODO Set up other stuff to do if we have it
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def respond_activity_task_canceled_with_retry(task_token, message)
|
122
|
+
if @failure_retrier.nil?
|
123
|
+
respond_activity_task_canceled(task_token, message)
|
124
|
+
end
|
125
|
+
#TODO Set up other stuff to do if we have it
|
126
|
+
end
|
127
|
+
|
128
|
+
def respond_activity_task_canceled(task_token, message)
|
129
|
+
@service.respond_activity_task_canceled({:task_token => task_token, :details => message})
|
130
|
+
end
|
131
|
+
|
132
|
+
def respond_activity_task_failed(task_token, reason, details)
|
133
|
+
@logger.debug "The task token to be reported on is #{task_token}"
|
134
|
+
@service.respond_activity_task_failed(:task_token => task_token, :reason => reason.to_s, :details => details.to_s)
|
135
|
+
end
|
136
|
+
|
137
|
+
def process_single_task(task)
|
138
|
+
@service = AWS::SimpleWorkflow.new.client.with_http_handler(AWS::Core::Http::NetHttpHandler.new(:ssl_ca_file => AWS.config.ssl_ca_file))
|
139
|
+
begin
|
140
|
+
begin
|
141
|
+
execute(task)
|
142
|
+
rescue CancellationException => e
|
143
|
+
respond_activity_task_canceled_with_retry(task.task_token, e.message)
|
144
|
+
rescue Exception => e
|
145
|
+
@logger.error "Got an error, #{e.message}, while executing #{task.activity_type.name}"
|
146
|
+
@logger.error "Full stack trace: #{e.backtrace}"
|
147
|
+
respond_activity_task_failed_with_retry(task.task_token, e.message, e.backtrace)
|
148
|
+
#Do rescue stuff
|
149
|
+
ensure
|
150
|
+
@poll_semaphore.release
|
151
|
+
end
|
152
|
+
rescue Exception => e
|
153
|
+
semaphore_needs_release = true
|
154
|
+
@logger.debug "Got into the other error mode"
|
155
|
+
raise e
|
156
|
+
ensure
|
157
|
+
@poll_semaphore.release if semaphore_needs_release
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def poll_and_process_single_task(use_forking = true)
|
162
|
+
|
163
|
+
@poll_semaphore ||= SuspendableSemaphore.new
|
164
|
+
@poll_semaphore.acquire
|
165
|
+
semaphore_needs_release = true
|
166
|
+
@logger.debug "before the poll\n\n"
|
167
|
+
begin
|
168
|
+
task = @domain.activity_tasks.poll_for_single_task(@task_list)
|
169
|
+
@logger.error "got a task, #{task.activity_type.name}"
|
170
|
+
@logger.error "The task token I got was: #{task.task_token}"
|
171
|
+
rescue Exception => e
|
172
|
+
@logger.debug "I have not been able to poll successfully, and am now bailing out, with error #{e}"
|
173
|
+
@poll_semaphore.release
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
if task.nil?
|
177
|
+
"Still polling at #{Time.now}, but didn't get anything"
|
178
|
+
@logger.debug "Still polling at #{Time.now}, but didn't get anything"
|
179
|
+
@poll_semaphore.release
|
180
|
+
return false
|
181
|
+
end
|
182
|
+
semaphore_needs_release = false
|
183
|
+
if use_forking
|
184
|
+
@executor.execute { process_single_task(task) }
|
185
|
+
else
|
186
|
+
process_single_task(task)
|
187
|
+
end
|
188
|
+
# process_single_task(task)
|
189
|
+
@logger.debug "finished executing the task"
|
190
|
+
return true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class SuspendableSemaphore
|
195
|
+
|
196
|
+
def initialize
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
def acquire
|
201
|
+
end
|
202
|
+
|
203
|
+
def release
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|