sidekiq-grouping 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +37 -0
- data/.github/workflows/test.yml +49 -0
- data/.rubocop.yml +31 -3
- data/Appraisals +6 -2
- data/README.md +3 -1
- data/gemfiles/sidekiq_6.0.gemfile +7 -0
- data/gemfiles/sidekiq_6.5.gemfile +7 -0
- data/lefthook.yml +15 -0
- data/lib/sidekiq/grouping/batch.rb +29 -19
- data/lib/sidekiq/grouping/config.rb +32 -22
- data/lib/sidekiq/grouping/flusher.rb +48 -36
- data/lib/sidekiq/grouping/flusher_observer.rb +16 -10
- data/lib/sidekiq/grouping/middleware.rb +27 -14
- data/lib/sidekiq/grouping/redis.rb +20 -11
- data/lib/sidekiq/grouping/version.rb +3 -1
- data/lib/sidekiq/grouping/web.rb +10 -5
- data/lib/sidekiq/grouping.rb +35 -30
- data/sidekiq-grouping.gemspec +13 -7
- data/spec/modules/batch_spec.rb +110 -80
- data/spec/modules/redis_spec.rb +17 -16
- data/spec/spec_helper.rb +5 -3
- data/spec/support/test_workers.rb +7 -10
- metadata +55 -20
- data/.travis.yml +0 -18
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: be68edcca66201c059ee0a9d18c163cf899a00e1d5013a64ca72206dc9b48e9e
         | 
| 4 | 
            +
              data.tar.gz: 55bfb74ecd86ce73a4fedd115e8adfb1a8531ab3460e008a74ce6c4b9c111892
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 709bbcf1bf236c18e330adba5fd5c0c23a5cce73f869fcc152c3b846f306c28189c3942bb6bfde1875f1895e0a799e7367e1e4bddd4abb8c6147f16490101841
         | 
| 7 | 
            +
              data.tar.gz: d46cb5d01ccaaae7c52f7768e8f8159a8be287e58852f6c167d4ebfbf01e37da70640b40b4476c868fa01a4874d717bbb4c0cbe9108418d1180c4a2c9d95c3ce
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            name: Lint
         | 
| 3 | 
            +
            on:
         | 
| 4 | 
            +
              push:
         | 
| 5 | 
            +
                branches:
         | 
| 6 | 
            +
                  - master
         | 
| 7 | 
            +
              pull_request:
         | 
| 8 | 
            +
                branches:
         | 
| 9 | 
            +
                  - master
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            jobs:
         | 
| 12 | 
            +
              rubocop:
         | 
| 13 | 
            +
                name: Rubocop
         | 
| 14 | 
            +
                runs-on: ubuntu-latest
         | 
| 15 | 
            +
                steps:
         | 
| 16 | 
            +
                  - uses: actions/checkout@v3
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  - name: Set up Ruby ${{ matrix.ruby_version }}
         | 
| 19 | 
            +
                    uses: ruby/setup-ruby@v1
         | 
| 20 | 
            +
                    with:
         | 
| 21 | 
            +
                      bundler: 2
         | 
| 22 | 
            +
                      ruby-version: 2.7
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  - uses: actions/cache@v3
         | 
| 25 | 
            +
                    with:
         | 
| 26 | 
            +
                      path: vendor/bundle
         | 
| 27 | 
            +
                      key: ${{ runner.os }}-gems-${{ matrix.ruby-version }}-${{ hashFiles('./*.gemspec') }}
         | 
| 28 | 
            +
                      restore-keys: |
         | 
| 29 | 
            +
                        ${{ runner.os }}-gems-
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  - name: Bundle install
         | 
| 32 | 
            +
                    run: |
         | 
| 33 | 
            +
                      bundle config path vendor/bundle
         | 
| 34 | 
            +
                      bundle install --jobs 4 --retry 3
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  - name: Run Rubocop
         | 
