shoryuken 5.0.5 → 6.1.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +17 -0
  3. data/.devcontainer/base.Dockerfile +43 -0
  4. data/.devcontainer/devcontainer.json +35 -0
  5. data/.github/dependabot.yml +6 -0
  6. data/.github/workflows/specs.yml +65 -0
  7. data/.github/workflows/stale.yml +20 -0
  8. data/.gitignore +1 -1
  9. data/.reek.yml +5 -0
  10. data/.rubocop.yml +1 -1
  11. data/Appraisals +42 -0
  12. data/CHANGELOG.md +114 -0
  13. data/Gemfile +8 -3
  14. data/README.md +41 -1
  15. data/Rakefile +15 -1
  16. data/bin/cli/sqs.rb +51 -6
  17. data/gemfiles/.gitignore +1 -0
  18. data/gemfiles/aws_sdk_core_2.gemfile +21 -0
  19. data/gemfiles/rails_4_2.gemfile +20 -0
  20. data/gemfiles/rails_5_2.gemfile +21 -0
  21. data/gemfiles/rails_6_0.gemfile +21 -0
  22. data/gemfiles/rails_6_1.gemfile +21 -0
  23. data/gemfiles/rails_7_0.gemfile +22 -0
  24. data/lib/shoryuken/default_exception_handler.rb +10 -0
  25. data/lib/shoryuken/environment_loader.rb +22 -4
  26. data/lib/shoryuken/extensions/active_job_adapter.rb +30 -20
  27. data/lib/shoryuken/extensions/active_job_extensions.rb +38 -0
  28. data/lib/shoryuken/launcher.rb +26 -3
  29. data/lib/shoryuken/logging.rb +2 -2
  30. data/lib/shoryuken/manager.rb +50 -14
  31. data/lib/shoryuken/message.rb +11 -28
  32. data/lib/shoryuken/options.rb +6 -3
  33. data/lib/shoryuken/polling/base.rb +2 -0
  34. data/lib/shoryuken/polling/strict_priority.rb +8 -0
  35. data/lib/shoryuken/polling/weighted_round_robin.rb +9 -0
  36. data/lib/shoryuken/processor.rb +1 -2
  37. data/lib/shoryuken/queue.rb +5 -3
  38. data/lib/shoryuken/runner.rb +4 -3
  39. data/lib/shoryuken/version.rb +1 -1
  40. data/lib/shoryuken.rb +8 -0
  41. data/shoryuken.gemspec +1 -2
  42. data/spec/integration/launcher_spec.rb +29 -2
  43. data/spec/shared_examples_for_active_job.rb +230 -11
  44. data/spec/shoryuken/default_exception_handler_spec.rb +71 -0
  45. data/spec/shoryuken/environment_loader_spec.rb +62 -9
  46. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +1 -1
  47. data/spec/shoryuken/extensions/active_job_base_spec.rb +84 -0
  48. data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +4 -0
  49. data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +20 -0
  50. data/spec/shoryuken/fetcher_spec.rb +12 -12
  51. data/spec/shoryuken/launcher_spec.rb +105 -0
  52. data/spec/shoryuken/manager_spec.rb +61 -1
  53. data/spec/shoryuken/polling/strict_priority_spec.rb +10 -0
  54. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +35 -0
  55. data/spec/shoryuken/queue_spec.rb +10 -5
  56. data/spec/shoryuken/worker/default_executor_spec.rb +48 -48
  57. data/spec/shoryuken_spec.rb +9 -0
  58. data/spec/spec_helper.rb +7 -9
  59. metadata +32 -24
  60. data/.travis.yml +0 -30
  61. data/Gemfile.aws-sdk-core-v2 +0 -13
