shoryuken 3.0.6 → 4.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +90 -24
- data/.travis.yml +17 -5
- data/CHANGELOG.md +265 -62
- data/Gemfile +9 -1
- data/Gemfile.aws-sdk-core-v2 +13 -0
- data/README.md +19 -113
- data/Rakefile +1 -1
- data/bin/cli/base.rb +0 -3
- data/bin/cli/sqs.rb +42 -16
- data/bin/shoryuken +4 -9
- data/examples/bootstrap_queues.rb +3 -3
- data/examples/default_worker.rb +2 -2
- data/lib/shoryuken/body_parser.rb +27 -0
- data/lib/shoryuken/client.rb +6 -2
- data/lib/shoryuken/core_ext.rb +1 -1
- data/lib/shoryuken/default_worker_registry.rb +2 -2
- data/lib/shoryuken/environment_loader.rb +60 -24
- data/lib/shoryuken/extensions/active_job_adapter.rb +21 -11
- data/lib/shoryuken/fetcher.rb +58 -19
- data/lib/shoryuken/launcher.rb +70 -7
- data/lib/shoryuken/logging.rb +1 -6
- data/lib/shoryuken/manager.rb +50 -80
- data/lib/shoryuken/middleware/chain.rb +4 -0
- data/lib/shoryuken/middleware/server/active_record.rb +1 -1
- data/lib/shoryuken/middleware/server/auto_delete.rb +4 -9
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +6 -9
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +9 -3
- data/lib/shoryuken/middleware/server/timing.rb +12 -16
- data/lib/shoryuken/options.rb +225 -0
- data/lib/shoryuken/polling/base.rb +67 -0
- data/lib/shoryuken/polling/strict_priority.rb +77 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +66 -0
- data/lib/shoryuken/processor.rb +30 -39
- data/lib/shoryuken/queue.rb +41 -10
- data/lib/shoryuken/runner.rb +13 -17
- data/lib/shoryuken/util.rb +3 -3
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker/default_executor.rb +33 -0
- data/lib/shoryuken/worker/inline_executor.rb +37 -0
- data/lib/shoryuken/worker.rb +76 -31
- data/lib/shoryuken/worker_registry.rb +4 -4
- data/lib/shoryuken.rb +54 -173
- data/shoryuken.gemspec +6 -6
- data/spec/integration/launcher_spec.rb +14 -8
- data/spec/shoryuken/body_parser_spec.rb +89 -0
- data/spec/shoryuken/client_spec.rb +1 -1
- data/spec/shoryuken/core_ext_spec.rb +6 -6
- data/spec/shoryuken/default_worker_registry_spec.rb +2 -4
- data/spec/shoryuken/environment_loader_spec.rb +32 -12
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +64 -0
- data/spec/shoryuken/fetcher_spec.rb +101 -18
- data/spec/shoryuken/manager_spec.rb +54 -26
- data/spec/shoryuken/middleware/chain_spec.rb +17 -5
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +9 -7
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +4 -4
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +6 -4
- data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
- data/spec/shoryuken/options_spec.rb +180 -0
- data/spec/shoryuken/{polling_spec.rb → polling/strict_priority_spec.rb} +2 -101
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
- data/spec/shoryuken/processor_spec.rb +26 -127
- data/spec/shoryuken/queue_spec.rb +115 -41
- data/spec/shoryuken/runner_spec.rb +3 -4
- data/spec/shoryuken/util_spec.rb +24 -0
- data/spec/shoryuken/worker/default_executor_spec.rb +105 -0
- data/spec/shoryuken/worker/inline_executor_spec.rb +49 -0
- data/spec/shoryuken/worker_spec.rb +35 -96
- data/spec/shoryuken_spec.rb +0 -59
- data/spec/spec_helper.rb +14 -3
- data/test_workers/endless_interruptive_worker.rb +2 -2
- data/test_workers/endless_uninterruptive_worker.rb +4 -4
- metadata +31 -12
- data/lib/shoryuken/polling.rb +0 -204
|
@@ -30,30 +30,40 @@ module ActiveJob
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def enqueue(job) #:nodoc:
|
|
33
|
+
def enqueue(job, options = {}) #:nodoc:
|
|
34
34
|
register_worker!(job)
|
|
35
35
|
|
|
36
36
|
queue = Shoryuken::Client.queues(job.queue_name)
|
|
37
|
-
queue.send_message(message(job))
|
|
37
|
+
queue.send_message(message(queue, job, options))
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def enqueue_at(job, timestamp) #:nodoc:
|
|
41
|
-
|
|
41
|
+
enqueue(job, delay_seconds: calculate_delay(timestamp))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
42
45
|
|
|
46
|
+
def calculate_delay(timestamp)
|
|
43
47
|
delay = (timestamp - Time.current.to_f).round
|
|
44
48
|
raise 'The maximum allowed delay is 15 minutes' if delay > 15.minutes
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
queue.send_message(message(job, delay_seconds: delay))
|
|
50
|
+
delay
|
|
48
51
|
end
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def message(job, options = {})
|
|
53
|
+
def message(queue, job, options = {})
|
|
53
54
|
body = job.serialize
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
msg = {}
|
|
57
|
+
|
|
58
|
+
if queue.fifo?
|
|
59
|
+
# See https://github.com/phstc/shoryuken/issues/457
|
|
60
|
+
msg[:message_deduplication_id] = Digest::SHA256.hexdigest(JSON.dump(body.except('job_id')))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
msg[:message_body] = body
|
|
64
|
+
msg[:message_attributes] = message_attributes
|
|
65
|
+
|
|
66
|
+
msg.merge(options)
|
|
57
67
|
end
|
|
58
68
|
|
|
59
69
|
def register_worker!(job)
|
|
@@ -74,7 +84,7 @@ module ActiveJob
|
|
|
74
84
|
|
|
75
85
|
shoryuken_options body_parser: :json, auto_delete: true
|
|
76
86
|
|
|
77
|
-
def perform(
|
|
87
|
+
def perform(_sqs_msg, hash)
|
|
78
88
|
Base.execute hash
|
|
79
89
|
end
|
|
80
90
|
end
|
data/lib/shoryuken/fetcher.rb
CHANGED
|
@@ -4,39 +4,78 @@ module Shoryuken
|
|
|
4
4
|
|
|
5
5
|
FETCH_LIMIT = 10
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
|
|
7
|
+
def initialize(group)
|
|
8
|
+
@group = group
|
|
9
|
+
end
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
def fetch(queue, limit)
|
|
12
|
+
fetch_with_auto_retry(3) do
|
|
13
|
+
started_at = Time.now
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
logger.debug { "Looking for new messages in #{queue}" }
|
|
16
|
+
|
|
17
|
+
sqs_msgs = Array(receive_messages(queue, [FETCH_LIMIT, limit].min))
|
|
18
|
+
|
|
19
|
+
logger.debug { "Found #{sqs_msgs.size} messages for #{queue.name}" } unless sqs_msgs.empty?
|
|
20
|
+
logger.debug { "Fetcher for #{queue} completed in #{elapsed(started_at)} ms" }
|
|
14
21
|
|
|
15
|
-
sqs_msgs = Array(receive_messages(queue, limit))
|
|
16
|
-
logger.info { "Found #{sqs_msgs.size} messages for '#{queue.name}'" } unless sqs_msgs.empty?
|
|
17
|
-
logger.debug { "Fetcher for '#{queue}' completed in #{elapsed(started_at)} ms" }
|
|
18
22
|
sqs_msgs
|
|
19
|
-
rescue => ex
|
|
20
|
-
logger.error { "Error fetching message: #{ex}" }
|
|
21
|
-
logger.error { ex.backtrace.first }
|
|
22
|
-
[]
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
+
def fetch_with_auto_retry(max_attempts)
|
|
29
|
+
attempts = 0
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
yield
|
|
33
|
+
rescue => ex
|
|
34
|
+
# Tries to auto retry connectivity errors
|
|
35
|
+
raise if attempts >= max_attempts
|
|
36
|
+
|
|
37
|
+
attempts += 1
|
|
38
|
+
|
|
39
|
+
logger.debug { "Retrying fetch attempt #{attempts} for #{ex.message}" }
|
|
40
|
+
|
|
41
|
+
sleep((1..5).to_a.sample)
|
|
42
|
+
|
|
43
|
+
retry
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
28
47
|
def receive_messages(queue, limit)
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
options = receive_options(queue)
|
|
49
|
+
|
|
50
|
+
shoryuken_queue = Shoryuken::Client.queues(queue.name)
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
# For FIFO queues we want to make sure we process one message per group at the time
|
|
53
|
+
# if we set max_number_of_messages greater than 1,
|
|
54
|
+
# SQS may return more than one message for the same message group
|
|
55
|
+
# since Shoryuken uses threads, it will try to process more than one at once
|
|
56
|
+
# > The message group ID is the tag that specifies that a message belongs to a specific message group.
|
|
57
|
+
# > Messages that belong to the same message group are always processed one by one,
|
|
58
|
+
# > in a strict order relative to the message group
|
|
59
|
+
# > (however, messages that belong to different message groups might be processed out of order).
|
|
60
|
+
# > https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html
|
|
61
|
+
options[:max_number_of_messages] = shoryuken_queue.fifo? ? 1 : max_number_of_messages(limit, options)
|
|
62
|
+
options[:message_attribute_names] = %w[All]
|
|
63
|
+
options[:attribute_names] = %w[All]
|
|
36
64
|
|
|
37
65
|
options.merge!(queue.options)
|
|
38
66
|
|
|
39
|
-
|
|
67
|
+
shoryuken_queue.receive_messages(options)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def max_number_of_messages(limit, options)
|
|
71
|
+
[limit, FETCH_LIMIT, options[:max_number_of_messages]].compact.min
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def receive_options(queue)
|
|
75
|
+
options = Shoryuken.sqs_client_receive_message_opts[queue.name]
|
|
76
|
+
options ||= Shoryuken.sqs_client_receive_message_opts[@group]
|
|
77
|
+
|
|
78
|
+
options.to_h.dup
|
|
40
79
|
end
|
|
41
80
|
end
|
|
42
81
|
end
|
data/lib/shoryuken/launcher.rb
CHANGED
|
@@ -3,17 +3,80 @@ module Shoryuken
|
|
|
3
3
|
include Util
|
|
4
4
|
|
|
5
5
|
def initialize
|
|
6
|
-
@
|
|
7
|
-
Shoryuken.options[:polling_strategy].new(Shoryuken.queues))
|
|
6
|
+
@managers = create_managers
|
|
8
7
|
end
|
|
9
8
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def start
|
|
10
|
+
logger.info { 'Starting' }
|
|
11
|
+
|
|
12
|
+
start_callback
|
|
13
|
+
start_managers
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def stop!
|
|
17
|
+
initiate_stop
|
|
18
|
+
|
|
19
|
+
executor.shutdown
|
|
20
|
+
|
|
21
|
+
return if executor.wait_for_termination(Shoryuken.options[:timeout])
|
|
22
|
+
|
|
23
|
+
executor.kill
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stop
|
|
27
|
+
fire_event(:quiet, true)
|
|
28
|
+
|
|
29
|
+
initiate_stop
|
|
30
|
+
|
|
31
|
+
executor.shutdown
|
|
32
|
+
executor.wait_for_termination
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def executor
|
|
38
|
+
@_executor ||= Shoryuken.launcher_executor || Concurrent.global_io_executor
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def start_managers
|
|
42
|
+
@managers.each do |manager|
|
|
43
|
+
Concurrent::Future.execute { manager.start }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initiate_stop
|
|
48
|
+
logger.info { 'Shutting down' }
|
|
49
|
+
|
|
50
|
+
stop_callback
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def start_callback
|
|
54
|
+
if (callback = Shoryuken.start_callback)
|
|
55
|
+
logger.debug { 'Calling start_callback' }
|
|
56
|
+
callback.call
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
fire_event(:startup)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stop_callback
|
|
63
|
+
if (callback = Shoryuken.stop_callback)
|
|
64
|
+
logger.debug { 'Calling stop_callback' }
|
|
65
|
+
callback.call
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
fire_event(:shutdown, true)
|
|
13
69
|
end
|
|
14
70
|
|
|
15
|
-
def
|
|
16
|
-
|
|
71
|
+
def create_managers
|
|
72
|
+
Shoryuken.groups.map do |group, options|
|
|
73
|
+
Shoryuken::Manager.new(
|
|
74
|
+
Shoryuken::Fetcher.new(group),
|
|
75
|
+
Shoryuken.polling_strategy(group).new(options[:queues]),
|
|
76
|
+
options[:concurrency],
|
|
77
|
+
executor
|
|
78
|
+
)
|
|
79
|
+
end
|
|
17
80
|
end
|
|
18
81
|
end
|
|
19
82
|
end
|
data/lib/shoryuken/logging.rb
CHANGED
|
@@ -3,10 +3,9 @@ require 'logger'
|
|
|
3
3
|
|
|
4
4
|
module Shoryuken
|
|
5
5
|
module Logging
|
|
6
|
-
|
|
7
6
|
class Pretty < Logger::Formatter
|
|
8
7
|
# Provide a call() method that returns the formatted message.
|
|
9
|
-
def call(severity, time,
|
|
8
|
+
def call(severity, time, _program_name, message)
|
|
10
9
|
"#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
|
|
11
10
|
end
|
|
12
11
|
|
|
@@ -37,9 +36,5 @@ module Shoryuken
|
|
|
37
36
|
def self.logger=(log)
|
|
38
37
|
@logger = (log ? log : Logger.new('/dev/null'))
|
|
39
38
|
end
|
|
40
|
-
|
|
41
|
-
def logger
|
|
42
|
-
shoryuken::Logging.logger
|
|
43
|
-
end
|
|
44
39
|
end
|
|
45
40
|
end
|
data/lib/shoryuken/manager.rb
CHANGED
|
@@ -6,103 +6,82 @@ module Shoryuken
|
|
|
6
6
|
# See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
|
|
7
7
|
MIN_DISPATCH_INTERVAL = 0.1
|
|
8
8
|
|
|
9
|
-
def initialize(fetcher, polling_strategy)
|
|
10
|
-
@
|
|
11
|
-
|
|
12
|
-
raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
|
|
13
|
-
|
|
14
|
-
@queues = Shoryuken.queues.dup.uniq
|
|
15
|
-
|
|
16
|
-
@done = Concurrent::AtomicBoolean.new(false)
|
|
17
|
-
|
|
18
|
-
@fetcher = fetcher
|
|
9
|
+
def initialize(fetcher, polling_strategy, concurrency, executor)
|
|
10
|
+
@fetcher = fetcher
|
|
19
11
|
@polling_strategy = polling_strategy
|
|
20
|
-
|
|
21
|
-
@
|
|
22
|
-
@
|
|
12
|
+
@max_processors = concurrency
|
|
13
|
+
@busy_processors = Concurrent::AtomicFixnum.new(0)
|
|
14
|
+
@executor = executor
|
|
15
|
+
@running = Concurrent::AtomicBoolean.new(true)
|
|
23
16
|
end
|
|
24
17
|
|
|
25
18
|
def start
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
dispatch_async
|
|
19
|
+
dispatch_loop
|
|
29
20
|
end
|
|
30
21
|
|
|
31
|
-
|
|
32
|
-
@done.make_true
|
|
33
|
-
|
|
34
|
-
if (callback = Shoryuken.stop_callback)
|
|
35
|
-
logger.info { 'Calling Shoryuken.on_stop block' }
|
|
36
|
-
callback.call
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
fire_event(:shutdown, true)
|
|
40
|
-
|
|
41
|
-
logger.info { 'Shutting down workers' }
|
|
42
|
-
|
|
43
|
-
@dispatcher_executor.kill
|
|
22
|
+
private
|
|
44
23
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else
|
|
48
|
-
soft_shutdown
|
|
49
|
-
end
|
|
24
|
+
def running?
|
|
25
|
+
@running.true? && @executor.running?
|
|
50
26
|
end
|
|
51
27
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
|
|
55
|
-
end
|
|
28
|
+
def dispatch_loop
|
|
29
|
+
return unless running?
|
|
56
30
|
|
|
57
|
-
|
|
58
|
-
logger.debug { "Process done for '#{queue}'" }
|
|
31
|
+
@executor.post { dispatch }
|
|
59
32
|
end
|
|
60
33
|
|
|
61
|
-
|
|
34
|
+
def dispatch
|
|
35
|
+
return unless running?
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def dispatch_now
|
|
68
|
-
return if @done.true?
|
|
37
|
+
if ready <= 0 || (queue = @polling_strategy.next_queue).nil?
|
|
38
|
+
return sleep(MIN_DISPATCH_INTERVAL)
|
|
39
|
+
end
|
|
69
40
|
|
|
70
|
-
|
|
71
|
-
if ready.zero? || (queue = @polling_strategy.next_queue).nil?
|
|
72
|
-
sleep MIN_DISPATCH_INTERVAL
|
|
73
|
-
return
|
|
74
|
-
end
|
|
41
|
+
fire_event(:dispatch, false, queue_name: queue.name)
|
|
75
42
|
|
|
76
|
-
|
|
43
|
+
logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
|
|
77
44
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
45
|
+
batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
|
|
46
|
+
rescue => ex
|
|
47
|
+
handle_dispatch_error(ex)
|
|
48
|
+
ensure
|
|
49
|
+
dispatch_loop
|
|
82
50
|
end
|
|
83
51
|
|
|
84
52
|
def busy
|
|
85
|
-
@
|
|
53
|
+
@busy_processors.value
|
|
86
54
|
end
|
|
87
55
|
|
|
88
56
|
def ready
|
|
89
|
-
@
|
|
57
|
+
@max_processors - busy
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def processor_done
|
|
61
|
+
@busy_processors.decrement
|
|
90
62
|
end
|
|
91
63
|
|
|
92
|
-
def assign(
|
|
64
|
+
def assign(queue_name, sqs_msg)
|
|
65
|
+
return unless running?
|
|
66
|
+
|
|
93
67
|
logger.debug { "Assigning #{sqs_msg.message_id}" }
|
|
94
68
|
|
|
95
|
-
@
|
|
69
|
+
@busy_processors.increment
|
|
70
|
+
|
|
71
|
+
Concurrent::Promise.execute(
|
|
72
|
+
executor: @executor
|
|
73
|
+
) { Processor.process(queue_name, sqs_msg) }.then { processor_done }.rescue { processor_done }
|
|
96
74
|
end
|
|
97
75
|
|
|
98
76
|
def dispatch_batch(queue)
|
|
99
|
-
batch = @fetcher.fetch(queue, BATCH_LIMIT)
|
|
77
|
+
return if (batch = @fetcher.fetch(queue, BATCH_LIMIT)).none?
|
|
100
78
|
@polling_strategy.messages_found(queue.name, batch.size)
|
|
101
79
|
assign(queue.name, patch_batch!(batch))
|
|
102
80
|
end
|
|
103
81
|
|
|
104
82
|
def dispatch_single_messages(queue)
|
|
105
83
|
messages = @fetcher.fetch(queue, ready)
|
|
84
|
+
|
|
106
85
|
@polling_strategy.messages_found(queue.name, messages.size)
|
|
107
86
|
messages.each { |message| assign(queue.name, message) }
|
|
108
87
|
end
|
|
@@ -111,24 +90,6 @@ module Shoryuken
|
|
|
111
90
|
Shoryuken.worker_registry.batch_receive_messages?(queue.name)
|
|
112
91
|
end
|
|
113
92
|
|
|
114
|
-
def soft_shutdown
|
|
115
|
-
@pool.shutdown
|
|
116
|
-
@pool.wait_for_termination
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def hard_shutdown_in(delay)
|
|
120
|
-
if busy > 0
|
|
121
|
-
logger.info { "Pausing up to #{delay} seconds to allow workers to finish..." }
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
@pool.shutdown
|
|
125
|
-
|
|
126
|
-
return if @pool.wait_for_termination(delay)
|
|
127
|
-
|
|
128
|
-
logger.info { "Hard shutting down #{busy} busy workers" }
|
|
129
|
-
@pool.kill
|
|
130
|
-
end
|
|
131
|
-
|
|
132
93
|
def patch_batch!(sqs_msgs)
|
|
133
94
|
sqs_msgs.instance_eval do
|
|
134
95
|
def message_id
|
|
@@ -138,5 +99,14 @@ module Shoryuken
|
|
|
138
99
|
|
|
139
100
|
sqs_msgs
|
|
140
101
|
end
|
|
102
|
+
|
|
103
|
+
def handle_dispatch_error(ex)
|
|
104
|
+
logger.error { "Manager failed: #{ex.message}" }
|
|
105
|
+
logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
|
|
106
|
+
|
|
107
|
+
Process.kill('USR1', Process.pid)
|
|
108
|
+
|
|
109
|
+
@running.make_false
|
|
110
|
+
end
|
|
141
111
|
end
|
|
142
112
|
end
|
|
@@ -61,6 +61,10 @@ module Shoryuken
|
|
|
61
61
|
entries << Entry.new(klass, *args) unless exists?(klass)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def prepend(klass, *args)
|
|
65
|
+
entries.insert(0, Entry.new(klass, *args)) unless exists?(klass)
|
|
66
|
+
end
|
|
67
|
+
|
|
64
68
|
def insert_before(oldklass, newklass, *args)
|
|
65
69
|
i = entries.index { |entry| entry.klass == newklass }
|
|
66
70
|
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
|
@@ -2,21 +2,16 @@ module Shoryuken
|
|
|
2
2
|
module Middleware
|
|
3
3
|
module Server
|
|
4
4
|
class AutoDelete
|
|
5
|
-
def call(worker, queue, sqs_msg,
|
|
5
|
+
def call(worker, queue, sqs_msg, _body)
|
|
6
6
|
yield
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
return unless worker.class.auto_delete?
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
entries = [sqs_msg].flatten.map.with_index do |message, i|
|
|
12
|
-
{ id: i.to_s, receipt_handle: message.receipt_handle }
|
|
13
|
-
end
|
|
10
|
+
entries = [sqs_msg].flatten.map.with_index { |message, i| { id: i.to_s, receipt_handle: message.receipt_handle } }
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
end
|
|
12
|
+
Shoryuken::Client.queues(queue).delete_messages(entries: entries)
|
|
17
13
|
end
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
end
|
|
21
17
|
end
|
|
22
|
-
|
|
@@ -7,6 +7,8 @@ module Shoryuken
|
|
|
7
7
|
EXTEND_UPFRONT_SECONDS = 5
|
|
8
8
|
|
|
9
9
|
def call(worker, queue, sqs_msg, body)
|
|
10
|
+
return yield unless worker.class.auto_visibility_timeout?
|
|
11
|
+
|
|
10
12
|
if sqs_msg.is_a?(Array)
|
|
11
13
|
logger.warn { "Auto extend visibility isn't supported for batch workers" }
|
|
12
14
|
return yield
|
|
@@ -23,22 +25,19 @@ module Shoryuken
|
|
|
23
25
|
class MessageVisibilityExtender
|
|
24
26
|
include Util
|
|
25
27
|
|
|
26
|
-
def auto_extend(
|
|
28
|
+
def auto_extend(_worker, queue, sqs_msg, _body)
|
|
27
29
|
queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
|
|
28
30
|
|
|
29
31
|
Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
|
|
30
32
|
begin
|
|
31
33
|
logger.debug do
|
|
32
|
-
"Extending message #{
|
|
33
|
-
"visibility timeout by #{queue_visibility_timeout}s."
|
|
34
|
+
"Extending message #{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s"
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
|
|
37
|
-
rescue =>
|
|
38
|
+
rescue => ex
|
|
38
39
|
logger.error do
|
|
39
|
-
|
|
40
|
-
"#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
|
41
|
-
"visibility timeout. Error: #{e.message}"
|
|
40
|
+
"Could not auto extend the message #{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{ex.message}"
|
|
42
41
|
end
|
|
43
42
|
end
|
|
44
43
|
end
|
|
@@ -46,8 +45,6 @@ module Shoryuken
|
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
def auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
49
|
-
return unless worker.class.auto_visibility_timeout?
|
|
50
|
-
|
|
51
48
|
MessageVisibilityExtender.new.auto_extend(worker, queue, sqs_msg, body).tap(&:execute)
|
|
52
49
|
end
|
|
53
50
|
end
|
|
@@ -4,7 +4,9 @@ module Shoryuken
|
|
|
4
4
|
class ExponentialBackoffRetry
|
|
5
5
|
include Util
|
|
6
6
|
|
|
7
|
-
def call(worker,
|
|
7
|
+
def call(worker, _queue, sqs_msg, _body)
|
|
8
|
+
return yield unless worker.class.exponential_backoff?
|
|
9
|
+
|
|
8
10
|
if sqs_msg.is_a?(Array)
|
|
9
11
|
logger.warn { "Exponential backoff isn't supported for batch workers" }
|
|
10
12
|
return yield
|
|
@@ -12,7 +14,7 @@ module Shoryuken
|
|
|
12
14
|
|
|
13
15
|
started_at = Time.now
|
|
14
16
|
yield
|
|
15
|
-
rescue
|
|
17
|
+
rescue => ex
|
|
16
18
|
retry_intervals = worker.class.get_shoryuken_options['retry_intervals']
|
|
17
19
|
|
|
18
20
|
if retry_intervals.nil? || !handle_failure(sqs_msg, started_at, retry_intervals)
|
|
@@ -20,6 +22,10 @@ module Shoryuken
|
|
|
20
22
|
# This allows custom middleware (like exception notifiers) to be aware of the unhandled failure.
|
|
21
23
|
raise
|
|
22
24
|
end
|
|
25
|
+
|
|
26
|
+
logger.warn { "Message #{sqs_msg.message_id} will attempt retry due to error: #{ex.message}" }
|
|
27
|
+
# since we didn't raise, lets log the backtrace for debugging purposes.
|
|
28
|
+
logger.debug { ex.backtrace.join("\n") } unless ex.backtrace.nil?
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
private
|
|
@@ -47,7 +53,7 @@ module Shoryuken
|
|
|
47
53
|
|
|
48
54
|
sqs_msg.change_visibility(visibility_timeout: next_visibility_timeout(interval.to_i, started_at))
|
|
49
55
|
|
|
50
|
-
logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds
|
|
56
|
+
logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds" }
|
|
51
57
|
|
|
52
58
|
true
|
|
53
59
|
end
|
|
@@ -4,27 +4,23 @@ module Shoryuken
|
|
|
4
4
|
class Timing
|
|
5
5
|
include Util
|
|
6
6
|
|
|
7
|
-
def call(
|
|
8
|
-
|
|
9
|
-
begin
|
|
10
|
-
started_at = Time.now
|
|
7
|
+
def call(_worker, queue, _sqs_msg, _body)
|
|
8
|
+
started_at = Time.now
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
logger.info { "started at #{started_at}" }
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
yield
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
total_time = elapsed(started_at)
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
logger.info { "completed in: #{total_time} ms" }
|
|
23
|
-
rescue => e
|
|
24
|
-
logger.info { "failed in: #{elapsed(started_at)} ms" }
|
|
25
|
-
raise e
|
|
26
|
-
end
|
|
16
|
+
if (total_time / 1000.0) > (timeout = Shoryuken::Client.queues(queue).visibility_timeout)
|
|
17
|
+
logger.warn { "exceeded the queue visibility timeout by #{total_time - (timeout * 1000)} ms" }
|
|
27
18
|
end
|
|
19
|
+
|
|
20
|
+
logger.info { "completed in: #{total_time} ms" }
|
|
21
|
+
rescue
|
|
22
|
+
logger.info { "failed in: #{elapsed(started_at)} ms" }
|
|
23
|
+
raise
|
|
28
24
|
end
|
|
29
25
|
end
|
|
30
26
|
end
|