| 37 | 
            +
                    run: bundle exec rubocop
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            name: Test
         | 
| 3 | 
            +
            on:
         | 
| 4 | 
            +
              push:
         | 
| 5 | 
            +
                branches:
         | 
| 6 | 
            +
                  - master
         | 
| 7 | 
            +
              pull_request:
         | 
| 8 | 
            +
                branches:
         | 
| 9 | 
            +
                  - master
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            jobs:
         | 
| 12 | 
            +
              rspec:
         | 
| 13 | 
            +
                name: Rspec
         | 
| 14 | 
            +
                runs-on: ubuntu-latest
         | 
| 15 | 
            +
                services:
         | 
| 16 | 
            +
                  redis:
         | 
| 17 | 
            +
                    image: redis:6
         | 
| 18 | 
            +
                    options: >-
         | 
| 19 | 
            +
                      --health-cmd "redis-cli ping"
         | 
| 20 | 
            +
                      --health-interval 10s
         | 
| 21 | 
            +
                      --health-timeout 5s
         | 
| 22 | 
            +
                      --health-retries 5
         | 
| 23 | 
            +
                    ports:
         | 
| 24 | 
            +
                      - 6379:6379
         | 
| 25 | 
            +
                strategy:
         | 
| 26 | 
            +
                  matrix:
         | 
| 27 | 
            +
                    include:
         | 
| 28 | 
            +
                      - { ruby_version: 2.7 }
         | 
| 29 | 
            +
                      - { ruby_version: 3.0 }
         | 
| 30 | 
            +
                      - { ruby_version: 3.1 }
         | 
| 31 | 
            +
                steps:
         | 
| 32 | 
            +
                  - uses: actions/checkout@v3
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  - name: Set up Ruby ${{ matrix.ruby_version }}
         | 
| 35 | 
            +
                    uses: ruby/setup-ruby@v1
         | 
| 36 | 
            +
                    with:
         | 
| 37 | 
            +
                      bundler: 2
         | 
| 38 | 
            +
                      ruby-version: ${{ matrix.ruby_version }}
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  # Appraisal doesn't support vendored install
         | 
| 41 | 
            +
                  # See: https://github.com/thoughtbot/appraisal/issues/173
         | 
| 42 | 
            +
                  #      https://github.com/thoughtbot/appraisal/pull/174
         | 
| 43 | 
            +
                  - name: Bundle install
         | 
| 44 | 
            +
                    run: |
         | 
| 45 | 
            +
                      bundle install --jobs 4 --retry 3
         | 
| 46 | 
            +
                      bundle exec appraisal install
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  - name: Run tests
         | 
| 49 | 
            +
                    run: bundle exec appraisal rspec
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -1,8 +1,15 @@ | |
| 1 | 
            +
            ---
         | 
| 1 2 | 
             
            require: rubocop-rspec
         | 
| 2 3 |  | 
| 3 4 | 
             
            AllCops:
         | 
| 5 | 
            +
              TargetRubyVersion: 2.7.0
         | 
| 6 | 
            +
              SuggestExtensions: false
         | 
| 7 | 
            +
              NewCops: enable
         | 
| 4 8 | 
             
              Include:
         | 
| 5 9 | 
             
                - ./Gemfile
         | 
| 10 | 
            +
                - ./Rakefile
         | 
| 11 | 
            +
                - '*.gemspec'
         | 
| 12 | 
            +
                - '**/*.rb'
         | 
| 6 13 |  | 
| 7 14 | 
             
            Documentation:
         | 
| 8 15 | 
             
              Enabled: false
         | 
| @@ -10,8 +17,29 @@ Documentation: | |
| 10 17 | 
             
            Style/StringLiterals:
         | 
| 11 18 | 
             
              EnforcedStyle: double_quotes
         | 
| 12 19 |  | 
| 13 | 
            -
            Style/ClassAndModuleChildren:
         | 
| 14 | 
            -
              EnforcedStyle: compact
         | 