@@ -0,0 +1,21 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ group :test do
6
+ gem "activejob", "~> 6.0"
7
+ gem "aws-sdk-core", "~> 3"
8
+ gem "aws-sdk-sqs"
9
+ gem "codeclimate-test-reporter", require: nil
10
+ gem "httparty"
11
+ gem "multi_xml"
12
+ gem "simplecov"
13
+ end
14
+
15
+ group :development do
16
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
17
+ gem "pry-byebug", "3.9.0"
18
+ gem "rubocop"
19
+ end
20
+
21
+ gemspec path: "../"
@@ -0,0 +1,21 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ group :test do
6
+ gem "activejob", "~> 6.1"
7
+ gem "aws-sdk-core", "~> 3"
8
+ gem "aws-sdk-sqs"
9
+ gem "codeclimate-test-reporter", require: nil
10
+ gem "httparty"
11
+ gem "multi_xml"
12
+ gem "simplecov"
13
+ end
14
+
15
+ group :development do
16
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
17
+ gem "pry-byebug", "3.9.0"
18
+ gem "rubocop"
19
+ end
20
+
21
+ gemspec path: "../"
@@ -0,0 +1,22 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ group :test do
6
+ gem "activejob", "~> 7.0"
7
+ gem "aws-sdk-core", "~> 3"
8
+ gem "aws-sdk-sqs"
9
+ gem "codeclimate-test-reporter", require: nil
10
+ gem "httparty"
11
+ gem "multi_xml"
12
+ gem "simplecov"
13
+ end
14
+
15
+ group :development do
16
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
17
+ gem "rubocop"
18
+ gem "pry", ">= 0.14.2"
19
+ gem "pry-byebug", ">= 3.10.1"
20
+ end
21
+
22
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ module Shoryuken
2
+ class DefaultExceptionHandler
3
+ extend Util
4
+
5
+ def self.call(exception, _queue, _sqs_msg)
6
+ logger.error { "Processor failed: #{exception.message}" }
7
+ logger.error { exception.backtrace.join("\n") } if exception.backtrace
8
+ end
9
+ end
10
+ end
@@ -18,12 +18,12 @@ module Shoryuken
18
18
  end
19
19
 
20
20
  def setup_options
21
+ initialize_rails if load_rails?
21
22
  initialize_options
22
23
  initialize_logger
23
24
  end
24
25
 
25
26
  def load
26
- load_rails if Shoryuken.options[:rails]
27
27
  prefix_active_job_queue_names
28
28
  parse_queues
29
29
  require_workers
@@ -55,7 +55,7 @@ module Shoryuken
55
55
  Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
56
56
  end
57
57
 
58
- def load_rails
58
+ def initialize_rails
59
59
  # Adapted from: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
60
60
 
61
61
  require 'rails'
@@ -70,12 +70,22 @@ module Shoryuken
70
70
  ::Rails.application.config.eager_load = true
71
71
  end
72
72
  end
73
- require 'shoryuken/extensions/active_job_adapter' if Shoryuken.active_job?
73
+ if Shoryuken.active_job?
74
+ require 'shoryuken/extensions/active_job_extensions'
75
+ require 'shoryuken/extensions/active_job_adapter'
76
+ require 'shoryuken/extensions/active_job_concurrent_send_adapter'
77
+ end
74
78
  require File.expand_path('config/environment.rb')
75
79
  end
76
80
  end
77
81
 
82
+ def load_rails?
83
+ options[:rails]
84
+ end
85
+
78
86
  def prefix_active_job_queue_name(queue_name, weight)
87
+ return [queue_name, weight] if queue_name.start_with?('https://', 'arn:')
88
+
79
89
  queue_name_prefix = ::ActiveJob::Base.queue_name_prefix
80
90
  queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
81
91
 
@@ -153,9 +163,17 @@ module Shoryuken
153
163
 
154
164
  return if non_existent_queues.none?
155
165
 
166
+ # NOTE: HEREDOC's ~ operator removes indents, but is only available Ruby 2.3+
167
+ # See github PR: https://github.com/ruby-shoryuken/shoryuken/pull/691#issuecomment-1007653595
168
+ error_msg = <<-MSG.gsub(/^\s+/, '')
169
+ The specified queue(s) #{non_existent_queues.join(', ')} do not exist.
170
+ Try 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings.
171
+ It's also possible that you don't have permission to access the specified queues.
172
+ MSG
173
+
156
174
  fail(
157
175
  ArgumentError,
158
- "The specified queue(s) #{non_existent_queues.join(', ')} do not exist.\nTry 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings"
176
+ error_msg
159
177
  )
160
178
  end
161
179
 
@@ -33,8 +33,12 @@ module ActiveJob
33
33
  def enqueue(job, options = {}) #:nodoc:
34
34
  register_worker!(job)
35
35
 
36
+ job.sqs_send_message_parameters.merge! options
37
+
36
38
  queue = Shoryuken::Client.queues(job.queue_name)
37
- queue.send_message(message(queue, job, options))
39
+ send_message_params = message queue, job
40
+ job.sqs_send_message_parameters = send_message_params
41
+ queue.send_message send_message_params
38
42
  end
