sidekiq-unique-jobs 8.0.2 → 8.0.4

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: 820d13b5365e9a4af437b1fecaebec61f4c07656b6aa77e49fff21186abc9088
4
- data.tar.gz: 0b90028e6697ec459ff63094e063100878080ad4a18101a60b7682a554c9b5f6
3
+ metadata.gz: 35c1bacf5f3ad9218a2791aa90b38a9f76e98aa53891ebbfb764b948c00b21fe
4
+ data.tar.gz: 750b7b72457f8f58c50f3419eba36954a8a518dc77473861f8d18e5fd8542975
5
5
  SHA512:
6
- metadata.gz: e1e24f2c8921566adc9339841d0ef4931f2768bf7acb207eb680cc84b11bb3671749325996c563e6a5aeb699cda33553597c6dec3451a5028c2bb83e292b82f5
7
- data.tar.gz: ff870dba5ef353df17aa80ad82c58418daef5a7643d1ff6c9bece90e4d57acacdd1091fd2893e88c0e5459a14668344cfb05d9ef3640d5c25eda1650f87f2f4c
6
+ metadata.gz: ea41240b24e440d3042a59570665b54e8bc9ffe186e10cdf4259a7f196c86f689c149586db0a37d02f75308b91fc1aedb0641f10e4d8565a0d70c4684371ad19
7
+ data.tar.gz: d12af8379f1262f92fefde8ca4fe4fbf80ff7ff2cbab477c12b3440e7d0513488adc2a6b7dbbd059ea033c7688b89734af52521b39086bacd01271de66ed449c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.3...HEAD)
6
+
7
+ **Closed issues:**
8
+
9
+ - Redis has deprecated the `rpoplpush`command [\#793](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/793)
10
+ - Deprecation Warning: Use of rpoplpush command in Redis [\#792](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/792)
11
+ - Logging::Middleware.context can include empty string as key when lock is not set in job hash [\#785](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/785)
12
+
13
+ ## [v8.0.3](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.3) (2023-05-27)
14
+
15
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.2...v8.0.3)
16
+
17
+ **Implemented enhancements:**
18
+
19
+ - chore\(ci\): improve matrix [\#775](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/775) ([mhenrixon](https://github.com/mhenrixon))
20
+ - chore\(ci\): better ci job output [\#772](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/772) ([mhenrixon](https://github.com/mhenrixon))
21
+
22
+ **Fixed bugs:**
23
+
24
+ - Add unlocked reflect to locksmith unlock! [\#783](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/783) ([ttstarck](https://github.com/ttstarck))
25
+ - fix\(locksmith\): replace deprecated rpoplpush w/lmove [\#781](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/781) ([GabrielaGuedes](https://github.com/GabrielaGuedes))
26
+ - fix\(unlock\): ensure callback and unlock [\#771](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/771) ([mhenrixon](https://github.com/mhenrixon))
27
+
28
+ **Closed issues:**
29
+
30
+ - `unlocked` reflection is never called [\#782](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/782)
31
+ - Migrate away from deprecated Redis commands [\#780](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/780)
32
+ - `while_executing` with `on_conflict: :reschedule` reschedules job when unlock fails [\#770](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/770)
33
+ - Redis server v6.0 compatibility [\#769](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/769)
34
+ - Locks not removed after worker finishes [\#758](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/758)
35
+
36
+ **Merged pull requests:**
37
+
38
+ - Add lock to item when lock\_type is pulled from Job class sidekiq options [\#786](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/786) ([ttstarck](https://github.com/ttstarck))
39
+ - Fix rubocop failure by expecting array to be empty [\#784](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/784) ([GabrielaGuedes](https://github.com/GabrielaGuedes))
40
+ - fix\(doc\): improve information on unlock\_failed [\#776](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/776) ([mhenrixon](https://github.com/mhenrixon))
41
+
42
+ ## [v8.0.2](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.2) (2023-03-13)
43
+
44
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.1...v8.0.2)
45
+
46
+ **Closed issues:**
47
+
48
+ - Missing v7.1 branch? [\#757](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/757)
49
+ - Allow one running job but at most one job in the queue [\#748](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/748)
50
+
51
+ **Merged pull requests:**
52
+
53
+ - update debug\_lua check to match passed in arg type [\#765](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/765) ([JeremiahChurch](https://github.com/JeremiahChurch))
54
+ - fix rubocop [\#764](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/764) ([JeremiahChurch](https://github.com/JeremiahChurch))
55
+ - Hide lock info debug suggestion on lock page if it's already enabled. [\#763](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/763) ([JeremiahChurch](https://github.com/JeremiahChurch))
56
+ - fix lock & changelog times on web interface [\#762](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/762) ([JeremiahChurch](https://github.com/JeremiahChurch))
57
+
3
58
  ## [v8.0.1](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.1) (2023-02-14)
4
59
 
5
60
  [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.0...v8.0.1)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SidekiqUniqueJobs
2
2
 
3
- [![Join the chat at https://gitter.im/mhenrixon/sidekiq-unique-jobs](https://badges.gitter.im/mhenrixon/sidekiq-unique-jobs.svg)](https://gitter.im/mhenrixon/sidekiq-unique-jobs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ![Build Status](https://github.com/mhenrixon/sidekiq-unique-jobs/actions/workflows/rspec.yml/badge.svg?branch=master) [![Code Climate](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs.svg)](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs) [![Test Coverage](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs/badges/coverage.svg)](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs/coverage)
3
+ [![Join the chat at https://gitter.im/mhenrixon/sidekiq-unique-jobs](https://badges.gitter.im/mhenrixon/sidekiq-unique-jobs.svg)](https://gitter.im/mhenrixon/sidekiq-unique-jobs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ![Build Status](https://github.com/mhenrixon/sidekiq-unique-jobs/actions/workflows/rspec.yml/badge.svg?branch=main) [![Code Climate](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs.svg)](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs) [![Test Coverage](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs/badges/coverage.svg)](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs/coverage)
4
4
 
5
5
  ## Support Me
6
6
 
@@ -45,11 +45,11 @@ Want to show me some ❤️ for the hard work I do on this gem? You can use the
45
45
  - [reschedule_failed](#reschedule_failed)
46
46
  - [rescheduled](#rescheduled)
47
47
  - [timeout](#timeout)
48
- - [unlock_failed](#unlock_failed)
49
- - [unlocked](#unlocked)
50
- - [unknown_sidekiq_worker](#unknown_sidekiq_worker)
51
- - [Show Locks](#show-locks)
52
- - [Show Lock](#show-lock)
48
+ - [unlock_failed](#unlock_failed)
49
+ - [unlocked](#unlocked)
50
+ - [unknown_sidekiq_worker](#unknown_sidekiq_worker)
51
+ - [Show Locks](#show-locks)
52
+ - [Show Lock](#show-lock)
53
53
  - [Testing](#testing)
54
54
  - [Validating Worker Configuration](#validating-worker-configuration)
55
55
  - [Uniqueness](#uniqueness)
@@ -545,23 +545,23 @@ For when a job was successfully rescheduled
545
545
 
546
546
  This is also mostly useful for reporting/metrics purposes. What this reflection does is signal that the job was configured to wait (`lock_timeout` was configured), but we couldn't retrieve a lock even though we waited for some time.
547
547
 
548
- ### unlock_failed
548
+ #### unlock_failed
549
549
 
550
- This is not got, this is worth
550
+ This means that the server middleware could not unlock your job and the lock is kept (potentially preventing subsequent jobs from being pushed or processed).
551
551
 
552
- ### unlocked
552
+ #### unlocked
553
553
 
554
554
  Also mostly useful for reporting purposes. The job was successfully unlocked.
555
555
 
556
- ### unknown_sidekiq_worker
556
+ #### unknown_sidekiq_worker
557
557
 
558
558
  The reason this happens is that the server couldn't find a valid sidekiq worker class. Most likely, that worker isn't intended to be processed by this sidekiq server instance.
559
559
 
560
- #### Show Locks
560
+ ### Show Locks
561
561
 
562
562
  ![Locks](assets/unique_digests_1.png)
563
563
 
564
- #### Show Lock
564
+ ### Show Lock
565
565
 
566
566
  ![Lock](assets/unique_digests_2.png)
567
567
 
@@ -93,7 +93,7 @@ module SidekiqUniqueJobs
93
93
  [
94
94
  total_size.to_i,
95
95
  digests[0].to_i, # next_cursor
96
- digests[1].map { |digest, score| Lock.new(digest, time: score) }, # entries
96
+ digests[1].each_slice(2).map { |digest, score| Lock.new(digest, time: score) }, # entries
97
97
  ]
98
98
  end
99
99
  end
@@ -7,10 +7,11 @@ module SidekiqUniqueJobs
7
7
  module Job
8
8
  extend self
9
9
 
10
- # Adds timeout, expiration, lock_args, lock_prefix and lock_digest to the sidekiq job hash
10
+ # Adds lock, timeout, expiration, lock_args, lock_prefix, and lock_digest to the sidekiq job hash
11
11
  # @return [Hash] the job hash
12
12
  def prepare(item)
13
13
  stringify_on_conflict_hash(item)
14
+ add_lock_type(item)
14
15
  add_lock_timeout(item)
15
16
  add_lock_ttl(item)
16
17
  add_digest(item)
@@ -54,5 +55,9 @@ module SidekiqUniqueJobs
54
55
  def add_lock_prefix(item)
55
56
  item[LOCK_PREFIX] ||= SidekiqUniqueJobs.config.lock_prefix
56
57
  end
58
+
59
+ def add_lock_type(item)
60
+ item[LOCK] ||= SidekiqUniqueJobs::LockType.call(item)
61
+ end
57
62
  end
58
63
  end
@@ -35,6 +35,7 @@ module SidekiqUniqueJobs
35
35
  def execute
36
36
  executed = locksmith.execute do
37
37
  yield
38
+ ensure
38
39
  unlock_and_callback
39
40
  end
40
41
 
@@ -42,9 +42,8 @@ module SidekiqUniqueJobs
42
42
  with_logging_context do
43
43
  executed = locksmith.execute do
44
44
  yield
45
- callback_safely if locksmith.unlock
46
45
  ensure
47
- locksmith.unlock
46
+ unlock_and_callback
48
47
  end
49
48
 
50
49
  unless executed
@@ -47,7 +47,7 @@ module SidekiqUniqueJobs
47
47
  #
48
48
  def initialize(key, time: nil)
49
49
  @key = get_key(key)
50
- time = time.is_a?(Float) ? time : time.to_f
50
+ time = time.to_f unless time.is_a?(Float)
51
51
  return unless time.nonzero?
52
52
 
53
53
  @created_at = time
@@ -113,14 +113,20 @@ 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"] || on_conflict[:client] if on_conflict.is_a?(Hash)
117
- @on_client_conflict ||= on_conflict
116
+ @on_client_conflict ||= if on_conflict.is_a?(Hash)
117
+ on_conflict["client"] || on_conflict[:client]
118
+ else
119
+ on_conflict
120
+ end
118
121
  end
119
122
 
120
123
  # the strategy to use as conflict resolution from sidekiq server
121
124
  def on_server_conflict
122
- @on_server_conflict ||= on_conflict["server"] || on_conflict[:server] if on_conflict.is_a?(Hash)
123
- @on_server_conflict ||= on_conflict
125
+ @on_server_conflict ||= if on_conflict.is_a?(Hash)
126
+ on_conflict["server"] || on_conflict[:server]
127
+ else
128
+ on_conflict
129
+ end
124
130
  end
125
131
  end
126
132
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ # Calculates the lock type
5
+ #
6
+ class LockType
7
+ # includes "SidekiqUniqueJobs::SidekiqWorkerMethods"
8
+ # @!parse include SidekiqUniqueJobs::SidekiqWorkerMethods
9
+ include SidekiqUniqueJobs::SidekiqWorkerMethods
10
+
11
+ #
12
+ # Computes lock type from job arguments, sidekiq_options.
13
+ #
14
+ # @return [Symbol] the lock type
15
+ # @return [NilClass] if no lock type is found.
16
+ #
17
+ def self.call(item)
18
+ new(item).call
19
+ end
20
+
21
+ # @!attribute [r] item
22
+ # @return [Hash] the Sidekiq job hash
23
+ attr_reader :item
24
+
25
+ # @param [Hash] item the Sidekiq job hash
26
+ # @option item [Symbol, nil] :lock the type of lock to use.
27
+ # @option item [String] :class the class of the sidekiq worker
28
+ def initialize(item)
29
+ @item = item
30
+ self.job_class = item[CLASS]
31
+ end
32
+
33
+ def call
34
+ item[LOCK] || job_options[LOCK] || default_job_options[LOCK]
35
+ end
36
+ end
37
+ end
@@ -127,7 +127,10 @@ module SidekiqUniqueJobs
127
127
  #
128
128
  def unlock!(conn = nil)
129
129
  call_script(:unlock, key.to_a, argv, conn) do |unlocked_jid|
130
- reflect(:debug, :unlocked, item, unlocked_jid) if unlocked_jid == job_id
130
+ if unlocked_jid == job_id
131
+ reflect(:debug, :unlocked, item, unlocked_jid)
132
+ reflect(:unlocked, item)
133
+ end
131
134
 
132
135
  unlocked_jid
133
136
  end
@@ -312,7 +315,7 @@ module SidekiqUniqueJobs
312
315
  # @api private
313
316
  #
314
317
  def rpoplpush(conn)
315
- conn.rpoplpush(key.queued, key.primed)
318
+ conn.lmove(key.queued, key.primed, "RIGHT", "LEFT")
316
319
  end
317
320
 
318
321
  #
@@ -21,7 +21,7 @@ module SidekiqUniqueJobs
21
21
  # This will mess up sidekiq stats because a new job is created
22
22
  def call
23
23
  if sidekiq_job_class?
24
- if job_class.set(queue: item["queue"].to_sym).perform_in(5, *item[ARGS])
24
+ if job_class.set(queue: item["queue"].to_sym).perform_in(schedule_in, *item[ARGS])
25
25
  reflect(:rescheduled, item)
26
26
  else
27
27
  reflect(:reschedule_failed, item)
@@ -30,6 +30,10 @@ module SidekiqUniqueJobs
30
30
  reflect(:unknown_sidekiq_worker, item)
31
31
  end
32
32
  end
33
+
34
+ def schedule_in
35
+ job_class.get_sidekiq_options["schedule_in"] || 5
36
+ end
33
37
  end
34
38
  end
35
39
  end
@@ -55,7 +55,7 @@ module SidekiqUniqueJobs
55
55
  # The type of lock for this worker
56
56
  #
57
57
  #
58
- # @return [Symbol]
58
+ # @return [Symbol, NilClass]
59
59
  #
60
60
  def lock_type
61
61
  @lock_type ||= options[LOCK] || item[LOCK]
@@ -72,7 +72,7 @@ module SidekiqUniqueJobs
72
72
  # @return [<type>] <description>
73
73
  #
74
74
  def task
75
- @task ||= default_task
75
+ @task ||= default_task # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
76
76
  end
77
77
 
78
78
  #
@@ -100,7 +100,7 @@ module SidekiqUniqueJobs
100
100
  # @return [void]
101
101
  #
102
102
  def task=(task)
103
- @task = task
103
+ @task = task # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
104
104
  end
105
105
 
106
106
  #
@@ -192,7 +192,7 @@ module SidekiqUniqueJobs
192
192
  # @return [void]
193
193
  #
194
194
  def register_reaper_process
195
- redis { |conn| conn.set(UNIQUE_REAPER, current_timestamp, nx: true, ex: drift_reaper_interval) }
195
+ redis { |conn| conn.set(UNIQUE_REAPER, current_timestamp, "nx", "ex", drift_reaper_interval) }
196
196
  end
197
197
 
198
198
  #
@@ -202,7 +202,7 @@ module SidekiqUniqueJobs
202
202
  # @return [void]
203
203
  #
204
204
  def refresh_reaper_mutex
205
- redis { |conn| conn.set(UNIQUE_REAPER, current_timestamp, ex: drift_reaper_interval) }
205
+ redis { |conn| conn.set(UNIQUE_REAPER, current_timestamp, "ex", drift_reaper_interval) }
206
206
  end
207
207
 
208
208
  #
@@ -79,7 +79,7 @@ module SidekiqUniqueJobs
79
79
  def expired_digests
80
80
  max_score = (start_time - reaper_timeout).to_f
81
81
 
82
- conn.zrange(EXPIRING_DIGESTS, 0, max_score, byscore: true)
82
+ conn.zrange(EXPIRING_DIGESTS, 0, max_score, "byscore")
83
83
  end
84
84
 
85
85
  #
@@ -24,10 +24,11 @@ module SidekiqUniqueJobs
24
24
  # @return [Hash] when given with_scores: true
25
25
  #
26
26
  def entries(with_scores: true)
27
- entrys = redis { |conn| conn.zrange(key, 0, -1, withscores: with_scores) }
28
- return entrys unless with_scores
27
+ return redis { |conn| conn.zrange(key, 0, -1) } unless with_scores
29
28
 
30
- entrys.each_with_object({}) { |pair, hash| hash[pair[0]] = pair[1] }
29
+ redis { |conn| conn.zrange(key, 0, -1, "withscores") }.each_with_object({}) do |pair, hash|
30
+ hash[pair[0]] = pair[1]
31
+ end
31
32
  end
32
33
 
33
34
  #
@@ -17,7 +17,7 @@ module SidekiqUniqueJobs # rubocop:disable Metrics/ModuleLength
17
17
  # @return [SidekiqUniqueJobs::Config] the gem configuration
18
18
  #
19
19
  def config
20
- @config ||= reset!
20
+ @config ||= reset! # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
21
21
  end
22
22
 
23
23
  #
@@ -108,7 +108,7 @@ module SidekiqUniqueJobs # rubocop:disable Metrics/ModuleLength
108
108
  # @return [SidekiqUniqueJobs::Config] a default gem configuration
109
109
  #
110
110
  def reset!
111
- @config = SidekiqUniqueJobs::Config.default
111
+ @config = SidekiqUniqueJobs::Config.default # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
112
112
  end
113
113
 
114
114
  #
@@ -288,7 +288,7 @@ module SidekiqUniqueJobs # rubocop:disable Metrics/ModuleLength
288
288
  # @return [Reflections]
289
289
  #
290
290
  def reflections
291
- @reflections ||= Reflections.new
291
+ @reflections ||= Reflections.new # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
292
292
  end
293
293
 
294
294
  #
@@ -3,5 +3,5 @@
3
3
  module SidekiqUniqueJobs
4
4
  #
5
5
  # @return [String] the current SidekiqUniqueJobs version
6
- VERSION = "8.0.2"
6
+ VERSION = "8.0.4"
7
7
  end
@@ -49,6 +49,7 @@ require "sidekiq_unique_jobs/cli"
49
49
  require "sidekiq_unique_jobs/core_ext"
50
50
  require "sidekiq_unique_jobs/lock_timeout"
51
51
  require "sidekiq_unique_jobs/lock_ttl"
52
+ require "sidekiq_unique_jobs/lock_type"
52
53
  require "sidekiq_unique_jobs/lock_args"
53
54
  require "sidekiq_unique_jobs/lock_digest"
54
55
  require "sidekiq_unique_jobs/unlockable"
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: 8.0.2
4
+ version: 8.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-13 00:00:00.000000000 Z
11
+ date: 2023-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: brpoplpush-redis_script
@@ -137,6 +137,7 @@ files:
137
137
  - lib/sidekiq_unique_jobs/lock_info.rb
138
138
  - lib/sidekiq_unique_jobs/lock_timeout.rb
139
139
  - lib/sidekiq_unique_jobs/lock_ttl.rb
140
+ - lib/sidekiq_unique_jobs/lock_type.rb
140
141
  - lib/sidekiq_unique_jobs/locksmith.rb
141
142
  - lib/sidekiq_unique_jobs/logging.rb
142
143
  - lib/sidekiq_unique_jobs/logging/middleware_context.rb
@@ -233,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
234
  - !ruby/object:Gem::Version
234
235
  version: '0'
235
236
  requirements: []
236
- rubygems_version: 3.4.7
237
+ rubygems_version: 3.4.20
237
238
  signing_key:
238
239
  specification_version: 4
239
240
  summary: Sidekiq middleware that prevents duplicates jobs