| 15 | 
            -
             | 
| 16 20 | 
             
            RSpec/FilePath:
         | 
| 17 21 | 
             
              Enabled: false
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            RSpec/ExampleLength:
         | 
| 24 | 
            +
              Enabled: false
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            RSpec/AnyInstance:
         | 
| 27 | 
            +
              Enabled: false
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            Metrics/MethodLength:
         | 
| 30 | 
            +
              Max: 15
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            Metrics/ClassLength:
         | 
| 33 | 
            +
              Max: 150
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            Layout/LineLength:
         | 
| 36 | 
            +
              Max: 80
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Layout/FirstArgumentIndentation:
         | 
| 39 | 
            +
              EnforcedStyle: consistent
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            Layout/FirstMethodArgumentLineBreak:
         | 
| 42 | 
            +
              Enabled: true
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            Layout/MultilineMethodArgumentLineBreaks:
         | 
| 45 | 
            +
              Enabled: true
         | 
    
        data/Appraisals
    CHANGED
    
    | @@ -14,6 +14,10 @@ appraise 'sidekiq-5.0' do | |
| 14 14 | 
             
              gem 'sidekiq', '~> 5.0.0'
         | 
| 15 15 | 
             
            end
         | 
| 16 16 |  | 
| 17 | 
            -
            appraise 'sidekiq- | 
| 18 | 
            -
              gem 'sidekiq',  | 
| 17 | 
            +
            appraise 'sidekiq-6.0' do
         | 
| 18 | 
            +
              gem 'sidekiq', '~> 6.0.0'
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            appraise 'sidekiq-6.5' do
         | 
| 22 | 
            +
              gem 'sidekiq', '~> 6.5.0'
         | 