39
43
 
40
44
  def enqueue_at(job, timestamp) #:nodoc:
@@ -50,44 +54,50 @@ module ActiveJob
50
54
  delay
51
55
  end
52
56
 
53
- def message(queue, job, options = {})
57
+ def message(queue, job)
54
58
  body = job.serialize
59
+ job_params = job.sqs_send_message_parameters
60
+
61
+ attributes = job_params[:message_attributes] || {}
55
62
 
56
- msg = {}
63
+ msg = {
64
+ message_body: body,
65
+ message_attributes: attributes.merge(MESSAGE_ATTRIBUTES)
66
+ }
57
67
 
58
68
  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')))
69
+ # See https://github.com/ruby-shoryuken/shoryuken/issues/457 and
70
+ # https://github.com/ruby-shoryuken/shoryuken/pull/750#issuecomment-1781317929
71
+ msg[:message_deduplication_id] = Digest::SHA256.hexdigest(
72
+ JSON.dump(body.except('job_id', 'enqueued_at'))
73
+ )
61
74
  end
62
75
 
63
- msg[:message_body] = body
64
- msg[:message_attributes] = message_attributes
65
-
66
- msg.merge(options)
76
+ msg.merge(job_params.except(:message_attributes))
67
77
  end
68
78
 
69
79
  def register_worker!(job)
70
80
  Shoryuken.register_worker(job.queue_name, JobWrapper)
71
81
  end
72
82
 
73
- def message_attributes
74
- @message_attributes ||= {
75
- 'shoryuken_class' => {
76
- string_value: JobWrapper.to_s,
77
- data_type: 'String'
78
- }
79
- }
80
- end
81
-
82
83
  class JobWrapper #:nodoc:
83
84
  include Shoryuken::Worker
84
85
 
85
86
  shoryuken_options body_parser: :json, auto_delete: true
86
87
 
87
- def perform(_sqs_msg, hash)
88
- Base.execute hash
88
+ def perform(sqs_msg, hash)
89
+ receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
90
+ past_receives = receive_count - 1
91
+ Base.execute hash.merge({ 'executions' => past_receives })
89
92
  end
90
93
  end
94
+
95
+ MESSAGE_ATTRIBUTES = {
96
+ 'shoryuken_class' => {
97
+ string_value: JobWrapper.to_s,
98
+ data_type: 'String'
99
+ }
100
+ }.freeze
91
101
  end
92
102
  end
93
103
  end
@@ -0,0 +1,38 @@
1
+ module Shoryuken
2
+ module ActiveJobExtensions
3
+ # Adds an accessor for SQS SendMessage parameters on ActiveJob jobs
4
+ # (instances of ActiveJob::Base). Shoryuken ActiveJob queue adapters use
5
+ # these parameters when enqueueing jobs; other adapters can ignore them.
6
+ module SQSSendMessageParametersAccessor
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ attr_accessor :sqs_send_message_parameters
11
+ end
12
+ end
13
+
14
+ # Initializes SQS SendMessage parameters on instances of ActiveJob::Base
15
+ # to the empty hash, and populates it whenever `#enqueue` is called, such
16
+ # as when using ActiveJob::Base.set.
17
+ module SQSSendMessageParametersSupport
18
+ def initialize(*arguments)
19
+ super(*arguments)
20
+ self.sqs_send_message_parameters = {}
21
+ end
22
+ ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
23
+
24
+ def enqueue(options = {})
25
+ sqs_options = options.extract! :message_attributes,
26
+ :message_system_attributes,
27
+ :message_deduplication_id,
28
+ :message_group_id
29
+ sqs_send_message_parameters.merge! sqs_options
30
+
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ ActiveJob::Base.include Shoryuken::ActiveJobExtensions::SQSSendMessageParametersAccessor
38
+ ActiveJob::Base.prepend Shoryuken::ActiveJobExtensions::SQSSendMessageParametersSupport
@@ -16,11 +16,13 @@ module Shoryuken
16
16
  def stop!
17
17
  initiate_stop
18
18
 
19
- executor.shutdown
19
+ # Don't await here so the timeout below is not delayed
20
+ stop_new_dispatching
20
21
 
21
- return if executor.wait_for_termination(Shoryuken.options[:timeout])
22
+ executor.shutdown
23
+ executor.kill unless executor.wait_for_termination(Shoryuken.options[:timeout])
22
24
 
