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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +17 -0
- data/.devcontainer/base.Dockerfile +43 -0
- data/.devcontainer/devcontainer.json +35 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/specs.yml +65 -0
- data/.github/workflows/stale.yml +20 -0
- data/.gitignore +1 -1
- data/.reek.yml +5 -0
- data/.rubocop.yml +1 -1
- data/Appraisals +42 -0
- data/CHANGELOG.md +114 -0
- data/Gemfile +8 -3
- data/README.md +41 -1
- data/Rakefile +15 -1
- data/bin/cli/sqs.rb +51 -6
- data/gemfiles/.gitignore +1 -0
- data/gemfiles/aws_sdk_core_2.gemfile +21 -0
- data/gemfiles/rails_4_2.gemfile +20 -0
- data/gemfiles/rails_5_2.gemfile +21 -0
- data/gemfiles/rails_6_0.gemfile +21 -0
- data/gemfiles/rails_6_1.gemfile +21 -0
- data/gemfiles/rails_7_0.gemfile +22 -0
- data/lib/shoryuken/default_exception_handler.rb +10 -0
- data/lib/shoryuken/environment_loader.rb +22 -4
- data/lib/shoryuken/extensions/active_job_adapter.rb +30 -20
- data/lib/shoryuken/extensions/active_job_extensions.rb +38 -0
- data/lib/shoryuken/launcher.rb +26 -3
- data/lib/shoryuken/logging.rb +2 -2
- data/lib/shoryuken/manager.rb +50 -14
- data/lib/shoryuken/message.rb +11 -28
- data/lib/shoryuken/options.rb +6 -3
- data/lib/shoryuken/polling/base.rb +2 -0
- data/lib/shoryuken/polling/strict_priority.rb +8 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +9 -0
- data/lib/shoryuken/processor.rb +1 -2
- data/lib/shoryuken/queue.rb +5 -3
- data/lib/shoryuken/runner.rb +4 -3
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken.rb +8 -0
- data/shoryuken.gemspec +1 -2
- data/spec/integration/launcher_spec.rb +29 -2
- data/spec/shared_examples_for_active_job.rb +230 -11
- data/spec/shoryuken/default_exception_handler_spec.rb +71 -0
- data/spec/shoryuken/environment_loader_spec.rb +62 -9
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +1 -1
- data/spec/shoryuken/extensions/active_job_base_spec.rb +84 -0
- data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +4 -0
- data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +20 -0
- data/spec/shoryuken/fetcher_spec.rb +12 -12
- data/spec/shoryuken/launcher_spec.rb +105 -0
- data/spec/shoryuken/manager_spec.rb +61 -1
- data/spec/shoryuken/polling/strict_priority_spec.rb +10 -0
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +35 -0
- data/spec/shoryuken/queue_spec.rb +10 -5
- data/spec/shoryuken/worker/default_executor_spec.rb +48 -48
- data/spec/shoryuken_spec.rb +9 -0
- data/spec/spec_helper.rb +7 -9
- metadata +32 -24
- data/.travis.yml +0 -30
- 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  | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 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 | 
| 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/ | 
| 60 | 
            -
                       | 
| 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 | 
| 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( | 
| 88 | 
            -
                       | 
| 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
         | 
    
        data/lib/shoryuken/launcher.rb
    CHANGED
    
    | @@ -16,11 +16,13 @@ module Shoryuken | |
| 16 16 | 
             
                def stop!
         | 
| 17 17 | 
             
                  initiate_stop
         | 
| 18 18 |  | 
| 19 | 
            -
                   | 
| 19 | 
            +
                  # Don't await here so the timeout below is not delayed
         | 
| 20 | 
            +
                  stop_new_dispatching
         | 
| 20 21 |  | 
| 21 | 
            -
                   | 
| 22 | 
            +
                  executor.shutdown
         | 
| 23 | 
            +
                  executor.kill unless executor.wait_for_termination(Shoryuken.options[:timeout])
         | 
| 22 24 |  | 
| 23 | 
            -
                   | 
| 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],
         | 
    
        data/lib/shoryuken/logging.rb
    CHANGED
    
    | @@ -30,11 +30,11 @@ module Shoryuken | |
| 30 30 | 
             
                end
         | 
| 31 31 |  | 
| 32 32 | 
             
                def self.logger
         | 
| 33 | 
            -
                  @logger  | 
| 33 | 
            +
                  @logger ||= initialize_logger
         | 
| 34 34 | 
             
                end
         | 
| 35 35 |  | 
| 36 36 | 
             
                def self.logger=(log)
         | 
| 37 | 
            -
                  @logger = (log  | 
| 37 | 
            +
                  @logger = (log || Logger.new('/dev/null'))
         | 
| 38 38 | 
             
                end
         | 
| 39 39 | 
             
              end
         | 
| 40 40 | 
             
            end
         | 
    
        data/lib/shoryuken/manager.rb
    CHANGED
    
    | @@ -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 | 
            -
                 | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                  @ | 
| 13 | 
            -
                  @ | 
| 14 | 
            -
                  @ | 
| 15 | 
            -
                  @ | 
| 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 | 
            -
                 | 
| 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 | 
            -
                   | 
| 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 | 
| 72 | 
            -
                    executor: @executor
         | 
| 73 | 
            -
             | 
| 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
         | 
    
        data/lib/shoryuken/message.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/shoryuken/options.rb
    CHANGED
    
    | @@ -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 : | 
| 139 | 
            +
                # :startup, :quiet, :shutdown or :stopped are valid events.
         | 
| 137 140 | 
             
                #
         | 
| 138 141 | 
             
                #   Shoryuken.configure_server do |config|
         | 
| 139 142 | 
             
                #     config.on(:shutdown) do
         | 
| @@ -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]}"
         | 
    
        data/lib/shoryuken/processor.rb
    CHANGED
    
    | @@ -22,8 +22,7 @@ module Shoryuken | |
| 22 22 | 
             
                    end
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 | 
             
                rescue Exception => ex
         | 
| 25 | 
            -
                   | 
| 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
         | 
    
        data/lib/shoryuken/queue.rb
    CHANGED
    
    | @@ -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 | 
| 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 | 
| 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?
         | 
    
        data/lib/shoryuken/runner.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/shoryuken/version.rb
    CHANGED