sidekiq-ultimate 0.0.1.alpha.19 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f588d8dd927a0eaba4910ffbded5a8a24f69b6e8aae7c45b4bb28092373e6825
4
- data.tar.gz: e2701097c8fbc5c99054bdb36a3bb4ae1089ee91abd1da74dfab4cab94f12ef7
3
+ metadata.gz: 3fe29c4188e4bcc73a818ac756cc6fa0eaa97588c2373920d0dc9cf36ba8859e
4
+ data.tar.gz: bb650bdf494eab357b0d042243cb42d9e5c1a0914e70971576766a55c84508d7
5
5
  SHA512:
6
- metadata.gz: f5a37773ca7013b24a5e7abb15aab91f7667f157b7e5d4bf43085c8268546a50d51319d351fad4db478428ee437d4acd43f762b33212a96d0d5d3bea57ad7d77
7
- data.tar.gz: 7ceeb9436521f998f9f34e642f22aee1e8762e60c6ae9f5689b82902b95a27d6c1578aee89bd984ca81dca91fcfb4ef227eae170c9bfb1e854a1f17cd9ba08f6
6
+ metadata.gz: 9389d82c1071237e11955ea3ad62ada63c084d4a41d412c87ab8b034982dd0104a4ee4701dbf09e563322150234cc06c18150be65ea5d51aeec3a38b03a55964
7
+ data.tar.gz: 6a0d8801f2959479e77ec201d377dfac040eacf5a366237962622faf4b92e9696d3cfed40ee00f8b03893798da848e5b42bfa48af27e3493020c87bcb20c7821
@@ -0,0 +1,58 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+
6
+ # GitHub recommends pinning actions to a commit SHA.
7
+ # To get a newer version, you will need to update the SHA.
8
+ # You can also reference a tag or branch, but the action may change without warning.
9
+
10
+ name: Gem Rspec testing
11
+
12
+ on:
13
+ push:
14
+ branches: [ master ]
15
+ pull_request:
16
+ branches: [ master ]
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ rubocop:
23
+ name: Rubocop
24
+ runs-on: ubuntu-latest
25
+ env:
26
+ BUNDLE_WITHOUT: development
27
+ steps:
28
+ - uses: actions/checkout@v3
29
+ - uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: 2.7.6
32
+ bundler-cache: true
33
+ - run: bundle exec rubocop --config .rubocop.yml
34
+
35
+ test:
36
+ runs-on: ubuntu-latest
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ ruby-version: ['2.7.6']
41
+ redis: ['4.8.0']
42
+ sidekiq: ['6.5.0']
43
+ env:
44
+ BUNDLE_GEMFILE: gemfiles/redis_${{ matrix.redis }}_sidekiq_${{ matrix.sidekiq }}.gemfile
45
+ BUNDLE_WITHOUT: development
46
+
47
+ steps:
48
+ - uses: actions/checkout@v3
49
+ - name: Set up Ruby
50
+ uses: ruby/setup-ruby@v1
51
+ with:
52
+ ruby-version: ${{ matrix.ruby-version }}
53
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
54
+ - run: docker-compose up -d
55
+ - name: Run specs and generate coverage report
56
+ run: bundle exec rake spec
57
+ - name: Upload coverage to Codecov
58
+ uses: codecov/codecov-action@v3
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  /Gemfile.lock
2
+ /gemfiles/*.gemfile.lock
3
+ /gemfiles/.bundle/config
2
4
 
3
5
  /.autoenv.zsh
4
6
  /.bundle
data/.rubocop.yml CHANGED
@@ -1,6 +1,15 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
1
6
  AllCops:
2
- TargetRubyVersion: 2.3
7
+ TargetRubyVersion: 2.7
3
8
  DisplayCopNames: true
9
+ NewCops: enable
10
+ Exclude:
11
+ - "gemfiles/**/*"
12
+ - "vendor/**/*"
4
13
 
5
14
 
