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,22 @@
|
|
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
|
+
#!/usr/bin/ruby -wKU
|
17
|
+
|
18
|
+
def require_all(path)
|
19
|
+
Dir[File.join(File.dirname(__FILE__), path, "*.rb")].each {|f| require f}
|
20
|
+
end
|
21
|
+
|
22
|
+
require_all('rubyflow/')
|
@@ -0,0 +1,63 @@
|
|
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
|
+
describe SimpleDFA do
|
17
|
+
before(:each) do
|
18
|
+
class Tester
|
19
|
+
attr_accessor :trace
|
20
|
+
|
21
|
+
extend SimpleDFA
|
22
|
+
init(:start_state)
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@trace = []
|
26
|
+
end
|
27
|
+
|
28
|
+
add_transition(:start_state, :a) do |t|
|
29
|
+
t.trace << :start_to_second
|
30
|
+
t.current_state = :second_state
|
31
|
+
end
|
32
|
+
add_transition(:second_state, :b) do |t|
|
33
|
+
t.trace << :second_to_third
|
34
|
+
t.current_state = :third_state
|
35
|
+
end
|
36
|
+
add_transition(:third_state, :c) do |t|
|
37
|
+
t.trace << :third_to_start
|
38
|
+
t.current_state = :start_state
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@this_dfa = Tester.new
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
it "ensures that consume works as expected" do
|
46
|
+
@this_dfa.consume(:a)
|
47
|
+
@this_dfa.trace.should == [:start_to_second]
|
48
|
+
@this_dfa.current_state.should == :second_state
|
49
|
+
end
|
50
|
+
|
51
|
+
it "ensures that define_general defines general transitions for a state" do
|
52
|
+
class Tester
|
53
|
+
define_general(:start_state) {|t| t.current_state = :new_state }
|
54
|
+
end
|
55
|
+
@this_dfa.consume(:c)
|
56
|
+
@this_dfa.current_state.should == :new_state
|
57
|
+
end
|
58
|
+
|
59
|
+
it "ensures that uncovered_transitions raises on those transitions" do
|
60
|
+
expect { @this_dfa.consume(:b) }.to raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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 'rubygems'
|
17
|
+
|
18
|
+
require 'aws/flow'
|
19
|
+
|
20
|
+
include AWS::Flow::Core
|
21
|
+
|
22
|
+
Spec::DSL::Main.class_eval do
|
23
|
+
if method_defined? :context
|
24
|
+
undef :context
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def constantize(camel_case_word)
|
29
|
+
names = camel_case_word.split('::')
|
30
|
+
names.shift if names.empty? || names.first.empty?
|
31
|
+
constant = Object
|
32
|
+
names.each do |name|
|
33
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
34
|
+
end
|
35
|
+
constant
|
36
|
+
end
|
data/aws-flow.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'aws-flow'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.date = Time.now
|
5
|
+
s.summary = "AWS Flow Decider package decider"
|
6
|
+
s.description = "Library to provide the AWS Flow Framework for Ruby"
|
7
|
+
s.authors = "Michael Steger"
|
8
|
+
s.email = ''
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.require_paths << "lib/aws/"
|
11
|
+
s.add_dependency "aws-sdk", "~> 1"
|
12
|
+
s.add_dependency "aws-flow-core", "~> 1"
|
13
|
+
end
|
data/lib/aws/decider.rb
ADDED
@@ -0,0 +1,67 @@
|
|
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
|
+
# @!visibility private
|
17
|
+
def require_all(path)
|
18
|
+
glob = File.join(path, "*.rb")
|
19
|
+
Dir[glob].each { |f| require f}
|
20
|
+
Dir[glob].map { |f| File.basename(f) }
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'aws/flow'
|
24
|
+
include AWS::Flow::Core
|
25
|
+
|
26
|
+
require 'aws-sdk'
|
27
|
+
# Setting the user-agent as ruby-flow for all calls to the service
|
28
|
+
AWS.config(:user_agent_prefix => "ruby-flow")
|
29
|
+
|
30
|
+
#LOAD_PATH << File.dirname(File.expand_path(__FILE__))
|
31
|
+
|
32
|
+
|
33
|
+
require "aws/decider/utilities"
|
34
|
+
require "aws/decider/worker"
|
35
|
+
require 'aws/decider/generic_client'
|
36
|
+
require "aws/decider/async_retrying_executor"
|
37
|
+
require "aws/decider/activity_definition"
|
38
|
+
require "aws/decider/decider"
|
39
|
+
require "aws/decider/task_handler"
|
40
|
+
require "aws/decider/data_converter"
|
41
|
+
require "aws/decider/state_machines"
|
42
|
+
require "aws/decider/workflow_definition"
|
43
|
+
|
44
|
+
require "aws/decider/executor"
|
45
|
+
require "aws/decider/workflow_enabled"
|
46
|
+
require "aws/decider/options"
|
47
|
+
require "aws/decider/activity"
|
48
|
+
require "aws/decider/async_decider"
|
49
|
+
require "aws/decider/workflow_clock"
|
50
|
+
require "aws/decider/decision_context"
|
51
|
+
require "aws/decider/workflow_definition_factory"
|
52
|
+
require "aws/decider/workflow_client"
|
53
|
+
require "aws/decider/history_helper"
|
54
|
+
require "aws/decider/exceptions"
|
55
|
+
require "aws/decider/task_poller"
|
56
|
+
require "aws/decider/flow_defaults"
|
57
|
+
require "aws/decider/implementation"
|
58
|
+
|
59
|
+
# @!visibility private
|
60
|
+
def get_const(name)
|
61
|
+
name = name.split('::').reverse
|
62
|
+
current = Object
|
63
|
+
while ! name.empty?
|
64
|
+
current = current.const_get(name.pop)
|
65
|
+
end
|
66
|
+
current
|
67
|
+
end
|
@@ -0,0 +1,408 @@
|
|
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
|
+
# Represents information about an open request.
|
20
|
+
# @!visibility private
|
21
|
+
class OpenRequestInfo
|
22
|
+
attr_accessor :completion_handle, :result, :blocking_promise, :description, :run_id
|
23
|
+
end
|
24
|
+
|
25
|
+
class ActivityMetadata
|
26
|
+
attr_reader :activity_id
|
27
|
+
def initialize(activity_id); @activity_id = activity_id; end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# A generic activity client that can be used to perform standard activity actions.
|
32
|
+
class GenericActivityClient < GenericClient
|
33
|
+
# The data converter used for serializing/deserializing data when sending requests to and receiving results from
|
34
|
+
# workflow executions of this workflow type. By default, this is {YAMLDataConverter}.
|
35
|
+
attr_accessor :data_converter
|
36
|
+
|
37
|
+
# The decision helper used by the activity client.
|
38
|
+
attr_accessor :decision_helper
|
39
|
+
|
40
|
+
# A hash of ActivityRuntimeOptions for the activity client
|
41
|
+
attr_accessor :options
|
42
|
+
|
43
|
+
# Returns the default option class for the activity client, which is {ActivityRuntimeOptions}.
|
44
|
+
def self.default_option_class; ActivityRuntimeOptions; end
|
45
|
+
|
46
|
+
# Separates the activity name from the activity type at the point of the last (.) symbol.
|
47
|
+
#
|
48
|
+
# @param [String] name
|
49
|
+
# The name of the activity type.
|
50
|
+
#
|
51
|
+
def activity_name_from_activity_type(name)
|
52
|
+
return name.to_s.split(".").last.to_sym
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a new GenericActivityClient instance.
|
56
|
+
#
|
57
|
+
# @param [DecisionHelper] decision_helper
|
58
|
+
# The decision helper to use for the activity client.
|
59
|
+
#
|
60
|
+
# @param [ActivityOptions] options ActivityOptions to set for the activity client.
|
61
|
+
#
|
62
|
+
def initialize(decision_helper, options)
|
63
|
+
@decision_helper = decision_helper
|
64
|
+
@options = options
|
65
|
+
@activity_option_map = @decision_helper.activity_options
|
66
|
+
@failure_map = {}
|
67
|
+
@data_converter ||= YAMLDataConverter.new
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
# Runs an activity given a name and block of options.
|
72
|
+
#
|
73
|
+
# @param method_name
|
74
|
+
# The name of the activity type to define
|
75
|
+
#
|
76
|
+
# @param args
|
77
|
+
# Arguments for the method
|
78
|
+
#
|
79
|
+
# @param block
|
80
|
+
# A block of {ActivityOptions}
|
81
|
+
#
|
82
|
+
def method_missing(method_name, *args, &block)
|
83
|
+
options = Utilities::interpret_block_for_options(ActivityOptions, block)
|
84
|
+
options = Utilities::merge_all_options(@options,
|
85
|
+
@activity_option_map[method_name.to_sym],
|
86
|
+
@option_map[method_name.to_sym],
|
87
|
+
options
|
88
|
+
)
|
89
|
+
new_options = ActivityOptions.new(options)
|
90
|
+
|
91
|
+
activity_type = ActivityType.new("#{new_options.prefix_name}.#{method_name.to_s}", new_options.version, new_options.get_default_options)
|
92
|
+
if new_options._exponential_retry
|
93
|
+
retry_function = new_options._exponential_retry.retry_function || FlowConstants.exponential_retry_function
|
94
|
+
new_options._exponential_retry.return_on_start ||= new_options.return_on_start
|
95
|
+
future = _retry_with_options(lambda { self.schedule_activity(activity_type.name, activity_type, args, new_options ) }, retry_function, new_options._exponential_retry, args)
|
96
|
+
return future if new_options.return_on_start
|
97
|
+
result = Utilities::drill_on_future(future)
|
98
|
+
else
|
99
|
+
result = schedule_activity(activity_type.name, activity_type, args, new_options)
|
100
|
+
end
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
# @!visibility private
|
105
|
+
def retry_alias_to_method(retry_alias)
|
106
|
+
retry_alias.to_s[/__(.*)_retry/, 1].to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
# @!visibility private
|
110
|
+
def method_to_retry_alias(method_name)
|
111
|
+
"#{__method_name.to_s + "_retry"}".to_sym
|
112
|
+
end
|
113
|
+
|
114
|
+
# Requests that the activity is canceled.
|
115
|
+
#
|
116
|
+
# @param [Future] to_cancel
|
117
|
+
# The Future for the task to be canceled.
|
118
|
+
#
|
119
|
+
def request_cancel_activity_task(to_cancel)
|
120
|
+
metadata = to_cancel.metadata
|
121
|
+
if ! metadata.respond_to? :activity_id
|
122
|
+
raise "You need to use a future obtained from an activity"
|
123
|
+
end
|
124
|
+
@decision_helper[metadata.activity_id].consume(:cancel)
|
125
|
+
end
|
126
|
+
|
127
|
+
# A handler for the ActivityClassCanceled event.
|
128
|
+
#
|
129
|
+
# @param [ActivityClassCanceled] event
|
130
|
+
# The event data.
|
131
|
+
#
|
132
|
+
def handle_activity_task_canceled(event)
|
133
|
+
activity_id = @decision_helper.get_activity_id(event.attributes[:scheduled_event_id])
|
134
|
+
@decision_helper[activity_id].consume(:handle_cancellation_event)
|
135
|
+
if @decision_helper[activity_id].done?
|
136
|
+
open_request = @decision_helper.scheduled_activities.delete(activity_id)
|
137
|
+
exception = CancellationException.new("Cancelled from ActivityTaskCanceledEvent", nil)
|
138
|
+
if ! open_request.nil?
|
139
|
+
open_request.completion_handle.fail(exception)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# A handler for the ActivityClassTimedOut event.
|
145
|
+
#
|
146
|
+
# @param [ActivityClassTimedOut] event
|
147
|
+
# The event data.
|
148
|
+
#
|
149
|
+
def handle_activity_task_timed_out(event)
|
150
|
+
activity_id = @decision_helper.get_activity_id(event.attributes[:scheduled_event_id])
|
151
|
+
activity_state_machine = @decision_helper[activity_id]
|
152
|
+
activity_state_machine.consume(:handle_completion_event)
|
153
|
+
if activity_state_machine.done?
|
154
|
+
open_request = @decision_helper.scheduled_activities.delete(activity_id)
|
155
|
+
if ! open_request.nil?
|
156
|
+
timeout_type = event.attributes[:timeout_type]
|
157
|
+
failure = ActivityTaskTimedOutException.new(event.id, activity_id, timeout_type, "Time out")
|
158
|
+
open_request.completion_handle.fail(failure)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# A handler for the {ActivityTaskFailed} event.
|
164
|
+
#
|
165
|
+
# @param [ActivityClassFailed] event
|
166
|
+
# The event data.
|
167
|
+
#
|
168
|
+
def handle_activity_task_failed(event)
|
169
|
+
attributes = event.attributes
|
170
|
+
activity_id = @decision_helper.get_activity_id(attributes[:scheduled_event_id])
|
171
|
+
@decision_helper[activity_id].consume(:handle_completion_event)
|
172
|
+
open_request_info = @decision_helper.scheduled_activities.delete(activity_id)
|
173
|
+
reason = attributes[:reason]
|
174
|
+
details = attributes[:details]
|
175
|
+
# TODO consider adding user_context to open request, and adding it here
|
176
|
+
# @decision_helper[@decision_helper.activity_scheduling_event_id_to_activity_id[event.attributes.scheduled_event_id]].attributes[:options].data_converter
|
177
|
+
failure = ActivityTaskFailedException.new(event.id, activity_id, reason, details)
|
178
|
+
open_request_info.completion_handle.fail(failure)
|
179
|
+
end
|
180
|
+
|
181
|
+
# A handler for the {ScheduleActivityTaskFailed} event.
|
182
|
+
#
|
183
|
+
# @param [ScheduleActivityTaskFailed] event
|
184
|
+
# The event data.
|
185
|
+
#
|
186
|
+
def handle_schedule_activity_task_failed(event)
|
187
|
+
attributes = event.attributes
|
188
|
+
activity_id = attributes[:activity_id]
|
189
|
+
open_request_info = @decision_helper.scheduled_activities.delete(activity_id)
|
190
|
+
activity_state_machine = @decision_helper[activity_id]
|
191
|
+
activity_state_machine.consume(:handle_initiation_failed_event)
|
192
|
+
if activity_state_machine.done?
|
193
|
+
# TODO Fail task correctly
|
194
|
+
failure = ScheduleActivityTaskFailedException.new(event.id, event.attributes.activity_type, activity_id, event.attributes.cause)
|
195
|
+
open_request_info.completion_handle.fail(failure)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# A handler for the {ActivityClassCompleted} event.
|
200
|
+
#
|
201
|
+
# @param [ActivityClassCompleted] event
|
202
|
+
# The event data.
|
203
|
+
#
|
204
|
+
def handle_activity_task_completed(event)
|
205
|
+
scheduled_id = event.attributes[:scheduled_event_id]
|
206
|
+
activity_id = @decision_helper.activity_scheduling_event_id_to_activity_id[scheduled_id]
|
207
|
+
@decision_helper[activity_id].consume(:handle_completion_event)
|
208
|
+
if @decision_helper[activity_id].done?
|
209
|
+
open_request = @decision_helper.scheduled_activities.delete(activity_id)
|
210
|
+
open_request.result = event.attributes[:result]
|
211
|
+
open_request.completion_handle.complete
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Schedules the named activity.
|
216
|
+
#
|
217
|
+
# @param [String] name
|
218
|
+
# The name of the activity to schedule.
|
219
|
+
#
|
220
|
+
# @param [String]
|
221
|
+
# activity_type The activity type for this scheduled activity.
|
222
|
+
#
|
223
|
+
# @param [Object]
|
224
|
+
# input Optional data passed to the activity.
|
225
|
+
#
|
226
|
+
# @param [ActivityOptions]
|
227
|
+
# options ActivityOptions to set for the scheduled activity.
|
228
|
+
#
|
229
|
+
def schedule_activity(name, activity_type, input, options)
|
230
|
+
options = Utilities::merge_all_options(@option_map[activity_name_from_activity_type(name)], options)
|
231
|
+
new_options = ActivityOptions.new(options)
|
232
|
+
output = Utilities::AddressableFuture.new
|
233
|
+
open_request = OpenRequestInfo.new
|
234
|
+
decision_id = @decision_helper.get_next_id(:Activity)
|
235
|
+
output._metadata = ActivityMetadata.new(decision_id)
|
236
|
+
error_handler do |t|
|
237
|
+
t.begin do
|
238
|
+
@data_converter = new_options.data_converter
|
239
|
+
|
240
|
+
#input = input.map { |input_part| @data_converter.dump input_part } unless input.nil?
|
241
|
+
input = @data_converter.dump input unless input.empty?
|
242
|
+
attributes = {}
|
243
|
+
new_options.input ||= input unless input.empty?
|
244
|
+
attributes[:options] = new_options
|
245
|
+
attributes[:activity_type] = activity_type
|
246
|
+
attributes[:decision_id] = decision_id
|
247
|
+
@completion_handle = nil
|
248
|
+
external_task do |t|
|
249
|
+
t.initiate_task do |handle|
|
250
|
+
open_request.completion_handle = handle
|
251
|
+
|
252
|
+
@decision_helper.scheduled_activities[decision_id.to_s] = open_request
|
253
|
+
@decision_helper[decision_id.to_s] = ActivityDecisionStateMachine.new(decision_id, attributes)
|
254
|
+
end
|
255
|
+
t.cancellation_handler do |this_handle, cause|
|
256
|
+
state_machine = @decision_helper[decision_id.to_s]
|
257
|
+
if state_machine.current_state == :created
|
258
|
+
open_request = @decision_helper.scheduled_activities.delete(decision_id.to_s)
|
259
|
+
open_request.completion_handle.complete
|
260
|
+
end
|
261
|
+
state_machine.consume(:cancel)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
t.rescue(Exception) do |error|
|
266
|
+
@data_converter = new_options.data_converter
|
267
|
+
# If we have an ActivityTaskFailedException, then we should figure
|
268
|
+
# out what the cause was, and pull that out. If it's anything else,
|
269
|
+
# we should serialize the error, and stuff that into details, so
|
270
|
+
# that things above us can pull it out correctly. We don't have to
|
271
|
+
# do this for ActivityTaskFailedException, as the details is
|
272
|
+
# *already* serialized
|
273
|
+
if error.is_a? ActivityTaskFailedException
|
274
|
+
details = @data_converter.load(error.details)
|
275
|
+
error.cause = details
|
276
|
+
else
|
277
|
+
details = @data_converter.dump(error)
|
278
|
+
error.details = details
|
279
|
+
end
|
280
|
+
@failure_map[decision_id.to_s] = error
|
281
|
+
end
|
282
|
+
t.ensure do
|
283
|
+
@data_converter = new_options.data_converter
|
284
|
+
result = @data_converter.load open_request.result
|
285
|
+
output.set(result)
|
286
|
+
raise @failure_map[decision_id.to_s] if @failure_map[decision_id.to_s] && new_options.return_on_start
|
287
|
+
end
|
288
|
+
end
|
289
|
+
return output if new_options.return_on_start
|
290
|
+
output.get
|
291
|
+
this_failure = @failure_map[decision_id.to_s]
|
292
|
+
raise this_failure if this_failure
|
293
|
+
return output.get
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Represents an activity client.
|
298
|
+
class ActivityClient < Module
|
299
|
+
# Gets the data converter for the Activity Client.
|
300
|
+
def data_converter
|
301
|
+
@generic_client.data_converter
|
302
|
+
end
|
303
|
+
|
304
|
+
# Sets the data converter for the Activity Client.
|
305
|
+
def data_converter=(other)
|
306
|
+
@generic_client.data_converter = other
|
307
|
+
end
|
308
|
+
|
309
|
+
# Exponentially retries the supplied method with optional settings.
|
310
|
+
#
|
311
|
+
# @param [String] method_name The method name to retry.
|
312
|
+
#
|
313
|
+
# @param [ExponentialRetryOptions] block A hash of ExponentialRetryOptions to use.
|
314
|
+
#
|
315
|
+
def exponential_retry(method_name, &block)
|
316
|
+
@generic_client.retry(method_name, lambda {|first, time_of_failure, attempts| 1}, block)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Methods and constants related to activities.
|
321
|
+
#
|
322
|
+
# @!attribute activity_client
|
323
|
+
# Gets the [ActivityClient] for this Activity.
|
324
|
+
#
|
325
|
+
# @!attribute activities
|
326
|
+
# Gets an list of activities for this Activity.
|
327
|
+
#
|
328
|
+
module Activities
|
329
|
+
@precursors ||= []
|
330
|
+
attr_accessor :activity_client, :activities
|
331
|
+
def self.extended(base)
|
332
|
+
base.send :include, InstanceMethods
|
333
|
+
end
|
334
|
+
module InstanceMethods
|
335
|
+
attr_writer :_activity_execution_context
|
336
|
+
def activity_execution_context
|
337
|
+
raise IllegalStateException.new("No activity execution context") unless @_activity_execution_context
|
338
|
+
@_activity_execution_context
|
339
|
+
end
|
340
|
+
def record_activity_heartbeat(details)
|
341
|
+
@_activity_execution_context.record_activity_heartbeat(details)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
# @!visibility private
|
347
|
+
extend Utilities::UpwardLookups
|
348
|
+
|
349
|
+
# @!visibility private
|
350
|
+
def look_upwards(variable)
|
351
|
+
precursors = self.ancestors.dup
|
352
|
+
precursors.delete(self)
|
353
|
+
results = precursors.map { |x| x.send(variable) if x.methods.map(&:to_sym).include? variable }.compact.flatten.uniq
|
354
|
+
end
|
355
|
+
property(:activities, [])
|
356
|
+
|
357
|
+
# @!visibility private
|
358
|
+
def _options; @activities.map(&:options); end
|
359
|
+
|
360
|
+
# Defines one or more activities, with {ActivityOptions} provided in the supplied block.
|
361
|
+
#
|
362
|
+
# @param [Array] activity_names
|
363
|
+
# The names of the activities to define.
|
364
|
+
#
|
365
|
+
# @param [Hash] block
|
366
|
+
# {ActivityOptions} to use on the defined activities.
|
367
|
+
#
|
368
|
+
# @example Defining an activity
|
369
|
+
# new_activity_class = Class.new(MyActivity) do
|
370
|
+
# extend Activities
|
371
|
+
# activity :run_activity1 do
|
372
|
+
# {
|
373
|
+
# :default_task_heartbeat_timeout => "3600",
|
374
|
+
# :default_task_list => task_list,
|
375
|
+
# :default_task_schedule_to_close_timeout => "20",
|
376
|
+
# :default_task_schedule_to_start_timeout => "20",
|
377
|
+
# :default_task_start_to_close_timeout => "20",
|
378
|
+
# :version => "1",
|
379
|
+
# :prefix_name => "#{class_name}Activity"
|
380
|
+
# }
|
381
|
+
# end
|
382
|
+
# def run_activity1
|
383
|
+
# end
|
384
|
+
# end
|
385
|
+
def activity(*activity_names, &block)
|
386
|
+
options = Utilities::interpret_block_for_options(ActivityOptions, block)
|
387
|
+
activity_names.each do |activity_name|
|
388
|
+
prefix_name = options.prefix_name || self.to_s
|
389
|
+
activity_type = ActivityType.new(prefix_name + "." + activity_name.to_s, options.version, options)
|
390
|
+
@activities ||= []
|
391
|
+
@activities << activity_type
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# This module is for internal use only and may be changed or removed
|
397
|
+
# without prior notice. Use {Activities} instead.
|
398
|
+
# @!visibility private
|
399
|
+
module Activity
|
400
|
+
include Activities
|
401
|
+
def self.extended(base)
|
402
|
+
base.send :include, InstanceMethods
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
end
|
408
|
+
end
|