sidekiq-unique-jobs 7.1.15 → 7.1.16

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05fc5a66be1f5117707cf5409bfbd37c7b22677a7035f8a9377fc89a949b85f1
4
- data.tar.gz: 5e7f255449d07dbc0758ffc315b7b22442132bb053cedbdf25a0253ea4c5a59f
3
+ metadata.gz: 4f0f434849fc23d828254f80ab5c7adeaf0a9bbea684071fd562e0c639fa2ef9
4
+ data.tar.gz: 9ac073e7731f2add9f55ecfce31f6a567723026ab3a97b6d4e31c67127839fa8
5
5
  SHA512:
6
- metadata.gz: 3554b3ca12ea3dd79d6c6fced007e6cb8fd40a0314a7edeecae397f3da4bb1fd276f48bbb84c16f9f99c1d915b2780595547232a6fdc20d6b0cf559c82acc6c3
7
- data.tar.gz: 169ee0a9a4809904e9a74957479a0a4f08e2f5d9f464d3e137a4eb2a2796eda3ea64a298f7e9b7a3c49d83d2b86a5df8c10848b4cfe74745828fd7166d5f9d79
6
+ metadata.gz: 5ed9e4982703780b97574d29f239f29c8a7d4c544203216a10bd52985381a09934adc0d750178760296a8b19d65811929b4a85cc6d1dd3c772291b7338a891d6
7
+ data.tar.gz: 6f874ec9fbffe76683dfa2009ef1be2b460288af4327a4feaffbb5588848b94f10faf73a1e9d916f5b1b4358af47b28ce3927c06ec81291f4002824e5bf5a37d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [v7.1.15](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.1.15) (2022-02-10)
4
+
5
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v7.1.14...v7.1.15)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Fixing reschedule when using a non default queue [\#679](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/679) ([bigzed](https://github.com/bigzed))
10
+
3
11
  ## [v7.1.14](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.1.14) (2022-02-04)
4
12
 
5
13
  [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v7.1.13...v7.1.14)
data/README.md CHANGED
@@ -90,7 +90,7 @@ Want to show me some ❤️ for the hard work I do on this gem? You can use the
90
90
 
91
91
  This gem adds unique constraints to sidekiq jobs. The uniqueness is achieved by creating a set of keys in redis based off of `queue`, `class`, `args` (in the sidekiq job hash).
92
92
 
93
- By default, only one lock for a given hash can be acquired. What happens when a lock can't be acquired is governed by a chosen [Conflict Strategy](#conflict-strategy) strategy. Unless a conflict strategy is chosen
93
+ By default, only one lock for a given hash can be acquired. What happens when a lock can't be acquired is governed by a chosen [Conflict Strategy](#conflict-strategy) strategy. Unless a conflict strategy is chosen (?)
94
94
 
95
95
  This is the documentation for the `main` branch. You can find the documentation for each release by navigating to its tag.
96
96
 
@@ -151,7 +151,7 @@ end
151
151
 
152
152
  ### Your first worker
153
153
 
154
- The most likely to be used worker is `:until_executed`. This type of lock creates a lock from when `UntilExecutedWorker.perform_async` is called until right after `UntilExecutedWorker.new.perform` has been called.
154
+ The lock type most likely to be is `:until_executed`. This type of lock creates a lock from when `UntilExecutedWorker.perform_async` is called until right after `UntilExecutedWorker.new.perform` has been called.
155
155
 
156
156
  ```ruby
157
157
  # frozen_string_literal: true
@@ -159,8 +159,7 @@ The most likely to be used worker is `:until_executed`. This type of lock create
159
159
  class UntilExecutedWorker
160
160
  include Sidekiq::Worker
161
161
 
162
- sidekiq_options queue: :until_executed,
163
- lock: :until_executed
162
+ sidekiq_options lock: :until_executed
164
163
 
165
164
  def perform
166
165
  logger.info("cowboy")
@@ -346,9 +345,9 @@ Please not that if you try to override a default lock, an `ArgumentError` will b
346
345
 
347
346
  ## Conflict Strategy
348
347
 
349
- Decides how we handle conflict. We can either reject the job to the dead queue or reschedule it. Both are useful for jobs that absolutely need to run and have been configured to use the lock `WhileExecuting` that is used only by the sidekiq server process.
348
+ Decides how we handle conflict. We can either `reject` the job to the dead queue or `reschedule` it. Both are useful for jobs that absolutely need to run and have been configured to use the lock `WhileExecuting` that is used only by the sidekiq server process.
350
349
 
351
- The last one is log which can be be used with the lock `UntilExecuted` and `UntilExpired`. Now we write a log entry saying the job could not be pushed because it is a duplicate of another job with the same arguments.
350
+ Furthermore, `log` can be be used with the lock `UntilExecuted` and `UntilExpired`. Now we write a log entry saying the job could not be pushed because it is a duplicate of another job with the same arguments.
352
351
 
353
352
  It is possible for locks to have different conflict strategy for the client and server. This is useful for `:until_and_while_executing`.
354
353
 
@@ -394,7 +393,7 @@ queue and retry the lock again.
394
393
  This is slightly dangerous and should probably only be used for jobs that are
395
394
  always scheduled in the future. Currently only attempting to retry one time.
396
395
 
397
- ### Reschedule
396
+ ### reschedule
398
397
 
399
398
  ```ruby
400
399
  sidekiq_options on_conflict: :reschedule
@@ -568,7 +567,7 @@ The reason this happens is that the server couldn't find a valid sidekiq worker
568
567
 
569
568
  ### Validating Worker Configuration
570
569
 
571
- Since v7 it is possible to perform some simple validation against your workers sidekiq_options. What it does is scan for some issues that are known to cause problems in production.
570
+ Since v7 it is possible to perform some simple validation against your workers `sidekiq_options`. What it does is scan for some issues that are known to cause problems in production.
572
571
 
573
572
  Let's take a _bad_ worker:
574
573
 
@@ -604,7 +603,7 @@ assert_raise(InvalidWorker){ SidekiqUniqueJobs.validate_worker!(BadWorker.get_si
604
603
 
605
604
  ### Uniqueness
606
605
 
607
- This has been probably the most confusing part of this gem. People get really confused with how unreliable the unique jobs have been. I there for decided to do what Mike is doing for sidekiq enterprise. Read the section about unique jobs: [Enterprise unique jobs][]
606
+ This has been probably the most confusing part of this gem. People get really confused with how unreliable the unique jobs have been. I there for decided to do what Mike is doing for sidekiq enterprise. Read the section about unique jobs: [Enterprise unique jobs][](?)
608
607
 
609
608
  ```ruby
610
609
  SidekiqUniqueJobs.configure do |config|
@@ -34,7 +34,7 @@ module SidekiqUniqueJobs
34
34
  # The unique arguments to use for creating a lock
35
35
  # @return [Array] the arguments filters by the {#filtered_args} method if {#lock_args_enabled?}
36
36
  def lock_args
37
- @lock_args ||= filtered_args
37
+ @lock_args ||= filtered_args || []
38
38
  end
39
39
 
40
40
  # Checks if the worker class has enabled lock_args
@@ -113,13 +113,13 @@ module SidekiqUniqueJobs
113
113
 
114
114
  # the strategy to use as conflict resolution from sidekiq client
115
115
  def on_client_conflict
116
- @on_client_conflict ||= on_conflict["client"] if on_conflict.is_a?(Hash)
116
+ @on_client_conflict ||= on_conflict["client"] || on_conflict[:client] if on_conflict.is_a?(Hash)
117
117
  @on_client_conflict ||= on_conflict
118
118
  end
119
119
 
120
120
  # the strategy to use as conflict resolution from sidekiq server
121
121
  def on_server_conflict
122
- @on_server_conflict ||= on_conflict["server"] if on_conflict.is_a?(Hash)
122
+ @on_server_conflict ||= on_conflict["server"] || on_conflict[:server] if on_conflict.is_a?(Hash)
123
123
  @on_server_conflict ||= on_conflict
124
124
  end
125
125
  end
@@ -38,8 +38,8 @@ module SidekiqUniqueJobs
38
38
  def initialize(item)
39
39
  @item = item
40
40
  @worker_class = item[CLASS]
41
- @lock_args = item.slice(LOCK_ARGS, UNIQUE_ARGS).values.first # TODO: Deprecate UNIQUE_ARGS
42
- @lock_prefix = item.slice(LOCK_PREFIX, UNIQUE_PREFIX).values.first # TODO: Deprecate UNIQUE_PREFIX
41
+ @lock_args = item[LOCK_ARGS] || item[UNIQUE_ARGS] # TODO: Deprecate UNIQUE_ARGS
42
+ @lock_prefix = item[LOCK_PREFIX] || item[UNIQUE_PREFIX] # TODO: Deprecate UNIQUE_PREFIX
43
43
  end
44
44
 
45
45
  # Memoized lock_digest
@@ -328,6 +328,7 @@ module SidekiqUniqueJobs
328
328
  end
329
329
 
330
330
  def add_drift(val)
331
+ val = val.to_f
331
332
  val + drift(val)
332
333
  end
333
334
 
@@ -31,7 +31,7 @@ local function log(message, prev_jid)
31
31
  log_debug("ZADD", changelog, current_time, entry);
32
32
  redis.call("ZADD", changelog, current_time, entry);
33
33
  local total_entries = redis.call("ZCARD", changelog)
34
- local removed_entries = redis.call("ZREMRANGEBYRANK", changelog, max_history, -1)
34
+ local removed_entries = redis.call("ZREMRANGEBYRANK", changelog, 0, -1 * max_history)
35
35
  if removed_entries > 0 then
36
36
  log_debug("Removing", removed_entries , "entries from changelog (total entries", total_entries, "exceeds max_history:", max_history ..")");
37
37
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/version"
4
+
3
5
  module SidekiqUniqueJobs
4
6
  module Orphans
5
7
  #
@@ -108,9 +110,13 @@ module SidekiqUniqueJobs
108
110
  # @return [Hash]
109
111
  #
110
112
  def timer_task_options
111
- { run_now: true,
112
- execution_interval: reaper_interval,
113
- timeout_interval: reaper_timeout }
113
+ timer_task_options = { run_now: true, execution_interval: reaper_interval }
114
+
115
+ if VersionCheck.satisfied?(::Concurrent::VERSION, "< 1.1.10")
116
+ timer_task_options[:timeout_interval] = reaper_timeout
117
+ end
118
+
119
+ timer_task_options
114
120
  end
115
121
 
116
122
  #
@@ -15,6 +15,9 @@ module SidekiqUniqueJobs
15
15
  # @return [String] the suffix for :RUN locks
16
16
  RUN_SUFFIX = ":RUN"
17
17
  #
18
+ # @return [Integer] the maximum combined length of sidekiq queues for running the reaper
19
+ MAX_QUEUE_LENGTH = 1000
20
+ #
18
21
  # @!attribute [r] digests
19
22
  # @return [SidekiqUniqueJobs::Digests] digest collection
20
23
  attr_reader :digests
@@ -46,6 +49,8 @@ module SidekiqUniqueJobs
46
49
  # @return [Integer] the number of reaped locks
47
50
  #
48
51
  def call
52
+ return if queues_very_full?
53
+
49
54
  BatchDelete.call(orphans, conn)
50
55
  end
51
56
 
@@ -212,6 +217,22 @@ module SidekiqUniqueJobs
212
217
  end
213
218
  end
214
219
 
220
+ # If sidekiq queues are very full, it becomes highly inefficient for the reaper
221
+ # because it must check every queued job to verify a digest is safe to delete
222
+ # The reaper checks queued jobs in batches of 50, adding 2 reads per digest
223
+ # With a queue length of 1,000 jobs, that's over 20 extra reads per digest.
224
+ def queues_very_full?
225
+ total_queue_size = 0
226
+ Sidekiq.redis do |conn|
227
+ queues(conn) do |queue|
228
+ total_queue_size += conn.llen("queue:#{queue}")
229
+
230
+ return true if total_queue_size > MAX_QUEUE_LENGTH
231
+ end
232
+ end
233
+ false
234
+ end
235
+
215
236
  #
216
237
  # Checks a sorted set for the existance of this digest
217
238
  #
@@ -21,16 +21,24 @@ module Sidekiq
21
21
  #
22
22
  # @param [Hash<Symbol, Object>] tmp_config the temporary config to use
23
23
  #
24
- def self.use_options(tmp_config = {})
25
- old_options = default_worker_options.dup
24
+ def self.use_options(tmp_config = {}) # rubocop:disable Metrics/MethodLength
25
+ if respond_to?(:default_job_options)
26
+ default_job_options.clear
27
+ self.default_job_options = tmp_config
28
+ else
29
+ default_worker_options.clear
30
+ self.default_worker_options = tmp_config
31
+ end
26
32
 
27
- default_worker_options.clear
28
- self.default_worker_options = tmp_config
29
33
  yield
30
34
  ensure
31
- default_worker_options.clear
32
- self.default_worker_options = DEFAULT_WORKER_OPTIONS
33
- self.default_worker_options = old_options
35
+ if respond_to?(:default_job_options)
36
+ default_job_options.clear
37
+ self.default_job_options = default_job_options
38
+ else
39
+ default_worker_options.clear
40
+ self.default_worker_options = DEFAULT_WORKER_OPTIONS
41
+ end
34
42
  end
35
43
 
36
44
  #
@@ -54,7 +62,13 @@ module Sidekiq
54
62
 
55
63
  yield
56
64
  ensure
57
- self.sidekiq_options_hash = Sidekiq::DEFAULT_WORKER_OPTIONS
65
+ self.sidekiq_options_hash =
66
+ if Sidekiq.respond_to?(:default_job_options)
67
+ Sidekiq.default_job_options
68
+ else
69
+ DEFAULT_WORKER_OPTIONS
70
+ end
71
+
58
72
  sidekiq_options(old_options)
59
73
  end
60
74
 
@@ -1,78 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/version"
4
+ require_relative "version_check"
5
+
3
6
  module SidekiqUniqueJobs
4
7
  # @see [Concurrent::TimerTask] https://www.rubydoc.info/gems/concurrent-ruby/Concurrent/TimerTask
5
8
  #
6
9
  class TimerTask < ::Concurrent::TimerTask
7
- private
10
+ if VersionCheck.satisfied?(::Concurrent::VERSION, "< 1.1.10")
8
11
 
9
- def ns_initialize(opts, &task)
10
- set_deref_options(opts)
12
+ private
11
13
 
12
- self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
13
- self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
14
- @run_now = opts[:now] || opts[:run_now]
15
- @executor = Concurrent::RubySingleThreadExecutor.new
16
- @running = Concurrent::AtomicBoolean.new(false)
17
- @task = task
18
- @value = nil
14
+ def ns_initialize(opts, &task)
15
+ set_deref_options(opts)
19
16
 
20
- self.observers = Concurrent::Collection::CopyOnNotifyObserverSet.new
21
- end
17
+ self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
18
+ self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
19
+ @run_now = opts[:now] || opts[:run_now]
20
+ @executor = Concurrent::RubySingleThreadExecutor.new
21
+ @running = Concurrent::AtomicBoolean.new(false)
22
+ @task = task
23
+ @value = nil
22
24
 
23
- def schedule_next_task(interval = execution_interval)
24
- exec_task = ->(completion) { execute_task(completion) }
25
- Concurrent::ScheduledTask.execute(interval, args: [Concurrent::Event.new], &exec_task)
26
- nil
27
- end
25
+ self.observers = Concurrent::Collection::CopyOnNotifyObserverSet.new
26
+ end
28
27
 
29
- # @!visibility private
30
- def execute_task(completion) # rubocop:disable Metrics/MethodLength
31
- return nil unless @running.true?
32
-
33
- timeout_task = -> { timeout_task(completion) }
34
-
35
- Concurrent::ScheduledTask.execute(
36
- timeout_interval,
37
- args: [completion],
38
- &timeout_task
39
- )
40
- @thread_completed = Concurrent::Event.new
41
-
42
- @value = @reason = nil
43
- @executor.post do
44
- @value = @task.call(self)
45
- rescue Exception => ex # rubocop:disable Lint/RescueException
46
- @reason = ex
47
- ensure
48
- @thread_completed.set
28
+ def schedule_next_task(interval = execution_interval)
29
+ exec_task = ->(completion) { execute_task(completion) }
30
+ Concurrent::ScheduledTask.execute(interval, args: [Concurrent::Event.new], &exec_task)
31
+ nil
49
32
  end
50
33
 
51
- @thread_completed.wait
34
+ # @!visibility private
35
+ def execute_task(completion) # rubocop:disable Metrics/MethodLength
36
+ return nil unless @running.true?
52
37
 
53
- if completion.try?
54
- schedule_next_task
55
- time = Time.now
56
- observers.notify_observers do
57
- [time, value, @reason]
38
+ timeout_task = -> { timeout_task(completion) }
39
+
40
+ Concurrent::ScheduledTask.execute(
41
+ timeout_interval,
42
+ args: [completion],
43
+ &timeout_task
44
+ )
45
+ @thread_completed = Concurrent::Event.new
46
+
47
+ @value = @reason = nil
48
+ @executor.post do
49
+ @value = @task.call(self)
50
+ rescue Exception => ex # rubocop:disable Lint/RescueException
51
+ @reason = ex
52
+ ensure
53
+ @thread_completed.set
58
54
  end
55
+
56
+ @thread_completed.wait
57
+
58
+ if completion.try?
59
+ schedule_next_task
60
+ time = Time.now
61
+ observers.notify_observers do
62
+ [time, value, @reason]
63
+ end
64
+ end
65
+ nil
59
66
  end
60
- nil
61
- end
62
67
 
63
- # @!visibility private
64
- def timeout_task(completion)
65
- return unless @running.true?
66
- return unless completion.try?
68
+ # @!visibility private
69
+ def timeout_task(completion)
70
+ return unless @running.true?
71
+ return unless completion.try?
67
72
 
68
- @executor.kill
69
- @executor.wait_for_termination
70
- @executor = Concurrent::RubySingleThreadExecutor.new
73
+ @executor.kill
74
+ @executor.wait_for_termination
75
+ @executor = Concurrent::RubySingleThreadExecutor.new
71
76
 
72
- @thread_completed.set
77
+ @thread_completed.set
73
78
 
74
- schedule_next_task
75
- observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
79
+ schedule_next_task
80
+ observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
81
+ end
76
82
  end
77
83
  end
78
84
  end
@@ -3,5 +3,5 @@
3
3
  module SidekiqUniqueJobs
4
4
  #
5
5
  # @return [String] the current SidekiqUniqueJobs version
6
- VERSION = "7.1.15"
6
+ VERSION = "7.1.16"
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-unique-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.15
4
+ version: 7.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-10 00:00:00.000000000 Z
11
+ date: 2022-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: brpoplpush-redis_script
@@ -257,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
257
257
  - !ruby/object:Gem::Version
258
258
  version: '0'
259
259
  requirements: []
260
- rubygems_version: 3.3.6
260
+ rubygems_version: 3.3.7
261
261
  signing_key:
262
262
  specification_version: 4
263
263
  summary: Sidekiq middleware that prevents duplicates jobs