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.
Files changed (62) hide show
  1. data/Gemfile +8 -0
  2. data/LICENSE.TXT +15 -0
  3. data/NOTICE.TXT +14 -0
  4. data/Rakefile +39 -0
  5. data/aws-flow-core/Gemfile +9 -0
  6. data/aws-flow-core/LICENSE.TXT +15 -0
  7. data/aws-flow-core/NOTICE.TXT +14 -0
  8. data/aws-flow-core/Rakefile +27 -0
  9. data/aws-flow-core/aws-flow-core.gemspec +12 -0
  10. data/aws-flow-core/lib/aws/flow.rb +26 -0
  11. data/aws-flow-core/lib/aws/flow/async_backtrace.rb +134 -0
  12. data/aws-flow-core/lib/aws/flow/async_scope.rb +195 -0
  13. data/aws-flow-core/lib/aws/flow/begin_rescue_ensure.rb +386 -0
  14. data/aws-flow-core/lib/aws/flow/fiber.rb +77 -0
  15. data/aws-flow-core/lib/aws/flow/flow_utils.rb +50 -0
  16. data/aws-flow-core/lib/aws/flow/future.rb +109 -0
  17. data/aws-flow-core/lib/aws/flow/implementation.rb +151 -0
  18. data/aws-flow-core/lib/aws/flow/simple_dfa.rb +85 -0
  19. data/aws-flow-core/lib/aws/flow/tasks.rb +405 -0
  20. data/aws-flow-core/test/aws/async_backtrace_spec.rb +41 -0
  21. data/aws-flow-core/test/aws/async_scope_spec.rb +118 -0
  22. data/aws-flow-core/test/aws/begin_rescue_ensure_spec.rb +665 -0
  23. data/aws-flow-core/test/aws/external_task_spec.rb +197 -0
  24. data/aws-flow-core/test/aws/factories.rb +52 -0
  25. data/aws-flow-core/test/aws/fiber_condition_variable_spec.rb +163 -0
  26. data/aws-flow-core/test/aws/fiber_spec.rb +78 -0
  27. data/aws-flow-core/test/aws/flow_spec.rb +255 -0
  28. data/aws-flow-core/test/aws/future_spec.rb +210 -0
  29. data/aws-flow-core/test/aws/rubyflow.rb +22 -0
  30. data/aws-flow-core/test/aws/simple_dfa_spec.rb +63 -0
  31. data/aws-flow-core/test/aws/spec_helper.rb +36 -0
  32. data/aws-flow.gemspec +13 -0
  33. data/lib/aws/decider.rb +67 -0
  34. data/lib/aws/decider/activity.rb +408 -0
  35. data/lib/aws/decider/activity_definition.rb +111 -0
  36. data/lib/aws/decider/async_decider.rb +673 -0
  37. data/lib/aws/decider/async_retrying_executor.rb +153 -0
  38. data/lib/aws/decider/data_converter.rb +40 -0
  39. data/lib/aws/decider/decider.rb +511 -0
  40. data/lib/aws/decider/decision_context.rb +60 -0
  41. data/lib/aws/decider/exceptions.rb +178 -0
  42. data/lib/aws/decider/executor.rb +149 -0
  43. data/lib/aws/decider/flow_defaults.rb +70 -0
  44. data/lib/aws/decider/generic_client.rb +178 -0
  45. data/lib/aws/decider/history_helper.rb +173 -0
  46. data/lib/aws/decider/implementation.rb +82 -0
  47. data/lib/aws/decider/options.rb +607 -0
  48. data/lib/aws/decider/state_machines.rb +373 -0
  49. data/lib/aws/decider/task_handler.rb +76 -0
  50. data/lib/aws/decider/task_poller.rb +207 -0
  51. data/lib/aws/decider/utilities.rb +187 -0
  52. data/lib/aws/decider/worker.rb +324 -0
  53. data/lib/aws/decider/workflow_client.rb +374 -0
  54. data/lib/aws/decider/workflow_clock.rb +104 -0
  55. data/lib/aws/decider/workflow_definition.rb +101 -0
  56. data/lib/aws/decider/workflow_definition_factory.rb +53 -0
  57. data/lib/aws/decider/workflow_enabled.rb +26 -0
  58. data/test/aws/decider_spec.rb +1299 -0
  59. data/test/aws/factories.rb +45 -0
  60. data/test/aws/integration_spec.rb +3108 -0
  61. data/test/aws/spec_helper.rb +23 -0
  62. metadata +138 -0