| 19 23 | 
             
            end
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            [](https://rubygems.org/gems/sidekiq-grouping)
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            # Sidekiq::Grouping
         | 
| 2 4 |  | 
| 3 5 | 
             
            <a href="https://evilmartians.com/?utm_source=sidekiq-grouping-gem">
         | 
| @@ -23,7 +25,7 @@ class ElasticBulkIndexWorker | |
| 23 25 | 
             
              include Sidekiq::Worker
         | 
| 24 26 |  | 
| 25 27 | 
             
              sidekiq_options(
         | 
| 26 | 
            -
                queue: : | 
| 28 | 
            +
                queue: :elastic_bulks,
         | 
| 27 29 | 
             
                batch_flush_size: 30,     # Jobs will be combined when queue size exceeds 30
         | 
| 28 30 | 
             
                batch_flush_interval: 60, # Jobs will be combined every 60 seconds
         | 
| 29 31 | 
             
                retry: 5
         | 
    
        data/lefthook.yml
    ADDED
    
    | @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            # Git hooks configuration
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # See: github.com/evilmartians/lefthook
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            pre-commit:
         | 
| 7 | 
            +
              commands:
         | 
| 8 | 
            +
                rubocop:
         | 
| 9 | 
            +
                  glob: "*.rb"
         | 
| 10 | 
            +
                  run: bundle exec rubocop -A {staged_files} && git add {staged_files}
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            pre-push:
         | 
| 13 | 
            +
              commands:
         | 
| 14 | 
            +
                rspec:
         | 
| 15 | 
            +
                  run: bundle exec appraisal rspec
         | 
| @@ -1,7 +1,9 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Sidekiq
         | 
| 2 4 | 
             
              module Grouping
         | 
| 3 5 | 
             
                class Batch
         | 
| 4 | 
            -
                  def initialize(worker_class, queue,  | 
| 6 | 
            +
                  def initialize(worker_class, queue, _redis_pool = nil)
         | 
| 5 7 | 
             
                    @worker_class = worker_class
         | 
| 6 8 | 
             
                    @queue = queue
         | 
| 7 9 | 
             
                    @name = "#{worker_class.underscore}:#{queue}"
         | 
| @@ -12,11 +14,18 @@ module Sidekiq | |
| 12 14 |  | 
| 13 15 | 
             
                  def add(msg)
         | 
| 14 16 | 
             
                    msg = msg.to_json
         | 
| 15 | 
            -
                     | 
| 17 | 
            +
                    return unless should_add? msg
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    @redis.push_msg(
         | 
| 20 | 
            +
                      @name,
         | 
| 21 | 
            +
                      msg,
         | 
| 22 | 
            +
                      remember_unique: enqueue_similar_once?
         | 
| 23 | 
            +
                    )
         | 
| 16 24 | 
             
                  end
         | 
| 17 25 |  | 
| 18 | 
            -
                  def should_add? | 
| 26 | 
            +
                  def should_add?(msg)
         | 
| 19 27 | 
             
                    return true unless enqueue_similar_once?
         | 
| 28 | 
            +
             | 
| 20 29 | 
             
                    !@redis.enqueued?(@name, msg)
         | 
| 21 30 | 
             
                  end
         | 
| 22 31 |  | 
| @@ -25,19 +34,19 @@ module Sidekiq | |
| 25 34 | 
             
                  end
         | 
| 26 35 |  | 
| 27 36 | 
             
                  def chunk_size
         | 
| 28 | 
            -
                    worker_class_options[ | 
| 37 | 
            +
                    worker_class_options["batch_size"] ||
         | 
| 29 38 | 
             
                      Sidekiq::Grouping::Config.max_batch_size
         | 
| 30 39 | 
             
                  end
         | 
| 31 40 |  | 
| 32 41 | 
             
                  def pluck_size
         | 
| 33 | 
            -
                    worker_class_options[ | 
| 42 | 
            +
                    worker_class_options["batch_flush_size"] ||
         | 
| 34 43 | 
             
                      chunk_size
         | 
| 35 44 | 
             
                  end
         | 
| 36 45 |  | 
| 37 46 | 
             
                  def pluck
         | 
| 38 | 
            -
                     | 
| 39 | 
            -
             | 
| 40 | 
            -
                     | 
| 47 | 
            +
                    return unless @redis.lock(@name)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    @redis.pluck(@name, pluck_size).map { |value| JSON.parse(value) }
         | 
| 41 50 | 
             
                  end
         | 
| 42 51 |  | 
| 43 52 | 
             
                  def flush
         | 
| @@ -46,9 +55,9 @@ module Sidekiq | |
| 46 55 |  | 
| 47 56 | 
             
                    chunk.each_slice(chunk_size) do |subchunk|
         | 
| 48 57 | 
             
                      Sidekiq::Client.push(
         | 
| 49 | 
            -
                         | 
| 50 | 
            -
                         | 
| 51 | 
            -
                         | 
| 58 | 
            +
                        "class" => @worker_class,
         | 
| 59 | 
            +
                        "queue" => @queue,
         | 
| 60 | 
            +
                        "args" => [true, subchunk]
         | 
| 52 61 | 
             
                      )
         | 
| 53 62 | 
             
                    end
         | 
| 54 63 | 
             
                    set_current_time_as_last
         | 
| @@ -74,10 +83,11 @@ module Sidekiq | |
| 74 83 | 
             
                  end
         | 
| 75 84 |  | 
| 76 85 | 
             
                  def next_execution_time
         | 
| 77 | 
            -
                     | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
                     | 
| 86 | 
            +
                    interval = worker_class_options["batch_flush_interval"]
         | 
| 87 | 
            +
                    return unless interval
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    last_time = last_execution_time
         | 
| 90 | 
            +
                    last_time + interval.seconds if last_time
         | 
| 81 91 | 
             
                  end
         | 
| 82 92 |  | 
| 83 93 | 
             
                  def delete
         | 
| @@ -99,13 +109,13 @@ module Sidekiq | |
| 99 109 | 
             
                    if last_time.blank?
         | 
| 100 110 | 
             
                      set_current_time_as_last
         | 
| 101 111 | 
             
                      false
         | 
| 102 | 
            -
                     | 
| 103 | 
            -
                      next_time < Time.now | 
| 112 | 
            +
                    elsif next_time
         | 
| 113 | 
            +
                      next_time < Time.now
         | 
| 104 114 | 
             
                    end
         | 
| 105 115 | 
             
                  end
         | 
| 106 116 |  | 
| 107 117 | 
             
                  def enqueue_similar_once?
         | 
| 108 | 
            -
                    worker_class_options[ | 
| 118 | 
            +
                    worker_class_options["batch_unique"] == true
         | 
| 109 119 | 
             
                  end
         | 
| 110 120 |  | 
| 111 121 | 
             
                  def set_current_time_as_last
         | 
| @@ -122,7 +132,7 @@ module Sidekiq | |
| 122 132 | 
             
                    end
         | 
| 123 133 |  | 
| 124 134 | 
             
                    def extract_worker_klass_and_queue(name)
         | 
| 125 | 
            -
                      klass, queue = name.split( | 
| 135 | 
            +
                      klass, queue = name.split(":")
         | 
| 126 136 | 
             
                      [klass.camelize, queue]
         | 
| 127 137 | 
             
                    end
         | 
| 128 138 | 
             
                  end
         | 
| @@ -1,29 +1,39 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
              include ActiveSupport::Configurable
         | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 3 2 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 3 | 
            +
            module Sidekiq
         | 
| 4 | 
            +
              module Grouping
         | 
| 5 | 
            +
                module Config
         | 
| 6 | 
            +
                  include ActiveSupport::Configurable
         | 
| 7 7 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 8 | 
            +
                  def self.options
         | 
| 9 | 
            +
                    if Sidekiq.respond_to?(:[])
         | 
| 10 | 
            +
                      Sidekiq[:grouping] || Sidekiq["grouping"] || {}
         | 
| 11 | 
            +
                    else
         | 
| 12 | 
            +
                      Sidekiq.options[:grouping] || Sidekiq.options["grouping"] || {}
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 12 15 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 16 | 
            +
                  # Queue size overflow check polling interval
         | 
| 17 | 
            +
                  config_accessor :poll_interval do
         | 
| 18 | 
            +
                    options[:poll_interval] || 3
         | 
| 19 | 
            +
                  end
         | 
| 17 20 |  | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 21 | 
            +
                  # Maximum batch size
         | 
| 22 | 
            +
                  config_accessor :max_batch_size do
         | 
| 23 | 
            +
                    options[:max_batch_size] || 1000
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # Batch queue flush lock timeout
         | 
| 27 | 
            +
                  config_accessor :lock_ttl do
         | 
| 28 | 
            +
                    options[:lock_ttl] || 1
         | 
| 29 | 
            +
                  end
         | 
| 22 30 |  | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 31 | 
            +
                  # Option to override how Sidekiq::Grouping know about tests env
         | 
| 32 | 
            +
                  config_accessor :tests_env do
         | 
| 33 | 
            +
                    options[:tests_env] || (
         | 
| 34 | 
            +
                      defined?(::Rails) && Rails.respond_to?(:env) && Rails.env.test?
         | 
| 35 | 
            +
                    )
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 28 38 | 
             
              end
         | 
| 29 39 | 
             
            end
         | 
| @@ -1,42 +1,54 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
              def flush
         | 
| 3 | 
            -
                batches = Sidekiq::Grouping::Batch.all.map do |batch|
         | 
| 4 | 
            -
                  batch if batch.could_flush?
         | 
| 5 | 
            -
                end
         | 
| 6 | 
            -
                flush_batches(batches)
         | 
| 7 | 
            -
              end
         | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 8 2 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                     | 
| 16 | 
            -
                     | 
| 17 | 
            -
             | 
| 18 | 
            -
                    "change 'tests_env' to cover this one"
         | 
| 19 | 
            -
                  ].join)
         | 
| 20 | 
            -
                  Sidekiq::Grouping.logger.warn(
         | 
| 21 | 
            -
                    "**************************************************"
         | 
| 22 | 
            -
                  )
         | 
| 23 | 
            -
                end
         | 
| 24 | 
            -
                flush_batches(Sidekiq::Grouping::Batch.all)
         | 
| 25 | 
            -
              end
         | 
| 3 | 
            +
            module Sidekiq
         | 
| 4 | 
            +
              module Grouping
         | 
| 5 | 
            +
                class Flusher
         | 
| 6 | 
            +
                  def flush
         | 
| 7 | 
            +
                    batches = Sidekiq::Grouping::Batch.all.map do |batch|
         | 
| 8 | 
            +
                      batch if batch.could_flush?
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
                    flush_batches(batches)
         | 
| 11 | 
            +
                  end
         | 
| 26 12 |  | 
| 27 | 
            -
             | 
| 13 | 
            +
                  def force_flush_for_test!
         | 
| 14 | 
            +
                    unless Sidekiq::Grouping::Config.tests_env
         | 
| 15 | 
            +
                      Sidekiq::Grouping.logger.warn(
         | 
| 16 | 
            +
                        "**************************************************"
         | 
| 17 | 
            +
                      )
         | 
| 18 | 
            +
                      Sidekiq::Grouping.logger.warn(
         | 
| 19 | 
            +
                        "⛔️ force_flush_for_test! for testing API, " \
         | 
| 20 | 
            +
                        "but this is not the test environment. " \
         | 
| 21 | 
            +
                        "Please check your environment or " \
         | 
| 22 | 
            +
                        "change 'tests_env' to cover this one"
         | 
| 23 | 
            +
                      )
         | 
| 24 | 
            +
                      Sidekiq::Grouping.logger.warn(
         | 
| 25 | 
            +
                        "**************************************************"
         | 
| 26 | 
            +
                      )
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                    flush_batches(Sidekiq::Grouping::Batch.all)
         | 
| 29 | 
            +
                  end
         | 
| 28 30 |  | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 31 | 
            +
                  private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def flush_batches(batches)
         | 
| 34 | 
            +
                    batches.compact!
         | 
| 35 | 
            +
                    flush_concrete(batches)
         | 
| 36 | 
            +
                  end
         | 
| 33 37 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 38 | 
            +
                  def flush_concrete(batches)
         | 
| 39 | 
            +
                    return if batches.empty?
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    names = batches.map do |batch|
         | 
| 42 | 
            +
                      "#{batch.worker_class} in #{batch.queue}"
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                    unless Sidekiq::Grouping::Config.tests_env
         | 
| 45 | 
            +
                      Sidekiq::Grouping.logger.info(
         | 
| 46 | 
            +
                        "[Sidekiq::Grouping] Trying to flush batched queues: " \
         | 
| 47 | 
            +
                        "#{names.join(',')}"
         | 
| 48 | 
            +
                      )
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                    batches.each(&:flush)
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 41 53 | 
             
              end
         | 
| 42 54 | 
             
            end
         | 
| @@ -1,13 +1,19 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
                  )
         | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sidekiq
         | 
| 4 | 
            +
              module Grouping
         | 
| 5 | 
            +
                class FlusherObserver
         | 
| 6 | 
            +
                  def update(time, _result, exception)
         | 
| 7 | 
            +
                    if exception.is_a?(Concurrent::TimeoutError)
         | 
| 8 | 
            +
                      Sidekiq::Grouping.logger.error(
         | 
| 9 | 
            +
                        "[Sidekiq::Grouping] (#{time}) Execution timed out\n"
         | 
| 10 | 
            +
                      )
         | 
| 11 | 
            +
                    elsif exception.present?
         | 
| 12 | 
            +
                      Sidekiq::Grouping.logger.error(
         | 
| 13 | 
            +
                        "[Sidekiq::Grouping] Execution failed with error #{exception}\n"
         | 
| 14 | 
            +
                      )
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 11 17 | 
             
                end
         | 
| 12 18 | 
             
              end
         | 
| 13 19 | 
             
            end
         | 
| @@ -1,43 +1,56 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Sidekiq
         | 
| 2 4 | 
             
              module Grouping
         | 
| 3 5 | 
             
                class Middleware
         | 
| 6 | 
            +
                  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
         | 
| 4 7 | 
             
                  def call(worker_class, msg, queue, redis_pool = nil)
         | 
| 5 | 
            -
                     | 
| 6 | 
            -
             | 
| 7 | 
            -
                     | 
| 8 | 
            +
                    if worker_class.is_a?(String)
         | 
| 9 | 
            +
                      worker_class = worker_class.camelize.constantize
         | 
| 10 | 
            +
                    end
         | 
| 8 11 | 
             
                    options = worker_class.get_sidekiq_options
         | 
| 9 12 |  | 
| 10 13 | 
             
                    batch =
         | 
| 11 | 
            -
                      options.key?( | 
| 12 | 
            -
                      options.key?( | 
| 13 | 
            -
                      options.key?( | 
| 14 | 
            +
                      options.key?("batch_flush_size") ||
         | 
| 15 | 
            +
                      options.key?("batch_flush_interval") ||
         | 
| 16 | 
            +
                      options.key?("batch_size")
         | 
| 14 17 |  | 
| 15 18 | 
             
                    passthrough =
         | 
| 16 | 
            -
                      msg[ | 
| 17 | 
            -
                      msg[ | 
| 18 | 
            -
                      msg['args'].try(:first) == true
         | 
| 19 | 
            +
                      msg["args"].is_a?(Array) &&
         | 
| 20 | 
            +
                      msg["args"].try(:first) == true
         | 
| 19 21 |  | 
| 20 22 | 
             
                    retrying = msg["failed_at"].present?
         | 
| 21 23 |  | 
| 22 24 | 
             
                    return yield unless batch
         | 
| 23 25 |  | 
| 24 | 
            -
                    if  | 
| 25 | 
            -
                       | 
| 26 | 
            -
             | 
| 27 | 
            -
                       | 
| 26 | 
            +
                    if inline_mode?
         | 
| 27 | 
            +
                      wrapped_args = [[msg["args"]]]
         | 
| 28 | 
            +
                      msg["args"] = wrapped_args
         | 
| 29 | 
            +
                      return yield
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    if passthrough || retrying
         | 
| 33 | 
            +
                      msg["args"].shift if passthrough
         | 
| 28 34 | 
             
                      yield
         | 
| 35 | 
            +
                    else
         | 
| 36 | 
            +
                      add_to_batch(worker_class, queue, msg, redis_pool)
         | 
| 29 37 | 
             
                    end
         | 
| 30 38 | 
             
                  end
         | 
| 39 | 
            +
                  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
         | 
| 31 40 |  | 
| 32 41 | 
             
                  private
         | 
| 33 42 |  | 
| 34 43 | 
             
                  def add_to_batch(worker_class, queue, msg, redis_pool = nil)
         | 
| 35 44 | 
             
                    Sidekiq::Grouping::Batch
         | 
| 36 45 | 
             
                      .new(worker_class.name, queue, redis_pool)
         | 
| 37 | 
            -
                      .add(msg[ | 
| 46 | 
            +
                      .add(msg["args"])
         | 
| 38 47 |  | 
| 39 48 | 
             
                    nil
         | 
| 40 49 | 
             
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def inline_mode?
         | 
| 52 | 
            +
                    defined?(Sidekiq::Testing) && Sidekiq::Testing.inline?
         | 
| 53 | 
            +
                  end
         | 
| 41 54 | 
             
                end
         | 
| 42 55 | 
             
              end
         | 
| 43 56 | 
             
            end
         | 
| @@ -1,22 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Sidekiq
         | 
| 2 4 | 
             
              module Grouping
         | 
| 3 5 | 
             
                class Redis
         | 
| 4 | 
            -
             | 
| 5 6 | 
             
                  PLUCK_SCRIPT = <<-SCRIPT
         | 
| 6 | 
            -
                    local pluck_values = redis.call(' | 
| 7 | 
            -
                     | 
| 8 | 
            -
             | 
| 9 | 
            -
                      redis.call('srem', KEYS[2], v)
         | 
| 7 | 
            +
                    local pluck_values = redis.call('lpop', KEYS[1], ARGV[1]) or {}
         | 
| 8 | 
            +
                    if #pluck_values > 0 then
         | 
| 9 | 
            +
                      redis.call('srem', KEYS[2], unpack(pluck_values))
         | 
| 10 10 | 
             
                    end
         | 
| 11 11 | 
             
                    return pluck_values
         | 
| 12 12 | 
             
                  SCRIPT
         | 
| 13 13 |  | 
| 14 | 
            -
                  def push_msg(name, msg, remember_unique  | 
| 14 | 
            +
                  def push_msg(name, msg, remember_unique: false)
         | 
| 15 15 | 
             
                    redis do |conn|
         | 
| 16 16 | 
             
                      conn.multi do |pipeline|
         | 
| 17 | 
            -
                        pipeline.sadd | 
| 17 | 
            +
                        sadd = pipeline.respond_to?(:sadd?) ? :sadd? : :sadd
         | 
| 18 | 
            +
                        pipeline.public_send(sadd, ns("batches"), name)
         | 
| 18 19 | 
             
                        pipeline.rpush(ns(name), msg)
         | 
| 19 | 
            -
                         | 
| 20 | 
            +
                        if remember_unique
         | 
| 21 | 
            +
                          pipeline.public_send(
         | 
| 22 | 
            +
                            sadd,
         | 
| 23 | 
            +
                            unique_messages_key(name),
         | 
| 24 | 
            +
                            msg
         | 
| 25 | 
            +
                          )
         | 
| 26 | 
            +
                        end
         | 
| 20 27 | 
             
                      end
         | 
| 21 28 | 
             
                    end
         | 
| 22 29 | 
             
                  end
         | 
| @@ -46,7 +53,9 @@ module Sidekiq | |
| 46 53 | 
             
                  end
         | 
| 47 54 |  | 
| 48 55 | 
             
                  def set_last_execution_time(name, time)
         | 
| 49 | 
            -
                    redis  | 
| 56 | 
            +
                    redis do |conn|
         | 
| 57 | 
            +
                      conn.set(ns("last_execution_time:#{name}"), time.to_json)
         | 
| 58 | 
            +
                    end
         | 
| 50 59 | 
             
                  end
         | 
| 51 60 |  | 
| 52 61 | 
             
                  def lock(name)
         | 
| @@ -60,13 +69,13 @@ module Sidekiq | |
| 60 69 | 
             
                    redis do |conn|
         | 
| 61 70 | 
             
                      conn.del(ns("last_execution_time:#{name}"))
         | 
| 62 71 | 
             
                      conn.del(ns(name))
         | 
| 63 | 
            -
                      conn.srem(ns( | 
| 72 | 
            +
                      conn.srem(ns("batches"), name)
         | 
| 64 73 | 
             
                    end
         | 
| 65 74 | 
             
                  end
         | 
| 66 75 |  | 
| 67 76 | 
             
                  private
         | 
| 68 77 |  | 
| 69 | 
            -
                  def unique_messages_key | 
| 78 | 
            +
                  def unique_messages_key(name)
         | 
| 70 79 | 
             
                    ns("#{name}:unique_messages")
         | 
| 71 80 | 
             
                  end
         | 
| 72 81 |  |