6
15
  ## Layout ######################################################################
@@ -8,12 +17,17 @@ AllCops:
8
17
  Layout/DotPosition:
9
18
  EnforcedStyle: trailing
10
19
 
11
- Layout/IndentArray:
20
+ Layout/FirstArrayElementIndentation:
12
21
  EnforcedStyle: consistent
13
22
 
14
- Layout/IndentHash:
23
+ Layout/FirstHashElementIndentation:
15
24
  EnforcedStyle: consistent
16
25
 
26
+ Layout/LineLength:
27
+ Max: 120
28
+
29
+ Layout/HashAlignment:
30
+ EnforcedHashRocketStyle: table
17
31
 
18
32
  ## Metrics #####################################################################
19
33
 
@@ -21,11 +35,13 @@ Metrics/BlockLength:
21
35
  Exclude:
22
36
  - "spec/**/*"
23
37
 
38
+ Metrics/MethodLength:
39
+ CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
24
40
 
25
- ## Style #######################################################################
41
+ Metrics/ModuleLength:
42
+ CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
26
43
 
27
- Style/BracesAroundHashParameters:
28
- Enabled: false
44
+ ## Style #######################################################################
29
45
 
30
46
  Style/HashSyntax:
31
47
  EnforcedStyle: hash_rockets
@@ -44,3 +60,23 @@ Style/StringLiterals:
44
60
 
45
61
  Style/YodaCondition:
46
62
  Enabled: false
63
+
64
+ ## RSpec ########################################################################
65
+ RSpec/ExampleLength:
66
+ Enabled: false
67
+
68
+ RSpec/MultipleExpectations:
69
+ Enabled: false
70
+
71
+ RSpec/MultipleMemoizedHelpers:
72
+ Enabled: false
73
+
74
+ RSpec/NestedGroups:
75
+ Enabled: false
76
+
77
+ Lint/AmbiguousBlockAssociation:
78
+ Exclude:
79
+ - "spec/**/*"
80
+
81
+ RSpec/IndexedLet:
82
+ Max: 5
data/ARCHITECTURE.md CHANGED
@@ -6,7 +6,7 @@ redis documentation. In short fetch will look like this:
6
6
 
7
7
  ``` ruby
8
8
  COOLDOWN = 2
9
- IDENTITY = Object.new.tap { |o| o.extend Sidekiq::Util }.identity
9
+ IDENTITY = Object.new.tap { |o| o.extend Sidekiq::Component }.identity
10
10
 
11
11
  def retrieve
12
12
  Sidekiq.redis do
data/Appraisals ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ REDIS_VERSIONS = %w[4.8.0].freeze
4
+ SIDEKIQ_VERSIONS = %w[6.5.0].freeze
5
+
6
+ version_combinations = REDIS_VERSIONS.product(SIDEKIQ_VERSIONS)
7
+
8
+ version_combinations.each do |redis_version, sidekiq_version|
9
+ appraise "redis_#{redis_version}_sidekiq_#{sidekiq_version}" do
10
+ gem "redis", "~> #{redis_version}"
11
+ gem "sidekiq", "~> #{sidekiq_version}"
12
+ end
13
+ end
data/CHANGES.md ADDED
@@ -0,0 +1,6 @@
1
+ ## 2.0.0 (2023-04-10)
2
+
3
+ * Move to Sidekiq 6.5
4
+ * Move to sidekiq-throttled 0.18.0
5
+ * Drop redis namespaces support
6
+ * Fix deprecation warnings for redis gem
data/Gemfile CHANGED
@@ -3,9 +3,13 @@
3
3
  source "https://rubygems.org"
4
4
  ruby RUBY_VERSION
5
5
 
6
+ gem "appraisal"
6
7
  gem "rake"
7
8
  gem "rspec"
8
- gem "rubocop", "~> 0.52.0", :require => false
9
+ gem "rubocop", "~> 1.50.2", :require => false
10
+ gem "rubocop-performance", "~> 1.17.1", :require => false
11
+ gem "rubocop-rake", "~> 0.6.0", :require => false
12
+ gem "rubocop-rspec", "~> 2.20.0", :require => false
9
13
 
10
14
  group :development do
11
15
  gem "guard", :require => false
@@ -19,10 +23,4 @@ group :test do
19
23
  gem "simplecov", :require => false
20
24
  end
21
25
 
22
- group :doc do
23
- gem "redcarpet"
24
- gem "yard"
25
- end
26
-
27
- # Specify your gem's dependencies in redis-prescription.gemspec
28
26
  gemspec
data/README.md CHANGED
@@ -18,7 +18,7 @@ extension one will need. :D
18
18
  Add this line to your application's Gemfile:
19
19
 
20
20
  ```ruby