23
- executor.kill
25
+ fire_event(:stopped)
24
26
  end
25
27
 
26
28
  def stop
@@ -28,12 +30,32 @@ module Shoryuken
28
30
 
29
31
  initiate_stop
30
32
 
33
+ stop_new_dispatching
34
+ await_dispatching_in_progress
35
+
31
36
  executor.shutdown
32
37
  executor.wait_for_termination
38
+
39
+ fire_event(:stopped)
40
+ end
41
+
42
+ def healthy?
43
+ Shoryuken.groups.keys.all? do |group|
44
+ manager = @managers.find { |m| m.group == group }
45
+ manager && manager.running?
46
+ end
33
47
  end
34
48
 
35
49
  private
36
50
 
51
+ def stop_new_dispatching
52
+ @managers.each(&:stop_new_dispatching)
53
+ end
54
+
55
+ def await_dispatching_in_progress
56
+ @managers.each(&:await_dispatching_in_progress)
57
+ end
58
+
37
59
  def executor
38
60
  @_executor ||= Shoryuken.launcher_executor || Concurrent.global_io_executor
39
61
  end
@@ -71,6 +93,7 @@ module Shoryuken
71
93
  def create_managers
72
94
  Shoryuken.groups.map do |group, options|
73
95
  Shoryuken::Manager.new(
96
+ group,
74
97
  Shoryuken::Fetcher.new(group),
75
98
  Shoryuken.polling_strategy(group).new(options[:queues], Shoryuken.delay(group)),
76
99
  options[:concurrency],
@@ -30,11 +30,11 @@ module Shoryuken
30
30
  end
31
31
 
32
32
  def self.logger
33
- @logger || initialize_logger
33
+ @logger ||= initialize_logger
34
34
  end
35
35
 
36
36
  def self.logger=(log)
37
- @logger = (log ? log : Logger.new('/dev/null'))
37
+ @logger = (log || Logger.new('/dev/null'))
38
38
  end
39
39
  end
40
40
  end
@@ -6,27 +6,47 @@ 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, concurrency, executor)
10
- @fetcher = fetcher
11
- @polling_strategy = polling_strategy
12
- @max_processors = concurrency
13
- @busy_processors = Concurrent::AtomicFixnum.new(0)
14
- @executor = executor
15
- @running = Concurrent::AtomicBoolean.new(true)
9
+ attr_reader :group
10
+
11
+ def initialize(group, fetcher, polling_strategy, concurrency, executor)
12
+ @group = group
13
+ @fetcher = fetcher
14
+ @polling_strategy = polling_strategy
15
+ @max_processors = concurrency
16
+ @busy_processors = Concurrent::AtomicFixnum.new(0)
17
+ @executor = executor
18
+ @running = Concurrent::AtomicBoolean.new(true)
19
+ @stop_new_dispatching = Concurrent::AtomicBoolean.new(false)
20
+ @dispatching_release_signal = ::Queue.new
16
21
  end
17
22
 
18
23
  def start
24
+ fire_utilization_update_event
19
25
  dispatch_loop
20
26
  end
21
27
 
22
- private
28
+ def stop_new_dispatching
29
+ @stop_new_dispatching.make_true
30
+ end
31
+
32
+ def await_dispatching_in_progress
33
+ # There might still be a dispatching on-going, as the response from SQS could take some time
34
+ # We don't want to stop the process before processing incoming messages, as they would stay "in-flight" for some time on SQS
35
+ # We use a queue, as the dispatch_loop is running on another thread, and this is a efficient way of communicating between threads.
36
+ @dispatching_release_signal.pop
37
+ end
23
38
 
24
39
  def running?
25
40
  @running.true? && @executor.running?
26
41
  end
27
42
 
43
+ private
44
+
28
45
  def dispatch_loop
29
- return unless running?
46
+ if @stop_new_dispatching.true? || !running?
47
+ @dispatching_release_signal << 1
48
+ return
49
+ end
30
50
 
31
51
  @executor.post { dispatch }
32
52
  end
@@ -57,8 +77,15 @@ module Shoryuken
57
77
  @max_processors - busy
58
78
  end
59
79
 
60
- def processor_done
80
+ def processor_done(queue)
61
81
  @busy_processors.decrement
