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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +90 -24
  3. data/.travis.yml +17 -5
  4. data/CHANGELOG.md +265 -62
  5. data/Gemfile +9 -1
  6. data/Gemfile.aws-sdk-core-v2 +13 -0
  7. data/README.md +19 -113
  8. data/Rakefile +1 -1
  9. data/bin/cli/base.rb +0 -3
  10. data/bin/cli/sqs.rb +42 -16
  11. data/bin/shoryuken +4 -9
  12. data/examples/bootstrap_queues.rb +3 -3
  13. data/examples/default_worker.rb +2 -2
  14. data/lib/shoryuken/body_parser.rb +27 -0
  15. data/lib/shoryuken/client.rb +6 -2
  16. data/lib/shoryuken/core_ext.rb +1 -1
  17. data/lib/shoryuken/default_worker_registry.rb +2 -2
  18. data/lib/shoryuken/environment_loader.rb +60 -24
  19. data/lib/shoryuken/extensions/active_job_adapter.rb +21 -11
  20. data/lib/shoryuken/fetcher.rb +58 -19
  21. data/lib/shoryuken/launcher.rb +70 -7
  22. data/lib/shoryuken/logging.rb +1 -6
  23. data/lib/shoryuken/manager.rb +50 -80
  24. data/lib/shoryuken/middleware/chain.rb +4 -0
  25. data/lib/shoryuken/middleware/server/active_record.rb +1 -1
  26. data/lib/shoryuken/middleware/server/auto_delete.rb +4 -9
  27. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +6 -9
  28. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +9 -3
  29. data/lib/shoryuken/middleware/server/timing.rb +12 -16
  30. data/lib/shoryuken/options.rb +225 -0
  31. data/lib/shoryuken/polling/base.rb +67 -0
  32. data/lib/shoryuken/polling/strict_priority.rb +77 -0
  33. data/lib/shoryuken/polling/weighted_round_robin.rb +66 -0
  34. data/lib/shoryuken/processor.rb +30 -39
  35. data/lib/shoryuken/queue.rb +41 -10
  36. data/lib/shoryuken/runner.rb +13 -17
  37. data/lib/shoryuken/util.rb +3 -3
  38. data/lib/shoryuken/version.rb +1 -1
  39. data/lib/shoryuken/worker/default_executor.rb +33 -0
  40. data/lib/shoryuken/worker/inline_executor.rb +37 -0
  41. data/lib/shoryuken/worker.rb +76 -31
  42. data/lib/shoryuken/worker_registry.rb +4 -4
  43. data/lib/shoryuken.rb +54 -173
  44. data/shoryuken.gemspec +6 -6
  45. data/spec/integration/launcher_spec.rb +14 -8
  46. data/spec/shoryuken/body_parser_spec.rb +89 -0
  47. data/spec/shoryuken/client_spec.rb +1 -1
  48. data/spec/shoryuken/core_ext_spec.rb +6 -6
  49. data/spec/shoryuken/default_worker_registry_spec.rb +2 -4
  50. data/spec/shoryuken/environment_loader_spec.rb +32 -12
  51. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +64 -0
  52. data/spec/shoryuken/fetcher_spec.rb +101 -18
  53. data/spec/shoryuken/manager_spec.rb +54 -26
  54. data/spec/shoryuken/middleware/chain_spec.rb +17 -5
  55. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +9 -7
  56. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +4 -4
  57. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +6 -4
  58. data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
  59. data/spec/shoryuken/options_spec.rb +180 -0
  60. data/spec/shoryuken/{polling_spec.rb → polling/strict_priority_spec.rb} +2 -101
  61. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +99 -0
  62. data/spec/shoryuken/processor_spec.rb +26 -127
  63. data/spec/shoryuken/queue_spec.rb +115 -41
  64. data/spec/shoryuken/runner_spec.rb +3 -4
  65. data/spec/shoryuken/util_spec.rb +24 -0
  66. data/spec/shoryuken/worker/default_executor_spec.rb +105 -0
  67. data/spec/shoryuken/worker/inline_executor_spec.rb +49 -0
  68. data/spec/shoryuken/worker_spec.rb +35 -96
  69. data/spec/shoryuken_spec.rb +0 -59
  70. data/spec/spec_helper.rb +14 -3
  71. data/test_workers/endless_interruptive_worker.rb +2 -2
  72. data/test_workers/endless_uninterruptive_worker.rb +4 -4
  73. metadata +31 -12
  74. 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
- register_worker!(job)
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
- queue = Shoryuken::Client.queues(job.queue_name)
47
- queue.send_message(message(job, delay_seconds: delay))
50
+ delay
48
51
  end
49
52
 
50
- private
51
-
52
- def message(job, options = {})
53
+ def message(queue, job, options = {})
53
54
  body = job.serialize
54
55
 
55
- { message_body: body,
56
- message_attributes: message_attributes }.merge(options)
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(sqs_msg, hash)
87
+ def perform(_sqs_msg, hash)
78
88
  Base.execute hash
79
89
  end
80
90
  end
@@ -4,39 +4,78 @@ module Shoryuken
4
4
 
5
5
  FETCH_LIMIT = 10
6
6
 
7
- def fetch(queue, available_processors)
8
- started_at = Time.now
7
+ def initialize(group)
8
+ @group = group
9
+ end
9
10
 
