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,60 @@
|
|
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
|
+
class DecisionContext
|
19
|
+
attr_accessor :activity_client, :workflow_client, :workflow_clock, :workflow_context, :decision_helper
|
20
|
+
def initialize(activity_client, workflow_client, workflow_clock, workflow_context, decision_helper)
|
21
|
+
@activity_client = activity_client
|
22
|
+
@workflow_client = workflow_client
|
23
|
+
@workflow_clock = workflow_clock
|
24
|
+
@workflow_context = workflow_context
|
25
|
+
@decision_helper = decision_helper
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# The context for a workflow
|
31
|
+
class WorkflowContext
|
32
|
+
|
33
|
+
attr_accessor :continue_as_new_options
|
34
|
+
# The decision task method for this workflow.
|
35
|
+
attr_accessor :decision_task
|
36
|
+
|
37
|
+
# The {WorkflowClock} for this workflow.
|
38
|
+
attr_accessor :workflow_clock
|
39
|
+
|
40
|
+
# Creates a new `WorkflowContext`
|
41
|
+
#
|
42
|
+
# @param decision_task
|
43
|
+
# The decision task method for this workflow. This is accessible after instance creation by using the
|
44
|
+
# {#decision_task} attribute.
|
45
|
+
#
|
46
|
+
# @param workflow_clock
|
47
|
+
# The {WorkflowClock} to use to schedule timers for this workflow. This is accessible after instance
|
48
|
+
# creation by using the {#workflow_clock} attribute.
|
49
|
+
#
|
50
|
+
def initialize(decision_task, workflow_clock)
|
51
|
+
@decision_task = decision_task
|
52
|
+
@workflow_clock = workflow_clock
|
53
|
+
end
|
54
|
+
def workflow_execution
|
55
|
+
@decision_task.workflow_execution
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -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
|
+
|
20
|
+
# Exception used to communicate failure during fulfillment of a decision sent to SWF. This exception and all its
|
21
|
+
# subclasses are expected to be thrown by the framework.
|
22
|
+
class DecisionException < Exception
|
23
|
+
attr_accessor :event_id
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# An exception that serves as the base for {ChildWorkflowFailedException}, {FailWorkflowExecutionException}, and
|
28
|
+
# {ActivityFailureException}.
|
29
|
+
class FlowException < Exception
|
30
|
+
# A string containing the reason for the exception.
|
31
|
+
attr_accessor :reason
|
32
|
+
|
33
|
+
# A string containing details for the exception.
|
34
|
+
attr_accessor :details
|
35
|
+
|
36
|
+
# Creates a new FlowException.
|
37
|
+
#
|
38
|
+
# @param reason [String]
|
39
|
+
# The reason for the exception. This is made available to the exception receiver through the {#reason}
|
40
|
+
# attribute.
|
41
|
+
#
|
42
|
+
# @param details [String]
|
43
|
+
# The details of the exception. This is made available to the exception receiver through the {#details}
|
44
|
+
# attribute.
|
45
|
+
#
|
46
|
+
def initialize(reason = "Something went wrong in Flow",
|
47
|
+
details = "But this indicates that it got corrupted getting out")
|
48
|
+
@reason = reason
|
49
|
+
@details = details
|
50
|
+
details = details.message if details.is_a? Exception
|
51
|
+
self.set_backtrace(details)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
class ChildWorkflowException < FlowException
|
58
|
+
def detail_termination(message, event_id, workflow_execution, workflow_type)
|
59
|
+
"#{message} for workflow_execution #{workflow_execution.to_s} with event_id #{event_id}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ChildWorkflowTerminatedException < ChildWorkflowException
|
64
|
+
def initialize(event_id, workflow_execution, workflow_type)
|
65
|
+
@reason = "WF exception terminated"
|
66
|
+
@detail = detail_termination("Terminated", event_id, workflow_execution, workflow_type)
|
67
|
+
# TODO: we'll likely want to provide more info later, but it'll take a bit to plumb it
|
68
|
+
# @detail = "Terminate for workflow_execution #{workflow_execution.to_s} of workflow_type #{workflow_type.to_s} with event_id #{event_id}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
class ChildWorkflowTimedOutException < ChildWorkflowException
|
72
|
+
def initialize(event_id, workflow_execution, workflow_type)
|
73
|
+
@reason = "WF exception timed out"
|
74
|
+
@detail = detail_termination("Timed out", event_id, workflow_execution, workflow_type)
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# Unhandled exceptions in child workflows are reported back to the parent workflow implementation by throwing a
|
79
|
+
# `ChildWorkflowFailedException`. The original exception can be retrieved from the {#reason} attribute of this
|
80
|
+
# exception. The exception also provides information in the {#details} attribute that is useful for debugging
|
81
|
+
# purposes, such as the unique identifiers of the child execution.
|
82
|
+
#
|
83
|
+
# @abstract An exception raised when the child workflow execution has failed.
|
84
|
+
#
|
85
|
+
class ChildWorkflowFailedException < FlowException
|
86
|
+
|
87
|
+
attr_accessor :cause, :details
|
88
|
+
# Creates a new `ChildWorkflowFailedException`
|
89
|
+
#
|
90
|
+
# @param event_id
|
91
|
+
# The event id for the exception.
|
92
|
+
#
|
93
|
+
# @param execution
|
94
|
+
# The child workflow execution that raised the exception.
|
95
|
+
#
|
96
|
+
# @param workflow_type
|
97
|
+
# The workflow type of the child workflow that raised the exception.
|
98
|
+
#
|
99
|
+
# @param (see FlowException#initialize)
|
100
|
+
#
|
101
|
+
def initialize(event_id, execution, workflow_type, reason, details)
|
102
|
+
@cause = details
|
103
|
+
# TODO This should probably do more with the event_id, execution, workflow_type
|
104
|
+
super(reason, details)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# @abstract An exception raised when the workflow execution has failed.
|
110
|
+
class FailWorkflowExecutionException < FlowException
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# This exception is used by the framework internally to communicate activity failure. When an activity fails due to
|
115
|
+
# an unhandled exception, it is wrapped in ActivityFailureException and reported to Amazon SWF. You need to deal
|
116
|
+
# with this exception only if you use the activity worker extensibility points. Your application code will never
|
117
|
+
# need to deal with this exception.
|
118
|
+
#
|
119
|
+
# @abstract An exception raised when the activity has failed.
|
120
|
+
class ActivityFailureException < FlowException
|
121
|
+
end
|
122
|
+
class WorkflowException < FlowException; end
|
123
|
+
class SignalExternalWorkflowException < FlowException
|
124
|
+
def initialize(event_id, workflow_execution, cause)
|
125
|
+
super("Signalling the external workflow failed", cause)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class StartChildWorkflowFailedException < FlowException
|
130
|
+
def initialize(event_id, workflow_execution, workflow_type, cause)
|
131
|
+
super("failed to start child workflow", cause)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
class StartTimerFailedException < FlowException
|
135
|
+
def initialize(event_id, timer_id, user_context, cause)
|
136
|
+
super("Timerid #{timer_id} got messed up", cause)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
# This exception is thrown if an activity was timed out by Amazon SWF. This could happen if the activity task could
|
140
|
+
# not be assigned to the worker within the require time period or could not be completed by the worker in the
|
141
|
+
# required time. You can set these timeouts on the activity using the @ActivityRegistrationOptions annotation or
|
142
|
+
# using the ActivitySchedulingOptions parameter when calling the activity method.
|
143
|
+
#
|
144
|
+
# @abstract An exception raised when the activity task has timed out.
|
145
|
+
class ActivityTaskTimedOutException < ActivityFailureException
|
146
|
+
|
147
|
+
# Creates a new ActivityTaskTimeoutException
|
148
|
+
def initialize(id, activity_id, reason, details)
|
149
|
+
@id = id
|
150
|
+
@activity_id = activity_id
|
151
|
+
super(reason, details)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class ScheduleActivityTaskFailedException < FlowException
|
156
|
+
def initialize(event_id, activity_type, activity_id, cause)
|
157
|
+
super("Schedule activity task failed", cause)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Unhandled exceptions in activities are reported back to the workflow implementation by throwing an
|
162
|
+
# `ActivityTaskFailedException`. The original exception can be retrieved from the reason attribute of this
|
163
|
+
# exception. The exception also provides information in the `details` attribute that is useful for debugging
|
164
|
+
# purposes, such as the unique activity identifier in the history.
|
165
|
+
#
|
166
|
+
# @abstract An exception raised when the activity task has failed.
|
167
|
+
class ActivityTaskFailedException < ActivityFailureException
|
168
|
+
attr_accessor :cause, :details
|
169
|
+
# Creates a new ActivityTaskFailedException
|
170
|
+
def initialize(id, activity_id, reason, details)
|
171
|
+
@id = id
|
172
|
+
@activity_id = activity_id
|
173
|
+
@cause = details
|
174
|
+
super(reason, details)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
require 'tmpdir'
|
17
|
+
require 'logger'
|
18
|
+
|
19
|
+
module AWS
|
20
|
+
module Flow
|
21
|
+
|
22
|
+
class LogMock
|
23
|
+
attr_accessor :log_level
|
24
|
+
def initialize()
|
25
|
+
end
|
26
|
+
def info(s)
|
27
|
+
p "info: #{s}" if @log_level > 4
|
28
|
+
end
|
29
|
+
def debug(s)
|
30
|
+
p "debug: #{s}" if @log_level > 3
|
31
|
+
end
|
32
|
+
def warn(s)
|
33
|
+
p "warn: #{s}" if @log_level > 2
|
34
|
+
end
|
35
|
+
def error(s)
|
36
|
+
p "error: #{s}" if @log_level > 1
|
37
|
+
p s.backtrace if s.respond_to?(:backtrace)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
class RejectedExecutionException < Exception; end
|
41
|
+
|
42
|
+
class ForkingExecutor
|
43
|
+
|
44
|
+
class << self
|
45
|
+
attr_accessor :executors
|
46
|
+
end
|
47
|
+
attr_accessor :max_workers, :pids, :is_shutdown
|
48
|
+
|
49
|
+
def initialize(options = {})
|
50
|
+
@log = options[:logger]
|
51
|
+
@log ||= Logger.new("#{Dir.tmpdir}/forking_log")
|
52
|
+
@semaphore = Mutex.new
|
53
|
+
log_level = options[:log_level] || 4
|
54
|
+
@log.level = Logger::DEBUG
|
55
|
+
@log.info("LOG INITIALIZED")
|
56
|
+
@max_workers = options[:max_workers] || 1
|
57
|
+
@pids = []
|
58
|
+
@is_shutdown = false
|
59
|
+
ForkingExecutor.executors ||= []
|
60
|
+
ForkingExecutor.executors << self
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute(&block)
|
64
|
+
@log.error "Here are the pids that are currently running #{@pids}"
|
65
|
+
raise RejectedExecutionException if @is_shutdown
|
66
|
+
block_on_max_workers
|
67
|
+
@log.debug "PARENT BEFORE FORK #{Process.pid}"
|
68
|
+
child_pid = fork do
|
69
|
+
begin
|
70
|
+
@log.debug "CHILD #{Process.pid}"
|
71
|
+
# TODO: which signals to ignore?
|
72
|
+
# ignore signals in the child
|
73
|
+
%w{ TERM INT HUP SIGUSR2 }.each { |signal| Signal.trap(signal, 'SIG_IGN') }
|
74
|
+
block.call
|
75
|
+
@log.debug "CHILD #{Process.pid} AFTER block.call"
|
76
|
+
Process.exit!(0)
|
77
|
+
rescue => e
|
78
|
+
@log.error e
|
79
|
+
@log.error "Definitely dying off right here"
|
80
|
+
Process.exit!(1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@log.debug "PARENT AFTER FORK #{Process.pid}, child_pid=#{child_pid}"
|
84
|
+
@pids << child_pid
|
85
|
+
end
|
86
|
+
|
87
|
+
def shutdown(timeout_seconds)
|
88
|
+
@is_shutdown = true
|
89
|
+
remove_completed_pids
|
90
|
+
|
91
|
+
unless @pids.empty?
|
92
|
+
@log.info "Exit requested, waiting up to #{timeout_seconds} seconds for child processes to finish"
|
93
|
+
|
94
|
+
# check every second for child processes to finish
|
95
|
+
timeout_seconds.times do
|
96
|
+
sleep 1
|
97
|
+
remove_completed_pids
|
98
|
+
break if @pids.empty?
|
99
|
+
end
|
100
|
+
|
101
|
+
# forcibly kill all remaining children
|
102
|
+
unless @pids.empty?
|
103
|
+
@log.warn "Child processes still running, sending KILL signal: #{@pids.join(',')}"
|
104
|
+
@pids.each { |pid| Process.kill('KILL', pid) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Remove all child processes from @pids list that have finished
|
112
|
+
# Block for at least one child to finish if block argument is set to true
|
113
|
+
def remove_completed_pids(block=false)
|
114
|
+
loop do
|
115
|
+
# waitpid2 throws an Errno::ECHILD if there are no child processes,
|
116
|
+
# so we don't even call it if there aren't any pids to wait on
|
117
|
+
break if @pids.empty?
|
118
|
+
# non-blocking wait - only returns a non-null pid
|
119
|
+
# if the child process has exited
|
120
|
+
pid, status = Process.waitpid2(-1, block ? 0 : Process::WNOHANG)
|
121
|
+
@log.debug "#{pid}"
|
122
|
+
# no more children have finished
|
123
|
+
break unless pid
|
124
|
+
|
125
|
+
if status.success?
|
126
|
+
@log.debug "Worker #{pid} exited successfully"
|
127
|
+
else
|
128
|
+
@log.error "Worker #{pid} exited with non-zero status code"
|
129
|
+
end
|
130
|
+
@pids.delete(pid)
|
131
|
+
break if pid
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def block_on_max_workers
|
136
|
+
@log.debug "block_on_max_workers workers=#{@pids.size}, max_workers=#{@max_workers}"
|
137
|
+
start_time = Time.now
|
138
|
+
if @pids.size > @max_workers
|
139
|
+
@log.info "Reached maximum number of workers (#{@max_workers}), \
|
140
|
+
waiting for some to finish before polling again"
|
141
|
+
begin
|
142
|
+
remove_completed_pids(true)
|
143
|
+
end while @pids.size > @max_workers
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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
|
+
class FlowConstants
|
19
|
+
class << self
|
20
|
+
attr_reader :exponential_retry_maximum_retry_interval_seconds, :exponential_retry_retry_expiration_seconds, :exponential_retry_backoff_coefficient, :exponential_retry_maximum_attempts, :exponential_retry_function, :default_data_converter, :exponential_retry_exceptions_to_include, :exponential_retry_exceptions_to_exclude, :jitter_function, :should_jitter
|
21
|
+
# # The maximum exponential retry interval, in seconds. Use the value -1 (the default) to set <i>no maximum</i>.
|
22
|
+
# attr_reader :exponential_retry_maximum_retry_interval_seconds
|
23
|
+
|
24
|
+
# # The maximum time that can pass, in seconds, before the exponential retry attempt is considered a failure. Use
|
25
|
+
# # the value -1 (the default) to set <i>no maximum</i>.
|
26
|
+
# attr_reader :exponential_retry_retry_expiration_seconds
|
27
|
+
|
28
|
+
# # The coefficient used to determine how much to back off the interval timing for an exponential retry scenario.
|
29
|
+
# # The default value, 2.0, causes each attempt to wait twice as long as the previous attempt.
|
30
|
+
# attr_reader :exponential_retry_backoff_coefficient
|
31
|
+
|
32
|
+
# # The maximum number of attempts to make for an exponential retry of a failed task. The default value is
|
33
|
+
# # Float::INFINITY.
|
34
|
+
# attr_reader :exponential_retry_maximum_attempts
|
35
|
+
|
36
|
+
# # The default exponential retry function.
|
37
|
+
# attr_reader :exponential_retry_function
|
38
|
+
|
39
|
+
# The DataConverter for instances of this class.
|
40
|
+
attr_reader :default_data_converter
|
41
|
+
end
|
42
|
+
INFINITY = -1
|
43
|
+
@exponential_retry_maximum_attempts = Float::INFINITY
|
44
|
+
@exponential_retry_maximum_retry_interval_seconds = -1
|
45
|
+
@exponential_retry_retry_expiration_seconds = -1
|
46
|
+
@exponential_retry_backoff_coefficient = 2.0
|
47
|
+
@exponential_retry_initial_retry_interval = 2
|
48
|
+
@should_jitter = true
|
49
|
+
@exponential_retry_exceptions_to_exclude = []
|
50
|
+
@exponential_retry_exceptions_to_include = [Exception]
|
51
|
+
@exponential_retry_function = lambda do |first, time_of_failure, attempts|
|
52
|
+
raise ArgumentError.new("first is not an instance of Time") unless first.instance_of?(Time)
|
53
|
+
raise ArgumentError.new("time_of_failure can't be negative") if time_of_failure < 0
|
54
|
+
raise ArgumentError.new("number of attempts can't be negative") if (attempts.values.find {|x| x < 0})
|
55
|
+
result = @exponential_retry_initial_retry_interval * (@exponential_retry_backoff_coefficient ** (attempts.values.reduce(0, :+) - 2))
|
56
|
+
result = @exponential_retry_maximum_retry_interval_seconds if @exponential_retry_maximum_retry_interval_seconds != INFINITY && result > @exponential_retry_maximum_retry_interval_seconds
|
57
|
+
seconds_since_first_attempt = time_of_failure.zero? ? 0 : -(first - time_of_failure).to_i
|
58
|
+
result = -1 if @exponential_retry_retry_expiration_seconds != INFINITY && (result + seconds_since_first_attempt) >= @exponential_retry_retry_expiration_seconds
|
59
|
+
return result.to_i
|
60
|
+
end
|
61
|
+
|
62
|
+
@jitter_function = lambda do |seed, max_value|
|
63
|
+
random = Random.new(seed.to_i)
|
64
|
+
random.rand(max_value)
|
65
|
+
end
|
66
|
+
|
67
|
+
@default_data_converter = YAMLDataConverter.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|