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,178 @@
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 generic activity client.
20
+ class GenericClient
21
+
22
+ # The option map for the client.
23
+ attr_accessor :option_map
24
+
25
+ # Creates a new generic client.
26
+ def initialize(*args)
27
+ @option_map = {}
28
+ end
29
+
30
+
31
+ # Sets a map of options for this client.
32
+ #
33
+ # @param opts
34
+ # The options to set.
35
+ #
36
+ def with_opts(opts = {})
37
+ klass = self.class.default_option_class
38
+ options = klass.new(opts)
39
+ modified_instance = self.dup
40
+ options = klass.new(Utilities::merge_all_options(modified_instance.options, options))
41
+ modified_instance.options = options
42
+ modified_instance
43
+ end
44
+
45
+ def reconfigure(*method_names, &block)
46
+ options = Utilities::interpret_block_for_options(self.class.default_option_class, block)
47
+ method_names.each { |method_name| @option_map[method_name.to_sym] = options }
48
+ end
49
+
50
+ # @!visibility private
51
+ def bail_if_external
52
+ raise "You cannot use this function outside of a workflow definition" if Utilities::is_external
53
+ end
54
+
55
+
56
+ # Starts an asynchronous execution of a task. This method returns immediately; it does not wait for the task to
57
+ # complete.
58
+ #
59
+ # @note Trying to use {#send_async} outside of a workflow will fail with an exception.
60
+ #
61
+ # @param task The task to execute.
62
+ #
63
+ # @param args A number of arguments used to start the task execution.
64
+ #
65
+ # @param block A code block with additional options to start the task execution.
66
+ #
67
+ # @example The following two calls are equivalent.
68
+ # foo.send_async :bar # plus args and block if appropriate
69
+ #
70
+ # task do
71
+ # foo.send :bar # plus args and block if appropriate
72
+ # end
73
+ #
74
+ def send_async(task, *args, &block)
75
+ bail_if_external
76
+ # If there is no block, just make a block for immediate return
77
+ if block.nil?
78
+ modified_options = Proc.new{ {:return_on_start => true } }
79
+ # If there is a block, and it doesn't take any arguments, it will evaluate to a hash. Add an option to the hash
80
+ elsif block.arity == 0
81
+ modified_options = Proc.new do
82
+ result = block.call
83
+ result[:return_on_start] = true
84
+ result
85
+ end
86
+ # Otherwise, it will expect an options object passed in, and will do things on that object. So make our new Proc do that, and add an option
87
+ else modified_options = Proc.new do |x|
88
+ result = block.call(x)
89
+ result.return_on_start = true
90
+ result
91
+ end
92
+ end
93
+ self.send(task, *args, &modified_options)
94
+ end
95
+
96
+
97
+ # Retries the given method using an exponential fallback function.
98
+ def exponential_retry(method_name, *args, &block)
99
+ future = self._retry(method_name, FlowConstants.exponential_retry_function, block, args)
100
+ Utilities::drill_on_future(future)
101
+ end
102
+
103
+
104
+
105
+ # Used by {#retry}
106
+ # @!visibility private
107
+ def _retry_with_options(lambda_to_execute, retry_function, retry_options, args = NoInput.new)
108
+ retry_policy = RetryPolicy.new(retry_function, retry_options)
109
+ output = Utilities::AddressableFuture.new
110
+ result = nil
111
+ failure = nil
112
+ error_handler do |t|
113
+ t.begin do
114
+ async_retrying_executor = AsyncRetryingExecutor.new(retry_policy, self.decision_context.workflow_clock, self.decision_context.workflow_context.decision_task.workflow_execution.run_id, retry_options.return_on_start)
115
+ result = async_retrying_executor.execute(lambda_to_execute)
116
+ end
117
+ t.rescue(Exception) do |error|
118
+ failure = error
119
+ end
120
+ t.ensure do
121
+ if failure.nil?
122
+ output.set(result)
123
+ else
124
+ output.set(nil)
125
+ end
126
+ end
127
+ end
128
+ return output if retry_options.return_on_start
129
+ output.get
130
+ raise failure unless failure.nil?
131
+ return output.get
132
+ end
133
+
134
+
135
+ # Retries the given method using an optional retry function and block of {RetryOptions}.
136
+ #
137
+ # @param (see #retry)
138
+ #
139
+ # @!visibility private
140
+ def _retry(method_name, retry_function, block, args = NoInput.new)
141
+ bail_if_external
142
+ retry_options = Utilities::interpret_block_for_options(RetryOptions, block)
143
+ _retry_with_options(lambda { self.send(method_name, *args) }, retry_function, retry_options)
144
+ end
145
+
146
+
147
+ # Retries the given method using an optional retry function and block of {RetryOptions}.
148
+ #
149
+ # @param method_name
150
+ # The activity to retry.
151
+ #
152
+ # @param retry_function
153
+ # The retry function to use
154
+ #
155
+ # @param args
156
+ # Arguments to send to the method named in the `method_name` parameter.
157
+ #
158
+ # @param block
159
+ # The {RetryOptions} to set.
160
+ #
161
+ def retry(method_name, retry_function, *args, &block)
162
+ if retry_function.is_a? Fixnum
163
+ retry_time = retry_function
164
+ retry_function = lambda {|first_attempt, time_of_failure, attempt| retry_time}
165
+ end
166
+ future = self._retry(method_name, retry_function, block, args)
167
+ Utilities::drill_on_future(future)
168
+ end
169
+
170
+
171
+ # @return The decision context for this client.
172
+ def decision_context
173
+ FlowFiber.current[:decision_context]
174
+ end
175
+
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,173 @@
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 'set'
17
+
18
+ module AWS
19
+ module Flow
20
+ class HistoryHelper
21
+ def initialize(decision_task_iterator)
22
+ # TODO make sure we page through correctly
23
+ @single_decision_events_iterator = SingleDecisionIterator.new(decision_task_iterator)
24
+ end
25
+
26
+ def get_single_decision_events
27
+ @current_decision_data = @single_decision_events_iterator.next
28
+ return @current_decision_data.decision_events
29
+ end
30
+
31
+ def get_replay_current_time_millis
32
+ raise IllegalStateException if @current_decision_data.nil?
33
+ @current_decision_data.replay_current_time_milliseconds
34
+ end
35
+
36
+ def get_last_non_replay_event_id
37
+ result = get_decision_task.previous_started_event_id
38
+ result ||= 0
39
+ end
40
+
41
+ def get_decision_task
42
+ @single_decision_events_iterator.get_decision_task
43
+ end
44
+ end
45
+
46
+ class EventsIterator
47
+ attr_accessor :events, :decision_task, :decision_tasks
48
+
49
+ def initialize(decision_tasks)
50
+ @decision_tasks = decision_tasks
51
+ if ! @decision_tasks.nil?
52
+ @decision_task = decision_tasks
53
+ @events = @decision_task.events.to_a
54
+ end
55
+ end
56
+ end
57
+
58
+ class SingleDecisionData
59
+ attr_reader :decision_events, :replay_current_time_milliseconds, :workflow_context_data
60
+ def initialize(decision_events, replay_current_time_milliseconds, workflow_context_data)
61
+ @decision_events = decision_events
62
+ @replay_current_time_milliseconds = replay_current_time_milliseconds
63
+ @workflow_context_data = workflow_context_data
64
+ end
65
+ end
66
+
67
+ class SingleDecisionIterator
68
+ class << self
69
+ attr_accessor :decision_events
70
+ end
71
+ @decision_events = Set.new([
72
+ :ActivityTaskCancelRequested,
73
+ :ActivityTaskScheduled,
74
+ :CancelTimerFailed,
75
+ :CancelWorkflowExecutionFailed,
76
+ :CompleteWorkflowExecutionFailed,
77
+ :ContinueAsNewWorkflowExecutionFailed,
78
+ :FailWorkflowExecutionFailed,
79
+ :MarkerRecorded,
80
+ :RequestCancelActivityTaskFailed,
81
+ :RequestCancelExternalWorkflowExecutionFailed,
82
+ :RequestCancelExternalWorkflowExecutionInitiated,
83
+ :ScheduleActivityTaskFailed,
84
+ :SignalExternalWorkflowExecutionFailed,
85
+ :SignalExternalWorkflowExecutionInitiated,
86
+ :StartChildWorkflowExecutionFailed,
87
+ :StartChildWorkflowExecutionInitiated,
88
+ :StartTimerFailed,
89
+ :TimerCanceled,
90
+ :TimerStarted,
91
+ :WorkflowExecutionCanceled,
92
+ :WorkflowExecutionCompleted,
93
+ :WorkflowExecutionContinuedAsNew,
94
+ :WorkflowExecutionFailed
95
+ ])
96
+
97
+ def is_decision_event?(event)
98
+ SingleDecisionIterator.decision_events.member? event
99
+ end
100
+
101
+ def get_decision_task
102
+ @events.decision_task
103
+ end
104
+
105
+ def initialize(decision_tasks)
106
+ @events = EventsIterator.new(decision_tasks)
107
+ fill_next
108
+ @current = @next
109
+ fill_next
110
+ end
111
+
112
+ def next
113
+ result = @current
114
+ @current = @next
115
+ fill_next
116
+ return result
117
+ end
118
+
119
+ def reorder_events(start_to_completion, completion_to_start, last_decision_index)
120
+ reordered = []
121
+ reordered.concat(completion_to_start.slice(0, last_decision_index + 1)) if last_decision_index >= 0
122
+ reordered.concat(start_to_completion)
123
+ if completion_to_start.length > last_decision_index + 1
124
+ reordered.concat(completion_to_start.slice((last_decision_index + 1)..-1))
125
+ end
126
+ return reordered.flatten
127
+ end
128
+
129
+ def fill_next
130
+ decision_task_timed_out = false
131
+ decision_start_to_completion_events, decision_completion_to_start_events = [], []
132
+ next_replay_current_time_milliseconds = -1
133
+ last_decision_index = -1
134
+ while @events.events.length > 0
135
+ event = @events.events.shift
136
+ event_type = event.event_type.to_sym
137
+ case event_type
138
+ when :DecisionTaskCompleted
139
+ #TODO get execution context
140
+ #TODO updateWorkflowContextDataAndComponentVersions
141
+ concurrent_to_decision = false
142
+ when :DecisionTaskStarted
143
+ next_replay_current_time_milliseconds = event.created_at
144
+ if decision_task_timed_out
145
+ @current.decision_events.concat(decision_start_to_completion_events)
146
+ decision_start_to_completion_events = []
147
+ decision_task_timed_out = false
148
+ else
149
+ break
150
+ end
151
+ when :DecisionTaskTimedOut
152
+ decision_task_timed_out = true
153
+ when :DecisionTaskScheduled
154
+ # pass
155
+ when :MarkerRecorded
156
+ # pass
157
+ else
158
+ if concurrent_to_decision
159
+ decision_start_to_completion_events << event
160
+ else
161
+ if is_decision_event? event_type
162
+ last_decision_index = decision_completion_to_start_events.length
163
+ end
164
+ decision_completion_to_start_events << event
165
+ end
166
+ end
167
+ end
168
+ next_events = reorder_events(decision_start_to_completion_events, decision_completion_to_start_events, last_decision_index)
169
+ @next = SingleDecisionData.new(next_events, next_replay_current_time_milliseconds, @workflow_context_data )
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,82 @@
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
+ # Creates a new {WorkflowClient} instance
20
+ #
21
+ # @param service
22
+ # A {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SimpleWorkflow.html SWF service} reference. This is usually
23
+ # created with:
24
+ #
25
+ # swf = AWS::SimpleWorkflow.new
26
+ #
27
+ # @param domain
28
+ # The SWF {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SimpleWorkflow/Domain.html Domain} to use for this
29
+ # workflow client. This is usually created on the service object, such as:
30
+ #
31
+ # domain = swf.domains.create('my-domain', 10)
32
+ #
33
+ # or retrieved from it (for existing domains):
34
+ #
35
+ # domain = swf.domains['my-domain']
36
+ #
37
+ # @param [Hash, StartWorkflowOptions] block
38
+ # A hash of options to start the workflow.
39
+ #
40
+ def workflow_client(service = nil, domain = nil, &block)
41
+ AWS::Flow.send(:workflow_client, service, domain, &block)
42
+ end
43
+
44
+ # Execute a block with retries within a workflow context.
45
+ #
46
+ # @param options
47
+ # The {RetryOptions} to use.
48
+ #
49
+ # @param block
50
+ # The block to execute.
51
+ #
52
+ def with_retry(options = {}, &block)
53
+ raise "with_retry can only be used inside a workflow context!" if Utilities::is_external
54
+ retry_options = RetryOptions.new(options)
55
+ retry_policy = RetryPolicy.new(retry_options.retry_function, retry_options)
56
+ async_retrying_executor = AsyncRetryingExecutor.new(retry_policy, self.decision_context.workflow_clock, retry_options.return_on_start)
57
+ future = async_retrying_executor.execute(lambda { block.call })
58
+ Utilities::drill_on_future(future) unless retry_options.return_on_start
59
+ end
60
+
61
+
62
+ # @!visibility private
63
+ def self.workflow_client(service = nil, domain = nil, &block)
64
+ options = Utilities::interpret_block_for_options(StartWorkflowOptions, block)
65
+ if ! Utilities::is_external
66
+ service = AWS::SimpleWorkflow.new
67
+ # So, we probably shouldn't be doing this, but we need to slightly
68
+ # redesign where this is available from
69
+ domain = FlowFiber.current[:decision_context].workflow_context.decision_task.workflow_execution.domain
70
+ else
71
+ if service.nil? || domain.nil?
72
+ raise "You must provide both a service and domain when using workflow client in an external setting"
73
+ end
74
+ end
75
+
76
+ workflow_class_name = options.from_class || options.workflow_name
77
+ workflow_class = get_const(workflow_class_name) rescue nil
78
+ WorkflowClient.new(service, domain, workflow_class, options)
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,607 @@
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
+ # The base class for option defaults in the AWS Flow Framework for Ruby.
20
+ class Defaults
21
+ def method_missing(method_name, *args, &block)
22
+ return nil
23
+ end
24
+ end
25
+
26
+ # The base class for all options classes in the AWS Flow Framework for Ruby.
27
+ class Options
28
+ extend Utilities::UpwardLookups
29
+ include Utilities::UpwardLookups::InstanceMethods
30
+ def method_missing(method_name, *args, &block)
31
+ return nil
32
+ end
33
+
34
+ # Sets the default classes on a child class (a class derived from {Options}).
35
+ def self.inherited(child)
36
+ child.precursors ||= []
37
+ default_classes = child.ancestors.map do |precursor|
38
+ precursor.default_classes if precursor.methods.map(&:to_sym).include? :default_classes
39
+ end.compact.flatten
40
+ child.instance_variable_set("@default_classes", default_classes)
41
+ end
42
+
43
+ class << self
44
+ # The set of default options. These are used when `use_defaults` is set to `true` on {#initialize}.
45
+ attr_accessor :default_classes
46
+ end
47
+
48
+ # Merges specified options with the set of options already held by the class, and returns the result.
49
+ #
50
+ # @return [Hash]
51
+ # The merged set of options, defined as a hash.
52
+ #
53
+ # @param [Options] options
54
+ # An {Options}-derived class containing a set of options to use if this instance has no options, or options to
55
+ # add to this one if this instance already has options.
56
+ #
57
+ # @param [Hash] extra_to_add
58
+ # A hash containing extra options to merge with the options held by the class and those provided in the
59
+ # +options+ parameter.
60
+ #
61
+ def get_options(options, extra_to_add = {})
62
+ options = self.class.held_properties.compact if options.empty?
63
+ set_options = options.select {|option| self.send(option) != nil && self.send(option) != "" }
64
+ option_values = set_options.map {|option| self.send(option) == Float::INFINITY ? "NONE" : self.send(option) }
65
+ result = Hash[set_options.zip(option_values)]
66
+ result.merge(extra_to_add)
67
+ end
68
+
69
+ # Creates a new {Options} instance.
70
+ #
71
+ # @param [Hash] hash
72
+ # A hash of values to use as the default options for this instance. The members of this hash are defined by
73
+ # classes derived from {Options}.
74
+ #
75
+ # @param [true, false] use_defaults
76
+ # In derived classes, this parameter is used to tell the constructor to use the set of default options as the
77
+ # runtime options. This has no effect in the base {Options} class.
78
+ #
79
+ def initialize(hash={}, use_defaults = false)
80
+ @precursors ||= []
81
+ hash.each_pair do |key, val|
82
+ if self.methods.map(&:to_sym).include? "#{key}=".to_sym
83
+ self.send("#{key}=", val)
84
+ end
85
+ end
86
+ end
87
+
88
+ property(:from_class)
89
+ property(:prefix_name, [lambda {|x| raise "You cannot have a . in your prefix name" if x.include? "."; x}, lambda(&:to_s)])
90
+ property(:return_on_start, [lambda {|x| x == true}])
91
+
92
+ end
93
+
94
+ class WorkerDefaults < Defaults
95
+ def use_forking; true; end
96
+ end
97
+
98
+ # Options for Activity and Workflow workers.
99
+ #
100
+ # @!attribute logger
101
+ # The logger to use for the worker.
102
+ #
103
+ # @!attribute poller_workers
104
+ # The logger to use for the worker.
105
+ #
106
+ # @!attribute execution_workers
107
+ # The logger to use for the worker.
108
+ #
109
+ class WorkerOptions < Options
110
+ property(:logger, [])
111
+ # At current, we only support one poller per worker
112
+ # property(:poller_workers, [lambda(&:to_i)])
113
+ property(:execution_workers, [lambda(&:to_i)])
114
+ property(:use_forking, [lambda {|x| x == true}] )
115
+ default_classes << WorkerDefaults.new
116
+ end
117
+
118
+ # Options for WorkflowClient#signal_workflow_execution.
119
+ #
120
+ # @!attribute control
121
+ # Optional data attached to the signal that can be used by the workflow execution.
122
+ #
123
+ # @!attribute domain
124
+ # *Required*. The name of the domain containing the workflow execution to signal.
125
+ #
126
+ # @!attribute input
127
+ # Data to attach to the WorkflowExecutionSignaled event in the target workflow execution's history.
128
+ #
129
+ # @!attribute run_id
130
+ # The runId of the workflow execution to signal.
131
+ #
132
+ # @!attribute signal_name
133
+ # *Required*. The name of the signal. This name must be meaningful to the target workflow.
134
+ #
135
+ # @!attribute workflow_id
136
+ # *Required*. The workflowId of the workflow execution to signal.
137
+ #
138
+ class SignalWorkflowOptions < Options
139
+ properties(:input, :signal_name, :run_id, :workflow_id, :control, :domain)
140
+
141
+ # Gets a hash containing the held options.
142
+ def get_full_options
143
+ result = {}
144
+ SignalWorkflowOptions.held_properties.each do |option|
145
+ result[option] = self.send(option) if self.send(option) && self.send(option) != ""
146
+ end
147
+ result
148
+ end
149
+ end
150
+
151
+ # Defaults for {RetryOptions}
152
+ class RetryDefaults < Defaults
153
+ # The default maximum number of attempts to make before the task is marked as failed.
154
+ def maximum_attempts; FlowConstants.exponential_retry_maximum_attempts; end
155
+ # The default retry function to use.
156
+ def retry_function; FlowConstants.exponential_retry_function; end
157
+ def exceptions_to_include; FlowConstants.exponential_retry_exceptions_to_include; end
158
+ def exceptions_to_exclude; FlowConstants.exponential_retry_exceptions_to_exclude; end
159
+ def should_jitter; FlowConstants.should_jitter; end
160
+ def jitter_function; FlowConstants.jitter_function; end
161
+ end
162
+
163
+ # Retry options used with {GenericClient#retry} and {ActivityClient#exponential_retry}
164
+ class RetryOptions < Options
165
+ property(:is_retryable_function, [])
166
+ property(:exceptions_to_allow, [])
167
+ property(:maximum_attempts, [lambda {|x| x == "NONE" ? "NONE" : x.to_i}])
168
+ property(:maximum_retry_interval_seconds, [lambda {|x| x == "NONE" ? "NONE" : x.to_i}])
169
+ property(:exceptions_to_retry, [])
170
+ property(:exceptions_to_exclude, [])
171
+ property(:exceptions_to_include, [])
172
+ property(:jitter_function, [])
173
+ property(:should_jitter, [lambda {|x| x == true}])
174
+ property(:retries_per_exception, [])
175
+ property(:retry_function, [])
176
+ default_classes << RetryDefaults.new
177
+
178
+ # Creates a new {RetryOptions} instance.
179
+ #
180
+ # @param [Hash] hash The set of default RetryOptions.
181
+ #
182
+ # @option hash :is_retryable_function
183
+ # The function used to test if the activity is retryable.
184
+ #
185
+ # @option hash :exceptions_to_allow [Integer]
186
+ # The number of exceptions to allow
187
+ #
188
+ # @option hash :maximum_attempts [Integer]
189
+ # The maximum number of attempts to make before the task is marked as failed.
190
+ #
191
+ # @option hash :maximum_retry_interval_seconds [Integer]
192
+ # The maximum retry interval, in seconds.
193
+ #
194
+ # @option hash :exceptions_to_retry [Array]
195
+ # The list of exceptions that will cause a retry attempt.
196
+ #
197
+ # @option hash :exceptions_to_exclude [Array]
198
+ # The list of exceptions to exclude from retry.
199
+ #
200
+ # @option hash :jitter_function
201
+ # The jitter function used to modify the actual retry time.
202
+ #
203
+ # @option hash :retries_per_exception [Integer]
204
+ # The number of retries to make per exception.
205
+ #
206
+ # @option hash :retry_function
207
+ # The retry function to use.
208
+ #
209
+ # @param [true, false] use_defaults
210
+ # If set to `true`, the default options specified will be used as the runtime options.
211
+ #
212
+ def initialize(hash={}, use_defaults=false)
213
+ super(hash, use_defaults)
214
+ end
215
+
216
+ # Tests whether or not this activity can be retried based on the `:exceptions_to_retry` and
217
+ # `:exceptions_to_exclude` options.
218
+ #
219
+ # @param [Object] failure
220
+ # The failure type to test.
221
+ #
222
+ # @return [true, false]
223
+ # Returns `true` if the activity can be retried; `false` otherwise.
224
+ #
225
+ def isRetryable(failure)
226
+ #TODO stuff about checking for a DecisionException, getting cause if so
227
+ is_retryable = false
228
+ is_retryable = @exceptions_to_retry.reject {|exception| failure.class <= exception}.empty?
229
+ if is_retryable
230
+ is_retryable = @exceptions_to_exclude.select{|exception| failure.class <= exception}.empty?
231
+ end
232
+ return is_retryable
233
+ end
234
+ end
235
+
236
+ # Exponential retry options for the {ActivityClient#exponential_retry} method.
237
+ class ExponentialRetryOptions < RetryOptions
238
+ # The backoff coefficient to use. This is a floating point value that is multiplied with the current retry
239
+ # interval after every retry attempt. The default value is 2.0, which means that each retry will take twice as
240
+ # long as the previous.
241
+ attr_accessor :backoff_coefficient
242
+
243
+ # The retry expiration interval, in seconds. This will be increased after every retry attempt by the factor
244
+ # provided in +backoff_coefficient+.
245
+ attr_accessor :retry_expiration_interval_seconds
246
+
247
+ def next_retry_delay_seconds(first_attmept, recorded_failure, attempts)
248
+ raise IllegalArgumentException "Attempt number is #{attempts}, when it needs to be greater than 1"
249
+ if @maximum_attempts
250
+ end
251
+ end
252
+ end
253
+
254
+ # Defaults for WorkflowOptions
255
+ class WorkflowDefaults < Defaults
256
+
257
+ # The default task start-to-close timeout duration.
258
+ def task_start_to_close_timeout; 30; end
259
+
260
+ # The default child workflow policy
261
+ def child_policy; :TERMINATE; end
262
+
263
+ # Returns a list of tags (currently an empty array).
264
+ def tag_list; []; end
265
+
266
+ def data_converter; FlowConstants.default_data_converter; end
267
+ end
268
+
269
+ # Options for workflows
270
+ #
271
+ # @!attribute child_policy
272
+ # The optional policy to use for the child workflow executions when a workflow execution of this type is
273
+ # terminated, by calling the TerminateWorkflowExecution action explicitly or due to an expired timeout. This can
274
+ # be overridden when starting a workflow execution using the StartWorkflowExecution action or the
275
+ # StartChildWorkflowExecution Decision. The supported child policies are:
276
+ #
277
+ # * *TERMINATE*: the child executions will be terminated.
278
+ # * *REQUEST_CANCEL*: a request to cancel will be attempted for each child execution by recording a
279
+ # WorkflowExecutionCancelRequested event in its history. It is up to the decider to take appropriate actions
280
+ # when it receives an execution history with this event.
281
+ # * *ABANDON*: no action will be taken. The child executions will continue to run.
282
+ #
283
+ # The default is TERMINATE.
284
+ #
285
+ # @!attribute execution_method
286
+ # TBD
287
+ #
288
+ # @!attribute execution_start_to_close_timeout
289
+ # The optional maximum duration, specified when registering the workflow type, for executions of this workflow
290
+ # type. This default can be overridden when starting a workflow execution using the StartWorkflowExecution action
291
+ # or the StartChildWorkflowExecution decision.
292
+ #
293
+ # The valid values are integers greater than or equal to 0. An integer value can be used to specify the duration
294
+ # in seconds while NONE can be used to specify unlimited duration.
295
+ #
296
+ # @!attribute input
297
+ # A string of up to 32768 characters, to be provided to the workflow execution.
298
+ #
299
+ # @!attribute tag_list
300
+ # The list of tags to associate with the child workflow execution. A maximum of five tags can be specified. You
301
+ # can list workflow executions with a specific tag by calling ListOpenWorkflowExecutions or
302
+ # ListClosedWorkflowExecutions and specifying a TagFilter.
303
+ #
304
+ # @!attribute task_list
305
+ # The optional task list, specified when registering the workflow type, for decisions tasks scheduled for workflow
306
+ # executions of this type. This default can be overridden when starting a workflow execution using the
307
+ # StartWorkflowExecution action or the StartChildWorkflowExecution Decision.
308
+ #
309
+ # @!attribute task_start_to_close_timeout
310
+ # The optional maximum duration, specified when registering the workflow type, that a decision task for executions
311
+ # of this workflow type might take before returning completion or failure. If the task does not close in the
312
+ # specified time then the task is automatically timed out and rescheduled. If the decider eventually reports a
313
+ # completion or failure, it is ignored. This default can be overridden when starting a workflow execution using
314
+ # the StartWorkflowExecution action or the StartChildWorkflowExecution Decision.
315
+ #
316
+ # The valid values are integers greater than or equal to 0. An integer value can be used to specify the duration
317
+ # in seconds while NONE can be used to specify unlimited duration.
318
+ #
319
+ # The default is 30.
320
+ #
321
+ # @!attribute version
322
+ # The version of the Workflow. If you update any of these options, you must update the version.
323
+ #
324
+ # @!attribute workflow_id
325
+ # *Required*. The workflow id of the workflow execution.
326
+ #
327
+ # The specified string must not start or end with whitespace. It must not contain a `:` (colon), `/` (slash), `|`
328
+ # (vertical bar), or any control characters (\u0000-\u001f | \u007f - \u009f). Also, it must not contain the
329
+ # literal string "arn".
330
+ #
331
+ class WorkflowOptions < Options
332
+ properties(:version, :input, :workflow_id, :execution_start_to_close_timeout, :task_start_to_close_timeout, :task_list, :execution_method)
333
+ property(:tag_list, [])
334
+ property(:child_policy, [lambda(&:to_s), lambda(&:upcase)])
335
+ property(:data_converter, [])
336
+ default_classes << WorkflowDefaults.new
337
+
338
+ # Returns a hash containing the runtime workflow options.
339
+ #
340
+ # @return [Hash] a hash of options with corresponding values.
341
+ #
342
+ def get_full_options
343
+ result = {}
344
+ usable_properties = self.class.held_properties
345
+ usable_properties.delete(:from_class)
346
+ usable_properties.each do |option|
347
+ result[option] = self.send(option) if self.send(option) && self.send(option) != ""
348
+ end
349
+ result
350
+ end
351
+ end
352
+
353
+ class WorkflowOptionsWithDefaults < WorkflowOptions
354
+ properties(:default_task_start_to_close_timeout, :default_execution_start_to_close_timeout, :default_task_list)
355
+ property(:default_child_policy, [lambda(&:to_s), lambda(&:upcase)])
356
+ end
357
+
358
+ # Options for #start_workflow
359
+ #
360
+ # @!attribute workflow_name
361
+ # The name of this workflow.
362
+ #
363
+ # @!attribute from_class
364
+ # If present, options from the specified class will be used as the workflow execution options.
365
+ #
366
+ class StartWorkflowOptions < WorkflowOptions
367
+ properties(:workflow_name, :from_class)
368
+ end
369
+
370
+ # Options for {AsyncDecider#continue_as_new_workflow} and {Workflows.InstanceMethods#continue_as_new}.
371
+ #
372
+ class ContinueAsNewOptions < WorkflowOptions
373
+ end
374
+
375
+ # Defaults for the {ActivityOptions} class
376
+ class ActivityDefaults < Defaults
377
+
378
+ # The default Schedule to Close timeout for activity tasks. This timeout represents the time, in seconds, between
379
+ # when the activity task is first scheduled to when it is closed (whether due to success, failure, or a timeout).
380
+ #
381
+ # This default can be overridden when scheduling an activity task. You can set this value to "NONE" to imply no
382
+ # timeout value.
383
+ #
384
+ def default_task_schedule_to_close_timeout; Float::INFINITY; end
385
+
386
+ # The default maximum time in seconds before which a worker processing a task of this type must report progress.
387
+ # If the timeout is exceeded, the activity task is automatically timed out. If the worker subsequently attempts to
388
+ # record a heartbeat or returns a result, it will be ignored.
389
+ #
390
+ # This default can be overridden when scheduling an activity task. You can set this value to "NONE" to imply no
391
+ # timeout value.
392
+ #
393
+ def default_task_heartbeat_timeout; Float::INFINITY; end
394
+
395
+ # The default Schedule to Close timeout. This timeout represents the time between when the activity task is first
396
+ # scheduled to when it is closed (whether due to success, failure, or a timeout).
397
+ #
398
+ # This default can be overridden when scheduling an activity task. You can set this value to "NONE" to imply no
399
+ # timeout value.
400
+ #
401
+ def schedule_to_close_timeout; Float::INFINITY; end
402
+
403
+ # The default maximum time before which a worker processing a task of this type must report progress. If the
404
+ # timeout is exceeded, the activity task is automatically timed out. If the worker subsequently attempts to record
405
+ # a heartbeat or returns a result, it will be ignored. This default can be overridden when scheduling an activity
406
+ # task.
407
+ #
408
+ # This default can be overridden when scheduling an activity task. You can set this value to "NONE" to imply no
409
+ # timeout value.
410
+ #
411
+ def heartbeat_timeout; Float::INFINITY; end
412
+
413
+ def data_converter; FlowConstants.default_data_converter; end
414
+ end
415
+
416
+
417
+ # Options to use on an activity or decider. The following options are defined:
418
+ #
419
+ # @!attribute default_task_heartbeat_timeout
420
+ #
421
+ # The optional default maximum time, specified when registering the activity type, before which a worker
422
+ # processing a task must report progress by calling
423
+ # {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SimpleWorkflow/ActivityTask.html#record_heartbeat!-instance_method
424
+ # record_heartbeat} on the ActivityTask.
425
+ #
426
+ # You can override this default when scheduling a task through the ScheduleActivityTask Decision. If the
427
+ # activity worker subsequently attempts to record a heartbeat or returns a result, the activity worker receives
428
+ # an UnknownResource fault. In this case, Amazon SWF no longer considers the activity task to be valid; the
429
+ # activity worker should clean up the activity task.
430
+ #
431
+ # The valid values are integers greater than or equal to zero. An integer value can be used to specify the
432
+ # duration in seconds while "NONE" can be used to specify unlimited duration.
433
+ #
434
+ # @!attribute default_task_list
435
+ #
436
+ # The optional default task list specified for this activity type at registration. This default task list is
437
+ # used if a task list is not provided when a task is scheduled through the ScheduleActivityTask decision. You
438
+ # can override this default when scheduling a task through the ScheduleActivityTask decision.
439
+ #
440
+ # @!attribute default_task_schedule_to_close_timeout
441
+ #
442
+ # The optional default maximum duration, specified when registering the activity type, for tasks of this
443
+ # activity type. You can override this default when scheduling a task through the ScheduleActivityTask Decision.
444
+ #
445
+ # The valid values are integers greater than or equal to zero, or the string "NONE". An integer value can be
446
+ # used to specify the duration in seconds while "NONE" is be used to specify *unlimited* duration.
447
+ #
448
+ # @!attribute default_task_schedule_to_start_timeout
449
+ #
450
+ # The optional default maximum duration, specified when registering the activity type, that a task of an
451
+ # activity type can wait before being assigned to a worker. You can override this default when scheduling a task
452
+ # through the ScheduleActivityTask decision.
453
+ #
454
+ # @!attribute default_task_start_to_close_timeout
455
+ #
456
+ # The optional default maximum duration for tasks of an activity type specified when registering the activity
457
+ # type. You can override this default when scheduling a task through the ScheduleActivityTask decision.
458
+ class ActivityOptions < Options
459
+ # The default options for the activity. These can be specified when creating a new ActivityOptions instance.
460
+ #
461
+ class << self
462
+ attr_reader :default_options, :runtime_options
463
+ end
464
+ properties(:default_task_heartbeat_timeout, :default_task_list, :default_task_schedule_to_close_timeout, :default_task_schedule_to_start_timeout, :default_task_start_to_close_timeout, :heartbeat_timeout, :task_list, :schedule_to_close_timeout, :schedule_to_start_timeout, :start_to_close_timeout, :version, :input)
465
+ property(:manual_completion, [lambda {|x| x == true}])
466
+ property(:data_converter, [])
467
+
468
+ default_classes << ActivityDefaults.new
469
+
470
+ # Gets the activity prefix name
471
+ # @return [String] the activity name
472
+ def activity_name
473
+ @prefix_name
474
+ end
475
+
476
+ # Sets the activity prefix name
477
+ # @param [String] value the activity name to set
478
+ def activity_name=(value)
479
+ @prefix_name = value
480
+ end
481
+
482
+ # Creates a new set of ActivityOptions
483
+ #
484
+ # @param [Hash] default_options
485
+ # A set of ActivityOptions to use as the default values.
486
+ #
487
+ # @option default_options [Integer] :heartbeat_timeout
488
+ # The optional default maximum time, specified when registering the activity type, before which a worker
489
+ # processing a task must report progress by calling RecordActivityTaskHeartbeat. You can override this default
490
+ # when scheduling a task through the ScheduleActivityTask Decision. If the activity worker subsequently attempts
491
+ # to record a heartbeat or returns a result, the activity worker receives an UnknownResource fault. In this
492
+ # case, Amazon SWF no longer considers the activity task to be valid; the activity worker should clean up the
493
+ # activity task.
494
+ #
495
+ # @option default_options [Integer] :schedule_to_close_timeout
496
+ # The optional default maximum duration, specified when registering the activity type, for tasks of this
497
+ # activity type. You can override this default when scheduling a task through the ScheduleActivityTask Decision.
498
+ #
499
+ # @option default_options [Integer] :schedule_to_start_timeout
500
+ # The optional default maximum duration, specified when registering the activity type, that a task of an
501
+ # activity type can wait before being assigned to a worker. You can override this default when scheduling a task
502
+ # through the ScheduleActivityTask Decision.
503
+ #
504
+ # @option default_options [Integer] :start_to_close_timeout
505
+ # The optional default maximum duration for tasks of an activity type specified when registering the activity
506
+ # type. You can override this default when scheduling a task through the ScheduleActivityTask Decision.
507
+ #
508
+ # @option default_options [Array] :task_list
509
+ # The optional default task list specified for this activity type at registration. This default task list is
510
+ # used if a task list is not provided when a task is scheduled through the ScheduleActivityTask Decision. You
511
+ # can override this default when scheduling a task through the ScheduleActivityTask Decision.
512
+ #
513
+ # @option default_options [String] :version
514
+ # The version of this Activity. If you change any other options on the activity, you must also change the
515
+ # version.
516
+ #
517
+ # @param [true, false] use_defaults
518
+ # Set to `true` to use the pre-defined default ActivityOptions.
519
+ #
520
+ def initialize(default_options={}, use_defaults=false)
521
+ if default_options.keys.include? :exponential_retry
522
+ @_exponential_retry = ExponentialRetryOptions.new(default_options[:exponential_retry])
523
+ end
524
+ super(default_options, use_defaults)
525
+ end
526
+
527
+ # Retrieves the runtime options for this Activity. The runtime options returned are:
528
+ #
529
+ # * :heartbeat_timeout
530
+ # * :task_list
531
+ # * :schedule_to_close_timeout
532
+ # * :schedule_to_start_timeout
533
+ # * :start_to_close_timeout
534
+ #
535
+ # For a description of each of these options, see {#initialize}.
536
+ #
537
+ # @return [Hash]
538
+ # The runtime option names and their current values.
539
+ #
540
+ def get_runtime_options
541
+ result = get_options([:heartbeat_timeout, :task_list, :schedule_to_close_timeout, :schedule_to_start_timeout, :start_to_close_timeout])
542
+ default_options = get_options([:default_task_heartbeat_timeout, :default_task_schedule_to_close_timeout, :default_task_schedule_to_start_timeout, :default_task_start_to_close_timeout])
543
+ default_option_keys, default_option_values = default_options.keys, default_options.values
544
+ default_option_keys.map! { |option| option.to_s.gsub(/default_task_/, "").to_sym }
545
+ default_hash = Hash[default_option_keys.zip(default_option_values)]
546
+ default_hash.merge(result)
547
+ end
548
+
549
+ property(:_exponential_retry, [])
550
+
551
+ # Retries the supplied block with exponential retry logic.
552
+ #
553
+ # @param [Hash] block
554
+ # A hash of ExponentialRetryOptions.
555
+ #
556
+ def exponential_retry(&block)
557
+ retry_options = Utilities::interpret_block_for_options(ExponentialRetryOptions, block)
558
+ @_exponential_retry = retry_options
559
+ end
560
+
561
+ # Retrieves the runtime options for this Activity.
562
+ #
563
+ # @return [Hash]
564
+ # A hash containing the runtime option names and their current values.
565
+ #
566
+ def get_full_options
567
+ options_hash = self.get_runtime_options
568
+ [:task_list, :version, :_exponential_retry, :prefix_name, :return_on_start, :manual_completion, :data_converter].each do |attribute|
569
+ options_hash.merge!(attribute => self.send(attribute)) if self.send(attribute)
570
+ end
571
+ options_hash
572
+ end
573
+
574
+ # Retrieves the default options for this Activity.
575
+ #
576
+ # @return [Hash]
577
+ # A hash containing the default option names and their current values.
578
+ #
579
+ # The options retrieved are:
580
+ #
581
+ # * :default_task_heartbeat_timeout
582
+ # * :default_task_schedule_to_close_timeout
583
+ # * :default_task_schedule_to_start_timeout
584
+ # * :default_task_start_to_close_timeout
585
+ #
586
+ def get_default_options
587
+ get_options([:default_task_heartbeat_timeout, :default_task_schedule_to_close_timeout, :default_task_schedule_to_start_timeout, :default_task_start_to_close_timeout])
588
+ end
589
+ end
590
+
591
+ # Runtime options for an Activity.
592
+ class ActivityRuntimeOptions < ActivityOptions
593
+
594
+ # Creates a new set of runtime options based on a set of default options.
595
+ #
596
+ # @param [ActivityOptions] default_options
597
+ # The default {ActivityOptions} to use to set the values of the runtime options in this class.
598
+ #
599
+ # @param [true, false] use_defaults
600
+ # Set to `true` to use the default runtime options for the Activity.
601
+ #
602
+ def initialize(default_options = {}, use_defaults=false)
603
+ super(default_options, use_defaults)
604
+ end
605
+ end
606
+ end
607
+ end