82
+ fire_utilization_update_event
83
+
84
+ client_queue = Shoryuken::Client.queues(queue)
85
+ return unless client_queue.fifo?
86
+ return unless @polling_strategy.respond_to?(:message_processed)
87
+
88
+ @polling_strategy.message_processed(queue)
62
89
  end
63
90
 
64
91
  def assign(queue_name, sqs_msg)
@@ -67,10 +94,12 @@ module Shoryuken
67
94
  logger.debug { "Assigning #{sqs_msg.message_id}" }
68
95
 
69
96
  @busy_processors.increment
97
+ fire_utilization_update_event
70
98
 
71
- Concurrent::Promise.execute(
72
- executor: @executor
73
- ) { Processor.process(queue_name, sqs_msg) }.then { processor_done }.rescue { processor_done }
99
+ Concurrent::Promise
100
+ .execute(executor: @executor) { Processor.process(queue_name, sqs_msg) }
101
+ .then { processor_done(queue_name) }
102
+ .rescue { processor_done(queue_name) }
74
103
  end
75
104
 
76
105
  def dispatch_batch(queue)
@@ -81,7 +110,6 @@ module Shoryuken
81
110
 
82
111
  def dispatch_single_messages(queue)
83
112
  messages = @fetcher.fetch(queue, ready)
84
-
85
113
  @polling_strategy.messages_found(queue.name, messages.size)
86
114
  messages.each { |message| assign(queue.name, message) }
87
115
  end
@@ -108,5 +136,13 @@ module Shoryuken
108
136
 
109
137
  @running.make_false
110
138
  end
139
+
140
+ def fire_utilization_update_event
141
+ fire_event :utilization_update, false, {
142
+ group: @group,
143
+ max_processors: @max_processors,
144
+ busy_processors: busy
145
+ }
146
+ end
111
147
  end
112
148
  end
@@ -1,5 +1,16 @@
1
1
  module Shoryuken
2
2
  class Message
3
+ extend Forwardable
4
+
5
+ def_delegators(:data,
6
+ :message_id,
7
+ :receipt_handle,
8
+ :md5_of_body,
9
+ :body,
10
+ :attributes,
11
+ :md5_of_message_attributes,
12
+ :message_attributes)
13
+
3
14
  attr_accessor :client, :queue_url, :queue_name, :data
4
15
 
5
16
  def initialize(client, queue, data)
@@ -29,33 +40,5 @@ module Shoryuken
29
40
  visibility_timeout: timeout
30
41
  )
31
42
  end
32
-
33
- def message_id
34
- data.message_id
35
- end
36
-
37
- def receipt_handle
38
- data.receipt_handle
39
- end
40
-
41
- def md5_of_body
42
- data.md5_of_body
43
- end
44
-
45
- def body
46
- data.body
47
- end
48
-
49
- def attributes
50
- data.attributes
51
- end
52
-
53
- def md5_of_message_attributes
54
- data.md5_of_message_attributes
55
- end
56
-
57
- def message_attributes
58
- data.message_attributes
59
- end
60
43
  end
61
44
  end
@@ -9,20 +9,23 @@ module Shoryuken
9
9
  lifecycle_events: {
10
10
  startup: [],
11
11
  dispatch: [],
12
+ utilization_update: [],
12
13
  quiet: [],
13
- shutdown: []
14
+ shutdown: [],
15
+ stopped: []
14
16
  }
15
17
  }.freeze
16
18
 
17
19
  attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout, :groups,
18
20
  :launcher_executor,
19
- :start_callback, :stop_callback, :worker_executor, :worker_registry
21
+ :start_callback, :stop_callback, :worker_executor, :worker_registry, :exception_handlers
20
22
  attr_writer :default_worker_options, :sqs_client
21
23
  attr_reader :sqs_client_receive_message_opts
22
24
 
23
25
  def initialize
24
26
  self.groups = {}
25
27
  self.worker_registry = DefaultWorkerRegistry.new
28
+ self.exception_handlers = [DefaultExceptionHandler]
26
29
  self.active_job_queue_name_prefixing = false
27
30
  self.worker_executor = Worker::DefaultExecutor
28
31
  self.cache_visibility_timeout = false
@@ -133,7 +136,7 @@ module Shoryuken
133
136
  end
134
137
 
135
138
  # Register a block to run at a point in the Shoryuken lifecycle.