10
- logger.debug { "Looking for new messages in '#{queue}'" }
11
+ def fetch(queue, limit)
12
+ fetch_with_auto_retry(3) do
13
+ started_at = Time.now
11
14
 
12
- begin
13
- limit = available_processors > FETCH_LIMIT ? FETCH_LIMIT : available_processors
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
- # AWS limits the batch size by 10
30
- limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
48
+ options = receive_options(queue)
49
+
50
+ shoryuken_queue = Shoryuken::Client.queues(queue.name)
31
51
 
32
- options = Shoryuken.sqs_client_receive_message_opts.to_h.dup
33
- options[:max_number_of_messages] = limit
34
- options[:message_attribute_names] = %w(All)
35
- options[:attribute_names] = %w(All)
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
- Shoryuken::Client.queues(queue.name).receive_messages(options)
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
@@ -3,17 +3,80 @@ module Shoryuken
3
3
  include Util
4
4
 
5
5
  def initialize
6
- @manager = Shoryuken::Manager.new(Shoryuken::Fetcher.new,
7
- Shoryuken.options[:polling_strategy].new(Shoryuken.queues))
6
+ @managers = create_managers
8
7
  end
9
8
 
10
- def stop(options = {})
11
- @manager.stop(shutdown: !options[:shutdown].nil?,
12
- timeout: Shoryuken.options[:timeout])
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 run
16
- @manager.start
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
@@ -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, program_name, message)
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
@@ -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
- @count = Shoryuken.options.fetch(:concurrency, 25)
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
- @pool = Concurrent::FixedThreadPool.new(@count, max_queue: @count)
22
- @dispatcher_executor = Concurrent::SingleThreadExecutor.new
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
- logger.info { 'Starting' }
27
-
28
- dispatch_async
19
+ dispatch_loop
29
20
  end
30
21
 
31
- def stop(options = {})
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
- if options[:shutdown]
46
- hard_shutdown_in(options[:timeout])
47
- else
48
- soft_shutdown
49
- end
24
+ def running?
25
+ @running.true? && @executor.running?
50
26
  end
51
27
 
52
- def processor_failed(ex)
53
- logger.error ex
54
- logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
55
- end
28
+ def dispatch_loop
29
+ return unless running?
56
30
 
57
- def processor_done(queue)
58
- logger.debug { "Process done for '#{queue}'" }
31
+ @executor.post { dispatch }
59
32
  end
60
33
 
61
- private
34
+ def dispatch
35
+ return unless running?
62
36
 
63
- def dispatch_async
64
- @dispatcher_executor.post(&method(:dispatch_now))
65
- end
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
- begin
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
- logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
43
+ logger.debug { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{@polling_strategy.active_queues}" }
77
44
 
78
- batched_queue?(queue) ? dispatch_batch(queue) : dispatch_single_messages(queue)
79
- ensure
80
- dispatch_async
81
- end
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
- @count - ready
53
+ @busy_processors.value
86
54
  end
87
55
 
88
56
  def ready
89
- @pool.remaining_capacity
57
+ @max_processors - busy
58
+ end
59
+
60
+ def processor_done
61
+ @busy_processors.decrement
90
62
  end
91
63
 
92
- def assign(queue, sqs_msg)
64
+ def assign(queue_name, sqs_msg)
65
+ return unless running?
66
+
93
67
  logger.debug { "Assigning #{sqs_msg.message_id}" }
94
68
 
95
- @pool.post { Processor.new(self).process(queue, sqs_msg) }
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,7 +2,7 @@ module Shoryuken
2
2
  module Middleware
3
3
  module Server
4
4
  class ActiveRecord
5
- def call(*args)
5
+ def call(*_args)
6
6
  yield
7
7
  ensure
8
8
  ::ActiveRecord::Base.clear_active_connections!
@@ -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, body)
5
+ def call(worker, queue, sqs_msg, _body)
6
6
  yield
7
7
 
8
- auto_delete = worker.class.get_shoryuken_options['delete'] || worker.class.get_shoryuken_options['auto_delete']
8
+ return unless worker.class.auto_delete?
9
9
 
10
- if auto_delete
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
- Shoryuken::Client.queues(queue).delete_messages(entries: entries)
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(worker, queue, sqs_msg, body)
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 #{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
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 => e
38
+ rescue => ex
38
39
  logger.error do
39
- 'Could not auto extend the message ' \
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, queue, sqs_msg, body)
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(worker, queue, sqs_msg, body)
8
- Shoryuken::Logging.with_context("#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id}") do
9
- begin
10
- started_at = Time.now
7
+ def call(_worker, queue, _sqs_msg, _body)
8
+ started_at = Time.now
11
9
 
12
- logger.info { "started at #{started_at}" }
10
+ logger.info { "started at #{started_at}" }
13
11
 
14
- yield
12
+ yield
15
13
 
16
- total_time = elapsed(started_at)
14
+ total_time = elapsed(started_at)
17
15
 
18
- if (total_time / 1000.0) > (timeout = Shoryuken::Client.queues(queue).visibility_timeout)
19
- logger.warn { "exceeded the queue visibility timeout by #{total_time - (timeout * 1000)} ms" }
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