shoryuken 5.0.5 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
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