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