21
- gem "sidekiq-ultimate", ">= 0.0.1.alpha"
21
+ gem "sidekiq-ultimate"
22
22
  ```
23
23
 
24
24
  And then execute:
@@ -40,6 +40,80 @@ require "sidekiq/ultimate"
40
40
  Sidekiq::Ultimate.setup!
41
41
  ```
42
42
 
43
+ ## Configuration
44
+
45
+ ### Resurrection event handler
46
+
47
+ An event handler can be called when a job is resurrected.
48
+
49
+ ```ruby
50
+ Sidekiq::Ultimate.setup! do |config|
51
+ config.on_resurrection = ->(queue_name, jobs_count) do
52
+ puts "Resurrected #{jobs_count} jobs from #{queue_name}"
53
+ end
54
+ end
55
+ ```
56
+
57
+ ### Resurrection counter
58
+
59
+ A resurrection counter can be enabled to count how many times a job was resurrected. If `enable_resurrection_counter` setting is enabled, on each resurrection event, a counter is increased. Counter value is stored in redis and has expiration time 24 hours.
60
+
61
+ For example this can be used in the `ServerMiddleware` later on to early return resurrected jobs based on the counter value.
62
+
63
+ `enable_resurrection_counter` can be either a `Proc` or a constant.
64
+
65
+ Having a `Proc` is useful if you want to enable or disable resurrection counter in run time. It will be called on each
66
+ resurrection event to decide whether to increase the counter or not.
67
+
68
+ ```ruby
69
+ Sidekiq::Ultimate.setup! do |config|
70
+ config.enable_resurrection_counter = -> do
71
+ DynamicSettings.get("enable_resurrection_counter")
72
+ end
73
+ end
74
+
75
+ Sidekiq::Ultimate.setup! do |config|
76
+ config.enable_resurrection_counter = true
77
+ end
78
+ ```
79
+
80
+ #### Read the value
81
+
82
+ Resurrection counter value can be read using `Sidekiq::Ultimate::Resurrector::Count.read` method.
83
+
84
+ ```ruby
85
+ Sidekiq::Ultimate::Resurrector::Count.read(:job_id => "2647c4fe13acc692326bd4c2")
86
+ => 1
87
+ ```
88
+
89
+ ### Empty Queues Cache Refresh Interval
90
+
91
+ ```ruby
92
+ Sidekiq::Ultimate.setup! do |config|
93
+ config.empty_queues_cache_refresh_interval_sec = 42
94
+ end
95
+ ```
96
+
97
+ Specifies how often the cache of empty queues should be refreshed.
98
+ In a nutshell, this sets the maximum possible delay between when a job was pushed to previously empty queue and earliest the moment when that new job could be picked up.
99
+
100
+ **Note:** every sidekiq process maintains its own local cache of empty queues.
101
+ Setting this interval to a low value will increase the number of Redis calls needed to check for empty queues, increasing the total load on Redis.
102
+
103
+ This setting helps manage the tradeoff between performance penalties and latency needed for reliable fetch.
104
+ Under the hood, Sidekiq's default fetch occurs with [a single Redis `BRPOP` call](https://redis.io/commands/brpop/) which is passes list of all queues to pluck work from.
105
+ In contrast, [reliable fetch uses `LPOPRPUSH`](https://redis.io/commands/rpoplpush/) (or the equivalent `LMOVE` in later Redis versions) to place in progress work into a WIP queue.
106
+ However, `LPOPRPUSH` can only check one source queue to pop from at once, and [no multi-key alternative is available](https://github.com/redis/redis/issues/1785), so multiple Redis calls are needed to pluck work if an empty queue is checked.
107
+ In order to avoid performance penalties for repeated calls to empty queues, Sidekiq Ultimate therefore maintains a list of recently know empty queues which it will avoid polling for work.
108
+
109
+ Therefore:
110
+ - If your Sidekiq architecture has *a low number of total queues*, the worst case penalty for polling empty queues will be bounded, and it is reasonable to **set a shorter refresh period**.
111
+ - If your Sidekiq architecture has a *high number of total queues*, the worst case penalty for polling empty queues is large, and it is recommended to **set a longer refresh period**.
112
+ - When adjusting this setting:
113
+ - Check that work is consumed appropriately quickly from high priority queues after they bottom out (after increasing the refresh interval)
114
+ - Check that backlog work does not accumulate in low priority queues (after decreasing the refresh interval)
115
+
116
+
43
117
  ---
44
118
 
45
119
  **NOTICE**
@@ -54,20 +128,13 @@ Thus look up it's README for throttling configuration details.
54
128
 
55
129
  ## Supported Ruby Versions
56
130
 
57
- This library aims to support and is [tested against][travis-ci] the following
58
- Ruby and Redis client versions:
131
+ This library aims to support and is tested against the following Ruby and Redis client versions:
59
132
 
60
133
  * Ruby
61
- * 2.3.x
62
- * 2.4.x
63
- * 2.5.x
134
+ * 2.7.x
64
135
 
65
136
  * [redis-rb](https://github.com/redis/redis-rb)
66
- * 4.x
67
-
68
- * [redis-namespace](https://github.com/resque/redis-namespace)
69
- * 1.6
70
-
137
+ * 4.8
71
138
 
72
139
  If something doesn't work on one of these versions, it's a bug.
73
140
 
@@ -106,11 +173,10 @@ push git commits and tags, and push the `.gem` file to [rubygems.org][].
106
173
 
107
174
  ## Copyright
108
175
 
109
- Copyright (c) 2018 SensorTower Inc.<br>
176
+ Copyright (c) 2018-23 SensorTower Inc.<br>
110
177
  See [LICENSE.md][] for further details.
111
178
 
112
179
 
113
- [travis.ci]: http://travis-ci.org/sensortower/sidekiq-ultimate
114
180
  [rubygems.org]: https://rubygems.org
115
181
  [LICENSE.md]: https://github.com/sensortower/sidekiq-ultimate/blob/master/LICENSE.txt
116
- [sidekiq-throttled]: http://travis-ci.org/sensortower/sidekiq-throttled
182
+ [sidekiq-throttled]: https://github.com/ixti/sidekiq-throttled
data/Rakefile CHANGED
@@ -8,8 +8,4 @@ RSpec::Core::RakeTask.new
8
8
  require "rubocop/rake_task"
9
9
  RuboCop::RakeTask.new
10
10
 
11
- if ENV["CI"]
12
- task :default => :spec
13
- else
14
- task :default => %i[rubocop spec]
15
- end
11
+ task :default => %i[rubocop spec]
@@ -0,0 +1,12 @@
1
+ version: '3.1'
2
+
3
+ services:
4
+ redis:
5
+ image: redis:6.0.6-alpine
6
+ volumes:
7
+ - redis-data:/data
8
+ ports:
9
+ - '6379:6379'
10
+
11
+ volumes:
12
+ redis-data:
@@ -0,0 +1,29 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ ruby "2.7.6"
6
+
7
+ gem "appraisal"
8
+ gem "rake"
9
+ gem "rspec"
10
+ gem "rubocop", "~> 1.50.2", require: false
11
+ gem "rubocop-performance", "~> 1.17.1", require: false
12
+ gem "rubocop-rake", "~> 0.6.0", require: false
13
+ gem "rubocop-rspec", "~> 2.20.0", require: false
14
+ gem "redis", "~> 4.8.0"
15
+ gem "sidekiq", "~> 6.5.0"
16
+
17
+ group :development do
18
+ gem "guard", require: false
19
+ gem "guard-rspec", require: false
20
+ gem "guard-rubocop", require: false
21
+ gem "pry", require: false
22
+ end
23
+
24
+ group :test do
25
+ gem "codecov", require: false
26
+ gem "simplecov", require: false
27
+ end
28
+
29
+ gemspec path: "../"
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module Sidekiq
6
+ module Ultimate
7
+ # Configuration options.
8
+ class Configuration
9
+ include Singleton
10
+
11
+ # @return [Proc] callback to be called when job is resurrected
12
+ # @yieldparam queue_name [String] name of the queue
13
+ # @yieldparam jobs_count [Integer] number of jobs resurrected
14
+ # @yieldreturn [void]
15
+ # @example
16
+ # Sidekiq::Ultimate::Configuration.instance.on_resurrection = ->(queue_name, jobs_count) do
17
+ # puts "Resurrected #{jobs_count} jobs from #{queue_name}"
18
+ # end
19
+ attr_accessor :on_resurrection
20
+
21
+ # If `enable_resurrection_counter` setting is enabled, on each resurrection event, a counter is increased.
22
+ # This is useful for telemetry purposes in order to understand how often jobs are resurrected
23
+ # Counter value is stored in redis by jid and has expiration time 24 hours.
24
+ # @return [Boolean]
25
+ attr_accessor :enable_resurrection_counter
26
+
27
+ # It specifies how often the cache of empty queues should be refreshed.
28
+ # In a nutshell, it specifies the maximum possible delay between a job was pushed to previously empty queue and
29
+ # the moment when that new job is picked up.
30
+ # Note that every sidekiq process needs to maintain its own local cache of empty queues. Setting this interval
31
+ # to a low values will increase the number of redis calls and will increase the load on redis.
32
+ # @return [Numeric] interval in seconds to refresh the cache of empty queues
33
+ attr_reader :empty_queues_cache_refresh_interval_sec
34
+
35
+ DEFAULT_EMPTY_QUEUES_CACHE_REFRESH_INTERVAL_SEC = 30
36
+
37
+ # If fetching attempt from a queue was throttled, it puts the queue to the exhausted list for this amount of time
38
+ # to avoid throttling for the same queue
39
+ # @return [Float] timeout in seconds
40
+ attr_writer :throttled_fetch_timeout_sec
41
+
42
+ DEFAULT_THROTTLED_FETCH_TIMEOUT_SEC = 15
43
+
44
+ def initialize
45
+ @empty_queues_cache_refresh_interval_sec = DEFAULT_EMPTY_QUEUES_CACHE_REFRESH_INTERVAL_SEC
46
+ @throttled_fetch_timeout_sec = DEFAULT_THROTTLED_FETCH_TIMEOUT_SEC
47
+ super
48
+ end
49
+
50
+ def empty_queues_cache_refresh_interval_sec=(value)
51
+ unless value.is_a?(Numeric)
52
+ raise ArgumentError, "Invalid 'empty_queues_cache_refresh_interval_sec' value: #{value}. Must be Numeric"
53
+ end
54
+
55
+ @empty_queues_cache_refresh_interval_sec = value
56
+ end
57
+
58
+ def throttled_fetch_timeout_sec
59
+ if @throttled_fetch_timeout_sec.respond_to?(:call)
60
+ @throttled_fetch_timeout_sec.call.to_f
61
+ else
62
+ @throttled_fetch_timeout_sec.to_f
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/timer_task"
4
+ require "sidekiq/ultimate/interval_with_jitter"
5
+
6
+ module Sidekiq
7
+ module Ultimate
8
+ class EmptyQueues
9
+ # Timer task that periodically refreshes empty queues. Also adds jitter to the execution interval.
10
+ class RefreshTimerTask
11
+ TASK_CLASS = Class.new(Concurrent::TimerTask)
12
+
13
+ class << self
14
+ def setup!(empty_queues_class)
15
+ interval = Sidekiq::Ultimate::Configuration.instance.empty_queues_cache_refresh_interval_sec
16
+ task = TASK_CLASS.new({
17
+ :run_now => true,
18
+ :execution_interval => Sidekiq::Ultimate::IntervalWithJitter.call(interval)
19
+ }) { empty_queues_class.instance.refresh! }
20
+ task.execute
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redlock"
4
+ require "singleton"
5
+
6
+ require "sidekiq/ultimate/configuration"
7
+ require "sidekiq/ultimate/queue_name"
8
+ require "sidekiq/ultimate/empty_queues/refresh_timer_task"
9
+
10
+ module Sidekiq
11
+ module Ultimate
12
+ # Maintains a cache of empty queues. It has a global cache and a local cache.
13
+ # The global cache is stored in redis and updated periodically. The local cache is updated either by using the fresh
14
+ # cache fetched after global cache update or by using existing global cache.
15
+ # Only one process can update the global cache at a time.
16
+ class EmptyQueues
17
+ include Singleton
18
+
19
+ LOCK_KEY = "ultimate:empty_queues_updater:lock"
20
+ LAST_RUN_KEY = "ultimate:empty_queues_updater:last_run"
21
+ KEY = "ultimate:empty_queues"
22
+
23
+ attr_reader :queues, :local_lock
24
+
25
+ def initialize
26
+ @local_lock = Mutex.new
27
+ @queues = []
28
+
29
+ super
30
+ end
31
+
32
+ # Sets up automatic empty queues cache updater.
33
+ # It will call #refresh! every
34
+ # `Sidekiq::Ultimate::Configuration.instance.empty_queues_cache_refresh_interval_sec` seconds
35
+ def self.setup!
36
+ refresher_timer_task = nil
37
+
38
+ Sidekiq.on(:startup) do
39
+ refresher_timer_task&.shutdown
40
+ refresher_timer_task = RefreshTimerTask.setup!(self)
41
+ end
42
+
43
+ Sidekiq.on(:shutdown) { refresher_timer_task&.shutdown }
44
+ end
45
+
46
+ # Attempts to update the global cache of empty queues by first acquiring a global lock
47
+ # If the lock is acquired, it brute force generates an accurate list of currently empty queues and
48
+ # then writes the updated list to the global cache
49
+ # The local queue cache is always updated as a result of this operation, either by using the recently generated
50
+ # list or fetching the most recent list from the global cache
51
+ #
52
+ # @return [Boolean] true if local cache was updated
53
+ def refresh!
54
+ return false unless local_lock.try_lock
55
+
56
+ begin
57
+ refresh_global_cache! || refresh_local_cache
58
+ ensure
59
+ local_lock.unlock
60
+ end
61
+ rescue => e
62
+ Sidekiq.logger.error { "Empty queues cache update failed: #{e}" }
63
+ raise
64
+ end
65
+
66
+ private
67
+
68
+ # Automatically updates local cache if global cache was updated
69
+ # @return [Boolean] true if cache was updated
70
+ def refresh_global_cache!
71
+ Sidekiq.logger.debug { "Refreshing global cache" }
72
+
73
+ global_lock do
74
+ Sidekiq.redis do |redis|
75
+ empty_queues = generate_empty_queues(redis)
76
+
77
+ update_global_cache(redis, empty_queues)
78
+ update_local_cache(empty_queues)
79
+ end
80
+ end
81
+ end
82
+
83
+ def generate_empty_queues(redis)
84
+ # Cursor is not atomic, so there may be duplicates because of concurrent update operations
85
+ queues = Sidekiq.redis { |r| r.sscan_each("queues").to_a.uniq }
86
+
87
+ queues_statuses =
88
+ redis.pipelined do |p|
89
+ queues.each do |queue|
90
+ p.exists?(QueueName.new(queue).pending)
91
+ end
92
+ end
93
+
94
+ queues.zip(queues_statuses).reject { |(_, exists)| exists }.map(&:first)
95
+ end
96
+
97
+ def refresh_local_cache
98
+ Sidekiq.logger.debug { "Refreshing local cache" }
99
+
100
+ # Cursor is not atomic, so there may be duplicates because of concurrent update operations
101
+ list = Sidekiq.redis { |redis| redis.sscan_each(KEY).to_a.uniq }
102
+ update_local_cache(list)
103
+ end
104
+
105
+ def update_global_cache(redis, list)
106
+ Sidekiq.logger.debug { "Setting global cache: #{list}" }
107
+
108
+ redis.multi do |multi|
109
+ multi.del(KEY)
110
+ multi.sadd(KEY, list) if list.any?
111
+ end
112
+ end
113
+
114
+ def update_local_cache(list)
115
+ Sidekiq.logger.debug { "Setting local cache: #{list}" }
116
+
117
+ @queues = list
118
+ end
119
+
120
+ # @return [Boolean] true if lock was acquired
121
+ def global_lock
122
+ Sidekiq.redis do |redis|
123
+ break false if skip_update?(redis) # Cheap check since lock will not be free most of the time
124
+
125
+ Redlock::Client.new([redis], :retry_count => 0).lock(LOCK_KEY, 30_000) do |locked|
126
+ break false unless locked
127
+ break false if skip_update?(redis)
128
+
129
+ yield
130
+
131
+ redis.set(LAST_RUN_KEY, redis.time.first)
132
+ end
133
+ end
134
+ end
135
+
136
+ def skip_update?(redis)
137
+ results = redis.pipelined { |pipeline| [pipeline.time, pipeline.get(LAST_RUN_KEY)] }
138
+ last_run_distance = results[0][0] - results[1].to_i
139
+
140
+ last_run_distance < Sidekiq::Ultimate::Configuration.instance.empty_queues_cache_refresh_interval_sec
141
+ end
142
+ end
143
+ end
144
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "monitor"
4
4
 
5
- require "concurrent/utility/monotonic_time"
6
-
7
5
  module Sidekiq
8
6
  module Ultimate
9
7
  # List that tracks when elements were added and enumerates over those not
@@ -24,7 +22,6 @@ module Sidekiq
24
22
  # It does not deduplicates elements. Eviction happens only upon elements
25
23
  # retrieval (see {#each}).
26
24
  #
27
- # @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#monotonic_time-class_method
28
25
  # @see https://ruby-doc.org/core/Process.html#method-c-clock_gettime
29
26
  # @see https://linux.die.net/man/3/clock_gettime
30
27
  #
@@ -52,7 +49,7 @@ module Sidekiq
52
49
  # @return [ExpirableSet] self
53
50
  def add(element, ttl:)
54
51
  @mon.synchronize do
55
- expires_at = Concurrent.monotonic_time + ttl
52
+ expires_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + ttl
56
53
 
57
54
  # do not allow decrease element's expiry
58
55
  @set[element] = expires_at if @set[element] < expires_at
@@ -71,7 +68,7 @@ module Sidekiq
71
68
  return to_enum __method__ unless block_given?
72
69
 
73
70
  @mon.synchronize do
74
- horizon = Concurrent.monotonic_time
71
+ horizon = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
75
72
  @set.each { |k, v| yield(k) if horizon <= v }
76
73
  end
77
74