@@ -0,0 +1,153 @@
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
+ class AsyncRetryingExecutor
20
+ def initialize(retrying_policy, clock, execution_id, return_on_start = false)
21
+ @retrying_policy = retrying_policy
22
+ @clock = clock
23
+ @return_on_start = return_on_start
24
+ @execution_id = execution_id
25
+ end
26
+ def execute(command, options = nil)
27
+ return schedule_with_retry(command, nil, Hash.new { |hash, key| hash[key] = 1 }, @clock.current_time, 0) if @return_on_start
28
+ task do
29
+ schedule_with_retry(command, nil, Hash.new { |hash, key| hash[key] = 1 }, @clock.current_time, 0)
30
+ end
31
+ end
32
+
33
+ def schedule_with_retry(command, failure, attempt, first_attempt_time, time_of_recorded_failure)
34
+ delay = -1
35
+ if attempt.values.reduce(0, :+) > 1
36
+ raise failure unless @retrying_policy.isRetryable(failure)
37
+ delay = @retrying_policy.next_retry_delay_seconds(first_attempt_time, time_of_recorded_failure, attempt, failure, @execution_id)
38
+ raise failure if delay < 0
39
+ end
40
+ if delay > 0
41
+ task do
42
+ @clock.create_timer(delay, lambda { invoke(command, attempt, first_attempt_time) })
43
+ end
44
+ else
45
+ invoke(command, attempt, first_attempt_time)
46
+ end
47
+ end
48
+
49
+ def invoke(command, attempt, first_attempt_time)
50
+ failure_to_retry = nil
51
+ should_retry = Future.new
52
+ return_value = Future.new
53
+ output = Utilities::AddressableFuture.new
54
+ error_handler do |t|
55
+ t.begin { return_value.set(command.call) }
56
+ t.rescue(Exception) do |error|
57
+ failure_to_retry = error
58
+ raise error if error.class <= CancellationException
59
+ end
60
+ t.ensure { should_retry.set(failure_to_retry) }
61
+ end
62
+ task do
63
+ failure = should_retry.get
64
+ if ! failure.nil?
65
+ attempt[failure.class] += 1
66
+ output.set(schedule_with_retry(command, failure, attempt, first_attempt_time, @clock.current_time - first_attempt_time))
67
+ else
68
+ output.set(return_value.get)
69
+ end
70
+ #to_return = return_value.set? ? return_value.get : nil
71
+ end
72
+ return output if @return_on_start
73
+ output.get
74
+ end
75
+
76
+ end
77
+
78
+ # Represents a policy for retrying failed tasks.
79
+ class RetryPolicy
80
+
81
+ # Creates a new RetryPolicy instance
82
+ #
83
+ # @param retry_function
84
+ # The method to be called for each retry attempt.
85
+ #
86
+ # @param options
87
+ # A set of {RetryOptions} to modify the retry behavior.
88
+ #
89
+ def initialize(retry_function, options)
90
+ @retry_function = retry_function
91
+ @exceptions_to_exclude = options.exceptions_to_exclude
92
+ @exceptions_to_include = options.exceptions_to_include
93
+ @max_attempts = options.maximum_attempts
94
+ @retries_per_exception = options.retries_per_exception
95
+ @should_jitter = options.should_jitter
96
+ @jitter_function = options.jitter_function
97
+ end
98
+
99
+ # @param failure
100
+ # The failure to test.
101
+ #
102
+ # @return [true, false]
103
+ # Returns `true` if the task can be retried for this failure.
104
+ #
105
+ def isRetryable(failure)
106
+ if failure.respond_to? :cause
107
+ failure_class = failure.cause.class
108
+ else
109
+ failure_class = failure.class
110
+ end
111
+
112
+ return true if @exceptions_to_exclude.empty? && @exceptions_to_include.empty?
113
+ raise "#{failure} appears in both exceptions_to_include and exceptions_to_exclude" if @exceptions_to_exclude.include?(failure_class) && @exceptions_to_include.include?(failure_class)
114
+ # In short, default to false
115
+ # the second part of the statement does an intersection of the 2 arrays to see if any of the ancestors of
116
+ # failure exists in @exceptions_to_include
117
+ return (!@exceptions_to_exclude.include?(failure_class) && !(@exceptions_to_include & failure_class.ancestors).empty?)
118
+
119
+ #return (!@exceptions_to_exclude.include?(failure) && @exceptions_to_include.include?(failure))
120
+ end
121
+
122
+ # Schedules a new retry attempt
123
+ #
124
+ # @param first_attempt
125
+ #
126
+ # @param time_of_recorded_failure
127
+ #
128
+ # @param attempt
129
+ #
130
+ # @param failure
131
+ #
132
+ def next_retry_delay_seconds(first_attempt, time_of_recorded_failure, attempt, failure = nil, execution_id)
133
+ if attempt.values.reduce(0, :+) < 2
134
+ raise "This is bad, you have less than 2 attempts. More precisely, #{attempt} attempts"
135
+ end
136
+ if @max_attempts && @max_attempts != "NONE"
137
+ return -1 if attempt.values.reduce(0, :+) > @max_attempts + 1
138
+ end
139
+ if failure && @retries_per_exception && @retries_per_exception.keys.include?(failure.class)
140
+ return -1 if attempt[failure.class] > @retries_per_exception[failure.class]
141
+ end
142
+ return -1 if failure == nil
143
+
144
+ # Check to see if we should jitter or not and pass in the jitter function to retry function accordingly.
145
+ retry_seconds = @retry_function.call(first_attempt, time_of_recorded_failure, attempt)
146
+ if @should_jitter
147
+ retry_seconds += @jitter_function.call(execution_id, retry_seconds/2)
148
+ end
149
+ return retry_seconds
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,40 @@
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
+ # We special case exception for now, as YAML doesn't propagate backtraces
19
+ # properly, and they are very handy for debugging
20
+ class YAMLDataConverter
21
+
22
+ def dump(object)
23
+ if object.is_a? Exception
24
+ return YAML.dump_stream(object, object.backtrace)
25
+ end
26
+ object.to_yaml
27
+ end
28
+ def load(source)
29
+ return nil if source.nil?
30
+ output = YAML.load source
31
+ if output.is_a? Exception
32
+ backtrace = YAML.load_stream(source).find {|x| ! x.is_a? Exception}
33
+ output.set_backtrace(backtrace.to_a)
34
+ end
35
+ output
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,511 @@
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
+ # A MethodPair groups a method name with an associated data converter.
20
+ class MethodPair
21
+ # The method name for the method pair.
22
+ attr_accessor :method_name
23
+
24
+ # The data converter for the method pair.
25
+ attr_accessor :data_converter
26
+
27
+ # Creates a new MethodPair instance.
28
+ #
29
+ # @param method_name
30
+ # The method name for the method pair.
31
+ #
32
+ # @param data_converter
33
+ # The data converter for the method pair.
34
+ #
35
+ def initialize(method_name, data_converter)
36
+ @method_name = method_name
37
+ @data_converter = data_converter
38
+ end
39
+ end
40
+
41
+ # A generic workflow client implementation.
42
+ class GenericWorkflowClient
43
+ include Utilities::SelfMethods
44
+ def completion_function(event, open_request)
45
+ open_request.result = event.attributes[:result]
46
+ open_request.completion_handle.complete
47
+ end
48
+
49
+
50
+ # The data converter for the generic workflow client.
51
+ attr_accessor :data_converter
52
+
53
+ # Creates a new generic workflow client.
54
+ #
55
+ # @param decision_helper
56
+ #
57
+ # @param workflow_context
58
+ #
59
+ def initialize(decision_helper, workflow_context)
60
+ @decision_helper = decision_helper
61
+ @workflow_context = workflow_context
62
+ end
63
+
64
+ # Handler for the ExternalWorkflowExecutionCancelRequested event.
65
+ #
66
+ # @param [Object] event The event instance.
67
+ #
68
+ def handle_external_workflow_execution_cancel_requested(event)
69
+ # NOOP
70
+ end
71
+
72
+ # Handler for the ChildWorkflowExecutionCanceled event.
73
+ #
74
+ # @param [Object] event The event instance.
75
+ #
76
+ def handle_child_workflow_execution_canceled(event)
77
+ handle_event(event,
78
+ {
79
+ :id_methods => [:workflow_execution, :workflow_id],
80
+ :consume_symbol => :handle_cancellation_event,
81
+ :decision_helper_scheduled => :scheduled_external_workflows,
82
+ :handle_open_request => lambda do |event, open_request|
83
+ cancellation_exception = CancellationException.new("Cancelled from a ChildWorkflowExecutionCancelled")
84
+ open_request.completion_handle.fail(cancellation_exception)
85
+ end
86
+ })
87
+ end
88
+
89
+
90
+ # Handler for the ChildWorkflowExecutionCompleted event.
91
+ #
92
+ # @param [Object] event The event instance.
93
+ #
94
+
95
+ def handle_child_workflow_execution_completed(event)
96
+ handle_event(event,
97
+ {:id_methods => [:workflow_execution, :workflow_id],
98
+ :consume_symbol => :handle_completion_event,
99
+ :decision_helper_scheduled => :scheduled_external_workflows,
100
+ :handle_open_request => method(:completion_function)
101
+ })
102
+ end
103
+
104
+ # Handler for the ChildWorkflowExecutionFailed event.
105
+ #
106
+ # @param [Object] event The event instance.
107
+ #
108
+ def handle_child_workflow_execution_failed(event)
109
+ handle_event(event,
110
+ {:id_methods => [:workflow_execution, :workflow_id],
111
+ :consume_symbol => :handle_completion_event,
112
+ :decision_helper_scheduled => :scheduled_external_workflows,
113
+ :handle_open_request => lambda do |event, open_request|
114
+ reason = event.attributes.reason
115
+ details = event.attributes.details
116
+ # workflow_id = @decision_helper.child_initiated_event_id_to_workflow_id[event.attributes.initiated_event_id]
117
+ # @decision_helper.scheduled_external_workflows[workflow_id]
118
+ failure = ChildWorkflowFailedException.new(event.id, event.attributes[:workflow_execution], event.attributes.workflow_type, reason, details )
119
+ open_request.completion_handle.fail(failure)
120
+ end
121
+ }
122
+ )
123
+ end
124
+
125
+
126
+ # Handler for the ChildWorkflowExecutionStarted event.
127
+ #
128
+ # @param [Object] event The event instance.
129
+ #
130
+ def handle_child_workflow_execution_started(event)
131
+ handle_event(event,
132
+ {:id_methods => [:workflow_execution, :workflow_id],
133
+ :consume_symbol => :handle_started_event,
134
+ :decision_helper_scheduled => :scheduled_external_workflows,
135
+ :handle_open_request => lambda do |event, open_request|
136
+ open_request.run_id.set(event.attributes.workflow_execution.run_id)
137
+ end
138
+ })
139
+ end
140
+
141
+ # Handler for the ChildWorkflowExecutionTerminated event.
142
+ #
143
+ # @param [Object] event The event instance.
144
+ #
145
+ def handle_child_workflow_execution_terminated(event)
146
+ handle_event(event,
147
+ {:id_methods => [:workflow_execution, :workflow_id],
148
+ :consume_symbol => :handle_completion_event,
149
+ :decision_helper_scheduled => :scheduled_external_workflows,
150
+ :handle_open_request => lambda do |event, open_request|
151
+ exception = ChildWorkflowTerminatedException.new(event.id, open_request.description, nil)
152
+ open_request.completion_handle.fail(exception)
153
+ end
154
+ })
155
+ end
156
+
157
+ # Handler for the ChildWorkflowExecutionTimedOut event.
158
+ #
159
+ # @param [Object] event The event instance.
160
+ #
161
+ def handle_child_workflow_execution_timed_out(event)
162
+ handle_event(event,
163
+ {:id_methods => [:workflow_execution, :workflow_id],
164
+ :consume_symbol => :handle_completion_event,
165
+ :decision_helper_scheduled => :scheduled_external_workflows,
166
+ :handle_open_request => lambda do |event, open_request|
167
+ exception = ChildWorkflowTimedOutException.new(event.id, open_request.description, nil)
168
+ open_request.completion_handle.fail(exception)
169
+ end
170
+ })
171
+ end
172
+
173
+ # Handler for the ExternalWorkflowExecutionSignaled event.
174
+ #
175
+ # @param [Object] event The event instance.
176
+ #
177
+ def handle_external_workflow_execution_signaled(event)
178
+ signal_id = @decision_helper.signal_initiated_event_to_signal_id[event.attributes[:initiated_event_id]]
179
+ state_machine = @decision_helper[signal_id]
180
+ state_machine.consume(:handle_completion_event)
181
+ if state_machine.done?
182
+ open_request = @decision_helper.scheduled_signals.delete(signal_id)
183
+ open_request.result = nil
184
+ open_request.completion_handle.complete
185
+ end
186
+ end
187
+
188
+ # Handler for the SignalExternalWorkflowExecutionFailed event.
189
+ #
190
+ # @param [Object] event The event instance.
191
+ #
192
+ def handle_signal_external_workflow_execution_failed(event)
193
+ handle_event(event, {
194
+ :id_methods => [:control],
195
+ :consume_symbol => :handle_completion_event,
196
+ :decision_helper_scheduled => :scheduled_signals,
197
+ :handle_open_request => lambda do |event, open_request|
198
+ workflow_execution = AWS::SimpleWorkflow::WorkflowExecution.new("",event.attributes.workflow_id, event.attributes.run_id)
199
+ failure = SignalExternalWorkflowException(event.id, workflow_execution, event.attributes.cause)
200
+ open_request.completion_handle.fail(failure)
201
+ end
202
+ })
203
+ end
204
+
205
+ # Handler for the StartExternalWorkflowExecutionFailed event.
206
+ #
207
+ # @param [Object] event The event instance.
208
+ #
209
+ def handle_start_child_workflow_execution_failed(event)
210
+ handle_event(event, {
211
+ :id_methods => [:workflow_id],
212
+ :consume_symbol => :handle_initiation_failed_event,
213
+ :decision_helper_scheduled => :scheduled_external_workflows,
214
+ :handle_open_request => lambda do |event, open_request|
215
+ workflow_execution = AWS::SimpleWorkflow::WorkflowExecution.new("",event.attributes.workflow_id, event.attributes.run_id)
216
+ workflow_type = event.attributes.workflow_type
217
+ cause = event.attributes.cause
218
+ failure = StartChildWorkflowFailedException.new(event.id, workflow_execution, workflow_type, cause)
219
+ open_request.completion_handle.fail(failure)
220
+ end
221
+ })
222
+ end
223
+ end
224
+
225
+
226
+ # Types and methods related to workflow execution. Extend this to implement a workflow decider.
227
+ #
228
+ # @!attribute version
229
+ # Sets or returns the Decider version.
230
+ #
231
+ # @!attribute options
232
+ # Sets or returns the {WorkflowOptions} for this decider.
233
+ #
234
+ module Workflows
235
+ attr_accessor :version, :options
236
+ extend Utilities::UpwardLookups
237
+ @precursors = []
238
+ def look_upwards(variable)
239
+ precursors = self.ancestors.dup
240
+ precursors.delete(self)
241
+ results = precursors.map { |x| x.send(variable) if x.methods.map(&:to_sym).include? variable }.compact.flatten.uniq
242
+ end
243
+ property(:workflows, [])
244
+ @workflows = []
245
+ def self.extended(base)
246
+ base.send :include, InstanceMethods
247
+ end
248
+
249
+ # This method is for internal use only and may be changed or removed
250
+ # without prior notice. Use {#workflows} instead. Set the entry point
251
+ # in the {#workflow} method when creating a new workflow.
252
+ # @!visibility private
253
+ def entry_point(input=nil)
254
+ if input
255
+ @entry_point = input
256
+ workflow_type = WorkflowType.new(self.to_s + "." + input.to_s, nil, WorkflowOptions.new(:execution_method => input))
257
+ self.workflows.each { |workflow| workflow.name = self.to_s + "." + input.to_s }
258
+ self.workflows.each do |workflow|
259
+ workflow.options = WorkflowOptions.new(:execution_method => input)
260
+ end
261
+ self.workflows = self.workflows << workflow_type
262
+ end
263
+ return @entry_point if @entry_point
264
+ raise "You must set an entry point on the workflow definition"
265
+ end
266
+
267
+ # This method is for internal use only and may be changed or removed
268
+ # without prior notice. Use {#workflows} instead.
269
+ # Set the version in the {WorkflowOptions} passed in to the {#workflow} method.
270
+ # @!visibility private
271
+ def version(arg = nil)
272
+ if arg
273
+ self.workflows.each { |workflow| workflow.version = arg }
274
+ self.workflows = self.workflows << WorkflowType.new(nil, arg, WorkflowOptions.new)
275
+ end
276
+ return @version
277
+ end
278
+
279
+ # Sets the activity client.
280
+ #
281
+ # @param name
282
+ # Sets the client name for the activity client.
283
+ #
284
+ # @param block
285
+ # A block of {ActivityOptions} for the activity client.
286
+ #
287
+ def activity_client(name, &block)
288
+ options = Utilities::interpret_block_for_options(ActivityOptions, block)
289
+ # TODO: Make sure this works for dynamic stuff
290
+ begin
291
+ activity_class = get_const(options.prefix_name)
292
+ rescue Exception => e
293
+ #pass
294
+ end
295
+ activity_options = {}
296
+ if activity_class
297
+ values = activity_class.activities.map{|x| [x.name.split(".").last.to_sym, x.options]}
298
+ activity_options = Hash[*values.flatten]
299
+ end
300
+ # define_method(name) do
301
+ # return @client if @client
302
+ # @client ||= activity_class.activity_client.new(@decision_helper, options)
303
+ # @client.decision_context = @decision_context
304
+ # @client
305
+ # end
306
+ # else
307
+ client_name = "@client_#{name}"
308
+
309
+ define_method(name) do
310
+ return instance_variable_get(client_name) if instance_variable_get(client_name)
311
+ @decision_context ||= Fiber.current[:decision_context]
312
+ @decision_helper ||= @decision_context.decision_helper
313
+ @decision_helper.activity_options = activity_options
314
+ instance_variable_set(client_name, GenericActivityClient.new(@decision_helper, options))
315
+ instance_variable_get(client_name)
316
+ end
317
+ instance_variable_get(client_name)
318
+ end
319
+
320
+
321
+ # @!visibility private
322
+ def _options; self.workflows.map(&:options); end
323
+
324
+ # Defines a new workflow
325
+ #
326
+ # @param entry_point
327
+ # The entry point (method) that starts the workflow.
328
+ #
329
+ # @param block
330
+ # A block of {WorkflowOptions} for the workflow.
331
+ #
332
+ def workflow(entry_point, &block)
333
+ options = Utilities::interpret_block_for_options(WorkflowOptionsWithDefaults, block)
334
+ options.execution_method = entry_point
335
+ workflow_name = options.prefix_name || self.to_s
336
+ workflow_type = WorkflowType.new(workflow_name.to_s + "." + entry_point.to_s, options.version, options)
337
+ self.workflows = self.workflows << workflow_type
338
+ end
339
+
340
+ # @return [MethodPair]
341
+ # A {MethodPair} object
342
+ #
343
+ def get_state_method(get_state_method = nil, options = {})
344
+ data_converter = options[:data_converter]
345
+ @get_state_method = MethodPair.new(get_state_method, data_converter) unless get_state_method.nil?
346
+ @get_state_method
347
+ end
348
+
349
+ # Defines a signal for the workflow.
350
+ #
351
+ # @param method_name
352
+ # The signal method for the workflow.
353
+ #
354
+ # @param [SignalWorkflowOptions] options
355
+ # The {SignalWorkflowOptions} for this signal.
356
+ #
357
+ def signal(method_name , options = {})
358
+ data_converter = options[:data_converter]
359
+ signal_name = options[:signal_name]
360
+ signal_name ||= method_name.to_s
361
+ data_converter ||= FlowConstants.default_data_converter
362
+ @signals ||= {}
363
+ @signals[signal_name] = MethodPair.new(method_name, data_converter)
364
+ @signals
365
+ end
366
+
367
+
368
+ # @return [Hash]
369
+ # A hash of string(SignalName) => MethodPair(method, signalConverter) objects
370
+ def signals
371
+ @signals
372
+ end
373
+
374
+
375
+ # Instance methods for {DecisionContext}
376
+ module InstanceMethods
377
+
378
+ # Returns the {DecisionContext} instance.
379
+ # @return [DecisionContext]
380
+ # The {DecisionContext} instance.
381
+ def decision_context
382
+ FlowFiber.current[:decision_context]
383
+ end
384
+
385
+
386
+ # Returns the workflow ID.
387
+ #
388
+ # @return
389
+ # The workflow ID
390
+ #
391
+ def workflow_id
392
+ self.decision_context.workflow_context.decision_task.workflow_execution.workflow_id
393
+ end
394
+
395
+ # Returns the decision helper for the decision context. This should be an instance of {DecisionHelper} or a
396
+ # class derived from it.
397
+ def run_id
398
+ self.decision_context.workflow_context.decision_task.workflow_execution.run_id
399
+ end
400
+
401
+ def decision_helper
402
+ FlowFiber.current[:decision_context].decision_helper
403
+ end
404
+
405
+
406
+ # Sets the activity client for this decision context.
407
+ #
408
+ # @param name
409
+ # The name of the activity client.
410
+ #
411
+ # @param block
412
+ # A block of {ActivityOptions} for the activity client.
413
+ #
414
+ def activity_client(name=nil, &block)
415
+ options = Utilities::interpret_block_for_options(ActivityOptions, block)
416
+ begin
417
+ activity_class = get_const(options.prefix_name)
418
+ rescue Exception => e
419
+ #pass
420
+ end
421
+ activity_options = {}
422
+ if activity_class
423
+ values = activity_class.activities.map{|x| [x.name.split(".").last.to_sym, x.options]}
424
+ activity_options = Hash[*values.flatten]
425
+ end
426
+ client = GenericActivityClient.new(self.decision_helper, options)
427
+ self.class.send(:define_method, name) { client } if ! name.nil?
428
+ client
429
+ end
430
+
431
+
432
+ # Creates a timer on the workflow that executes the supplied block after a specified delay.
433
+ #
434
+ # @param delay_seconds
435
+ # The number of seconds to delay before executing the block.
436
+ #
437
+ # @param block
438
+ # The block to execute when the timer expires.
439
+ #
440
+ def create_timer(delay_seconds, &block)
441
+ self.decision_context.workflow_clock.create_timer(delay_seconds, block)
442
+ end
443
+
444
+ # Creates an asynchronous timer on the workflow that executes the supplied block after a specified delay.
445
+ #
446
+ # @param (see #create_timer)
447
+ #
448
+ # @deprecated
449
+ # Use {#create_timer_async} instead.
450
+ #
451
+ # @!visibility private
452
+ def async_create_timer(delay_seconds, &block)
453
+ task { self.decision_context.workflow_clock.create_timer(delay_seconds, block) }
454
+ end
455
+
456
+
457
+ # Creates an asynchronous timer on the workflow that executes the supplied block after a specified delay.
458
+ #
459
+ # @param (see #create_timer)
460
+ #
461
+ def create_timer_async(delay_seconds, &block)
462
+ task { self.decision_context.workflow_clock.create_timer(delay_seconds, block) }
463
+ end
464
+
465
+
466
+ # Restarts the workflow as a new workflow execution.
467
+ #
468
+ # @param args
469
+ # Arguments for this workflow execution, in JSON format.
470
+ #
471
+ # @param [ContinueAsNewOptions] block
472
+ # The {ContinueAsNewOptions} for this workflow execution.
473
+ #
474
+ def continue_as_new(*args, &block)
475
+ continue_as_new_options = Utilities::interpret_block_for_options(ContinueAsNewOptions, block)
476
+ @data_converter ||= YAMLDataConverter.new
477
+ if ! args.empty?
478
+ input = @data_converter.dump args
479
+ continue_as_new_options.input = input
480
+ end
481
+ known_workflows = self.class.workflows
482
+ # If there is only one workflow, we can unambiguously say that we should use that one
483
+
484
+ if known_workflows.length == 1
485
+ continue_as_new_options.precursors << known_workflows.first.options
486
+ end
487
+ # If we can find a name that matches, use that one
488
+ if continue_as_new_options.execution_method
489
+ matching_option = self.class.workflows.map(&:options).find {|x| x.execution_method == continue_as_new_options.execution_method }
490
+ continue_as_new_options.precursors << matching_option unless matching_option.nil?
491
+ end
492
+ self.decision_context.workflow_context.continue_as_new_options = continue_as_new_options
493
+ end
494
+ end
495
+ end
496
+
497
+ # This method is for internal use only and may be changed or removed
498
+ # without prior notice.
499
+ # Use {Workflows} instead.
500
+ #
501
+ # @!visibility private
502
+ module Decider
503
+ include Workflows
504
+
505
+ def self.extended(base)
506
+ base.send :include, InstanceMethods
507
+ end
508
+ end
509
+
510
+ end
511
+ end