136
- # :startup, :quiet or :shutdown are valid events.
139
+ # :startup, :quiet, :shutdown or :stopped are valid events.
137
140
  #
138
141
  # Shoryuken.configure_server do |config|
139
142
  # config.on(:shutdown) do
@@ -40,6 +40,8 @@ module Shoryuken
40
40
  fail NotImplementedError
41
41
  end
42
42
 
43
+ def message_processed(_queue); end
44
+
43
45
  def active_queues
44
46
  fail NotImplementedError
45
47
  end
@@ -38,6 +38,13 @@ module Shoryuken
38
38
  .reverse
39
39
  end
40
40
 
41
+ def message_processed(queue)
42
+ if queue_paused?(queue)
43
+ logger.debug "Unpausing #{queue}"
44
+ @paused_until[queue] = Time.at 0
45
+ end
46
+ end
47
+
41
48
  private
42
49
 
43
50
  def next_active_queue
@@ -70,6 +77,7 @@ module Shoryuken
70
77
 
71
78
  def pause(queue)
72
79
  return unless delay > 0
80
+
73
81
  @paused_until[queue] = Time.now + delay
74
82
  logger.debug "Paused #{queue}"
75
83
  end
@@ -35,10 +35,18 @@ module Shoryuken
35
35
  unparse_queues(@queues)
36
36
  end
37
37
 
38
+ def message_processed(queue)
39
+ paused_queue = @paused_queues.find { |_time, name| name == queue }
40
+ return unless paused_queue
41
+
42
+ paused_queue[0] = Time.at 0
43
+ end
44
+
38
45
  private
39
46
 
40
47
  def pause(queue)
41
48
  return unless @queues.delete(queue)
49
+
42
50
  @paused_queues << [Time.now + delay, queue]
43
51
  logger.debug "Paused #{queue}"
44
52
  end
@@ -46,6 +54,7 @@ module Shoryuken
46
54
  def unpause_queues
47
55
  return if @paused_queues.empty?
48
56
  return if Time.now < @paused_queues.first[0]
57
+
49
58
  pause = @paused_queues.shift
50
59
  @queues << pause[1]
51
60
  logger.debug "Unpaused #{pause[1]}"
@@ -22,8 +22,7 @@ module Shoryuken
22
22
  end
23
23
  end
24
24
  rescue Exception => ex
25
- logger.error { "Processor failed: #{ex.message}" }
26
- logger.error { ex.backtrace.join("\n") } unless ex.backtrace.nil?
25
+ Array(Shoryuken.exception_handlers).each { |handler| handler.call(ex, queue, sqs_msg) }
27
26
 
28
27
  raise
29
28
  end
@@ -21,9 +21,10 @@ module Shoryuken
21
21
  end
22
22
 
23
23
  def delete_messages(options)
24
- client.delete_message_batch(
24
+ failed_messages = client.delete_message_batch(
25
25
  options.merge(queue_url: url)
26
- ).failed.any? do |failure|
26
+ ).failed || []
27
+ failed_messages.any? do |failure|
27
28
  logger.error do
28
29
  "Could not delete #{failure.id}, code: '#{failure.code}', message: '#{failure.message}', sender_fault: #{failure.sender_fault}"
29
30
  end
@@ -43,7 +44,8 @@ module Shoryuken
43
44
  end
44
45
 
45
46
  def receive_messages(options)
46
- client.receive_message(options.merge(queue_url: url)).messages.map { |m| Message.new(client, self, m) }
47
+ messages = client.receive_message(options.merge(queue_url: url)).messages || []
48
+ messages.map { |m| Message.new(client, self, m) }
47
49
  end
48
50
 
49
51
  def fifo?
@@ -30,9 +30,6 @@ module Shoryuken
30
30
 
31
31
  loader = EnvironmentLoader.setup_options(options)
32
32
 
33
- # When cli args exist, override options in config file
34
- Shoryuken.options.merge!(options)
35
-
36
33
  daemonize(Shoryuken.options)
37
34
  write_pid(Shoryuken.options)
38
35
 
@@ -55,6 +52,10 @@ module Shoryuken
55
52
  end
56
53
  end
57
54
 
55
+ def healthy?
56
+ (@launcher && @launcher.healthy?) || false
57
+ end
58
+
58
59
  private
59
60
 
60
61
  def initialize_concurrent_logger
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '5.0.5'.freeze
2
+ VERSION = '6.1.1'.freeze
3
3
  end