sidekiq-unique-jobs 6.0.25 → 7.0.0.beta2

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.

Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -20
  3. data/README.md +349 -112
  4. data/lib/sidekiq-unique-jobs.rb +2 -0
  5. data/lib/sidekiq_unique_jobs.rb +43 -6
  6. data/lib/sidekiq_unique_jobs/batch_delete.rb +121 -0
  7. data/lib/sidekiq_unique_jobs/changelog.rb +71 -0
  8. data/lib/sidekiq_unique_jobs/cli.rb +20 -29
  9. data/lib/sidekiq_unique_jobs/config.rb +193 -0
  10. data/lib/sidekiq_unique_jobs/connection.rb +5 -4
  11. data/lib/sidekiq_unique_jobs/constants.rb +36 -24
  12. data/lib/sidekiq_unique_jobs/core_ext.rb +38 -0
  13. data/lib/sidekiq_unique_jobs/digests.rb +78 -93
  14. data/lib/sidekiq_unique_jobs/exceptions.rb +152 -8
  15. data/lib/sidekiq_unique_jobs/job.rb +3 -3
  16. data/lib/sidekiq_unique_jobs/json.rb +34 -0
  17. data/lib/sidekiq_unique_jobs/key.rb +93 -0
  18. data/lib/sidekiq_unique_jobs/lock.rb +295 -0
  19. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +49 -43
  20. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  21. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  22. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +8 -17
  23. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +5 -5
  24. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +1 -23
  25. data/lib/sidekiq_unique_jobs/lock/validator.rb +65 -0
  26. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +12 -8
  27. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +1 -1
  28. data/lib/sidekiq_unique_jobs/lock_config.rb +95 -0
  29. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  30. data/lib/sidekiq_unique_jobs/locksmith.rb +255 -99
  31. data/lib/sidekiq_unique_jobs/logging.rb +148 -22
  32. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  33. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  34. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +46 -0
  35. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  36. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  37. data/lib/sidekiq_unique_jobs/lua/find_digest_in_sorted_set.lua +24 -0
  38. data/lib/sidekiq_unique_jobs/lua/lock.lua +91 -0
  39. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  40. data/lib/sidekiq_unique_jobs/lua/queue.lua +83 -0
  41. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +86 -0
  42. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  43. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  44. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +19 -0
  45. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  46. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +46 -0
  47. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/find_digest_in_sorted_set.lua +24 -0
  51. data/lib/sidekiq_unique_jobs/lua/unlock.lua +99 -0
  52. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  53. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  54. data/lib/sidekiq_unique_jobs/middleware.rb +62 -31
  55. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  56. data/lib/sidekiq_unique_jobs/middleware/server.rb +27 -0
  57. data/lib/sidekiq_unique_jobs/normalizer.rb +3 -3
  58. data/lib/sidekiq_unique_jobs/on_conflict.rb +22 -9
  59. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +8 -4
  60. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +59 -13
  61. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +42 -13
  62. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +4 -4
  63. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +24 -5
  64. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +47 -23
  65. data/lib/sidekiq_unique_jobs/orphans/manager.rb +100 -0
  66. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  67. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +201 -0
  68. data/lib/sidekiq_unique_jobs/profiler.rb +51 -0
  69. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  70. data/lib/sidekiq_unique_jobs/redis/entity.rb +94 -0
  71. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  72. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  73. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  74. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +59 -0
  75. data/lib/sidekiq_unique_jobs/redis/string.rb +49 -0
  76. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +19 -0
  77. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +43 -0
  78. data/lib/sidekiq_unique_jobs/{scripts.rb → script.rb} +43 -29
  79. data/lib/sidekiq_unique_jobs/script/caller.rb +125 -0
  80. data/lib/sidekiq_unique_jobs/script/template.rb +41 -0
  81. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +92 -65
  82. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +166 -28
  83. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +10 -11
  84. data/lib/sidekiq_unique_jobs/testing.rb +47 -15
  85. data/lib/sidekiq_unique_jobs/time_calculator.rb +103 -0
  86. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  87. data/lib/sidekiq_unique_jobs/unique_args.rb +19 -21
  88. data/lib/sidekiq_unique_jobs/unlockable.rb +11 -2
  89. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  90. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +151 -0
  91. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  92. data/lib/sidekiq_unique_jobs/version_check.rb +1 -1
  93. data/lib/sidekiq_unique_jobs/web.rb +25 -19
  94. data/lib/sidekiq_unique_jobs/web/helpers.rb +98 -6
  95. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  96. data/lib/sidekiq_unique_jobs/web/views/locks.erb +52 -0
  97. data/lib/tasks/changelog.rake +4 -3
  98. metadata +70 -35
  99. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  100. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  101. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  102. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  103. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  104. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  105. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  106. data/redis/acquire_lock.lua +0 -21
  107. data/redis/convert_legacy_lock.lua +0 -13
  108. data/redis/delete.lua +0 -14
  109. data/redis/delete_by_digest.lua +0 -23
  110. data/redis/delete_job_by_digest.lua +0 -60
  111. data/redis/lock.lua +0 -62
  112. data/redis/release_stale_locks.lua +0 -90
  113. data/redis/unlock.lua +0 -35
data/README.md CHANGED
@@ -1,45 +1,57 @@
1
- # SidekiqUniqueJobs [![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://travis-ci.com/mhenrixon/sidekiq-unique-jobs.svg?branch=v6.x)](https://travis-ci.com/mhenrixon/sidekiq-unique-jobs) [![Code Climate](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs.png)](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)
1
+ # SidekiqUniqueJobs [![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://travis-ci.org/mhenrixon/sidekiq-unique-jobs.png?branch=master)](https://travis-ci.org/mhenrixon/sidekiq-unique-jobs) [![Code Climate](https://codeclimate.com/github/mhenrixon/sidekiq-unique-jobs.png)](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)
2
2
 
3
3
  <!-- MarkdownTOC -->
4
4
 
5
5
  - [Introduction](#introduction)
6
- - [Documentation](#documentation)
7
6
  - [Requirements](#requirements)
8
- - [ActiveJob](#activejob)
9
- - [redis-namespace](#redis-namespace)
10
7
  - [Installation](#installation)
11
8
  - [Support Me](#support-me)
12
9
  - [General Information](#general-information)
13
- - [Options](#options)
14
- - [Lock Expiration](#lock-expiration)
15
- - [Lock Timeout](#lock-timeout)
16
- - [Unique Across Queues](#unique-across-queues)
17
- - [Unique Across Workers](#unique-across-workers)
10
+ - [Global Configuration](#global-configuration)
11
+ - [debug_lua](#debug_lua)
12
+ - [lock_timeout](#lock_timeout)
13
+ - [lock_ttl](#lock_ttl)
14
+ - [enabled](#enabled)
15
+ - [logger](#logger)
16
+ - [max_history](#max_history)
17
+ - [reaper](#reaper)
18
+ - [reaper_count](#reaper_count)
19
+ - [reaper_interval](#reaper_interval)
20
+ - [reaper_timeout](#reaper_timeout)
21
+ - [unique_prefix](#unique_prefix)
22
+ - [lock_info](#lock_info)
23
+ - [Worker Configuration](#worker-configuration)
24
+ - [lock_ttl](#lock_ttl-1)
25
+ - [lock_timeout](#lock_timeout-1)
26
+ - [unique_across_queues](#uniqueacrossqueues)
27
+ - [unique_across_workers](#uniqueacrossworkers)
18
28
  - [Locks](#locks)
19
29
  - [Until Executing](#until-executing)
20
30
  - [Until Executed](#until-executed)
21
- - [Until Timeout](#until-timeout)
22
- - [Unique Until And While Executing](#unique-until-and-while-executing)
31
+ - [Until Expired](#until-expired)
32
+ - [Until And While Executing](#until-and-while-executing)
23
33
  - [While Executing](#while-executing)
34
+ - [Custom Locks](#custom-locks)
24
35
  - [Conflict Strategy](#conflict-strategy)
25
- - [Log](#log)
26
- - [Raise](#raise)
27
- - [Reject](#reject)
28
- - [Replace](#replace)
36
+ - [log](#log)
37
+ - [raise](#raise)
38
+ - [reject](#reject)
39
+ - [replace](#replace)
29
40
  - [Reschedule](#reschedule)
41
+ - [Custom Strategies](#custom-strategies)
30
42
  - [Usage](#usage)
31
43
  - [Finer Control over Uniqueness](#finer-control-over-uniqueness)
32
44
  - [After Unlock Callback](#after-unlock-callback)
33
45
  - [Logging](#logging)
34
46
  - [Cleanup Dead Locks](#cleanup-dead-locks)
35
- - [Other Sidekiq gems](#other-sidekiq-gems)
36
- - [sidekiq-global_id](#sidekiq-global_id)
37
47
  - [Debugging](#debugging)
38
48
  - [Sidekiq Web](#sidekiq-web)
39
- - [Show Unique Digests](#show-unique-digests)
40
- - [Show keys for digest](#show-keys-for-digest)
49
+ - [Show Locks](#show-locks)
50
+ - [Show Lock](#show-lock)
41
51
  - [Communication](#communication)
42
52
  - [Testing](#testing)
53
+ - [Unique Sidekiq Configuration](#unique-sidekiq-configuration)
54
+ - [Uniqueness](#uniqueness)
43
55
  - [Contributing](#contributing)
44
56
  - [Contributors](#contributors)
45
57
 
@@ -49,52 +61,50 @@
49
61
 
50
62
  The goal of this gem is to ensure your Sidekiq jobs are unique. We do this by creating unique keys in Redis based on how you configure uniqueness.
51
63
 
52
- ## Documentation
64
+ This is the documentation for the master branch. You can find the documentation for each release by navigating to its tag.
53
65
 
54
- This is the documentation for the master branch. You can find the documentation for each release by navigating to its tag: [v5.0.10][]
66
+ Here are links to some of the old versions
55
67
 
56
- Below are links to the latest major versions (4 & 5):
57
-
58
- - [v5.0.10][]
59
- - [v4.0.18][]
68
+ - [v6.0.13](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v6.0.13)
69
+ - [v5.0.10](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v5.0.10)
70
+ - [v4.0.18](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v4.0.18)
60
71
 
61
72
  ## Requirements
62
73
 
63
- See [Sidekiq requirements][] for what is required. Starting from 5.0.0 only sidekiq >= 4 and MRI >= 2.2. ActiveJob is not supported
64
-
65
- ### ActiveJob
66
-
67
- Version 6 requires Redis >= 3 and pure Sidekiq, no ActiveJob supported anymore. See [About ActiveJob](https://github.com/mhenrixon/sidekiq-unique-jobs/wiki/About-ActiveJob) for why. It simply is too complex and generates more issues than I can handle given how little timer I have to spend on this project.
74
+ - Sidekiq `>= 4.0` (`>= 5.2` recommended)
75
+ - Ruby:
76
+ - MRI `>= 2.3` (`>= 2.5` recommended)
77
+ - JRuby `>= 9.0` (`>= 9.2` recommended)
78
+ - Truffleruby
79
+ - Redis Server `>= 3.0.2` (`>= 3.2` recommended)
80
+ - [ActiveJob officially not supported][48]
81
+ - [redis-namespace officially not supported][49]
68
82
 
69
- ### redis-namespace
70
-
71
- Will not be officially supported anymore. Since Mike [won't support redis-namespace](https://github.com/mperham/sidekiq/issues/3366#issuecomment-284270120) neither will I.
72
-
73
- [Read this](http://www.mikeperham.com/2017/04/10/migrating-from-redis-namespace/) for how to migrate away from namespacing.
83
+ See [Sidekiq requirements][24] for detailed requirements of Sidekiq itself (be sure to check the right sidekiq version).
74
84
 
75
85
  ## Installation
76
86
 
77
87
  Add this line to your application's Gemfile:
78
88
 
79
- ```
89
+ ```ruby
80
90
  gem 'sidekiq-unique-jobs'
81
91
  ```
82
92
 
83
93
  And then execute:
84
94
 
85
- ```
95
+ ```bash
86
96
  bundle
87
97
  ```
88
98
 
89
99
  Or install it yourself as:
90
100
 
91
- ```
101
+ ```bash
92
102
  gem install sidekiq-unique-jobs
93
103
  ```
94
104
 
95
105
  ## Support Me
96
106
 
97
- Want to show me some ❤️ for the hard work I do on this gem? You can use the following [PayPal link][]. Any amount is welcome and let me tell you it feels good to be appreciated. Even a dollar makes me super excited about all of this.
107
+ Want to show me some ❤️ for the hard work I do on this gem? You can use the following PayPal link: [https://paypal.me/mhenrixon1](https://paypal.me/mhenrixon1). Any amount is welcome and let me tell you it feels good to be appreciated. Even a dollar makes me super excited about all of this.
98
108
 
99
109
  ## General Information
100
110
 
@@ -102,20 +112,147 @@ See [Interaction w/ Sidekiq](https://github.com/mhenrixon/sidekiq-unique-jobs/wi
102
112
 
103
113
  See [Locking & Unlocking](https://github.com/mhenrixon/sidekiq-unique-jobs/wiki/Locking-&-Unlocking) for an overview of the differences on when the various lock types are locked and unlocked.
104
114
 
105
- ## Options
115
+ ## Global Configuration
116
+
117
+ The gem supports a few different configuration options that might be of interest if you run into some weird issues.
118
+
119
+ ```ruby
120
+ SidekiqUniqueJobs.configure do |config|
121
+ config.debug_lua = true
122
+ config.lock_info = true
123
+ config.lock_ttl = 10.minutes
124
+ config.lock_timeout = 10.minutes
125
+ config.logger = Sidekiq.logger
126
+ config.max_history = 10_000
127
+ config.reaper = :lua
128
+ config.reaper_count = 100
129
+ config.reaper_interval = 10
130
+ config.reaper_timeout = 5
131
+ end
132
+ ```
133
+
134
+ ### debug_lua
135
+
136
+ ```ruby
137
+ SidekiqUniqueJobs.config.debug_lua #=> false
138
+ ```
139
+
140
+ Turning on debug_lua will allow the lua scripts to output debug information about what the lua scripts do. It will log all redis commands that are executed and also some helpful messages about what is going on inside the lua script.
141
+
142
+ ### lock_timeout
143
+
144
+ ```ruby
145
+ SidekiqUniqueJobs.config.lock_timeout #=> 0
146
+ ```
147
+
148
+ Set a global lock_timeout to use for all jobs that don't otherwise specify a lock_timeout.
149
+
150
+ Lock timeout decides how long to wait for acquiring the lock. A value of nil means to wait indefinitely for a lock resource to become available.
151
+
152
+ ### lock_ttl
153
+
154
+ ```ruby
155
+ SidekiqUniqueJobs.config.lock_ttl #=> nil
156
+ ```
157
+
158
+ Set a global lock_ttl to use for all jobs that don't otherwise specify a lock_ttl.
159
+
160
+ Lock TTL decides how long to wait after the job has been successfully processed before making it possible to reuse that lock.
161
+
162
+ ### enabled
163
+
164
+ ```ruby
165
+ SidekiqUniqueJobs.config.enabled #=> true
166
+ ```
167
+
168
+ Globally turn the locking mechanism on or off.
169
+
170
+ ### logger
171
+
172
+ ```ruby
173
+ SidekiqUniqueJobs.config.logger #=> #<Sidekiq::Logger:0x00007fdc1f96d180>
174
+ ```
175
+
176
+ By default this gem piggybacks on the Sidekiq logger. It is not recommended to change this as the gem uses some features in the Sidekiq logger and you might run into problems. If you need a different logger and you do run into problems then get in touch and we'll see what we can do about it.
106
177
 
107
- ### Lock Expiration
178
+ ### max_history
108
179
 
109
- Lock expiration is used for two things. For the `UntilExpired` job releases the lock upon expiry. This is done from the client.
180
+ ```ruby
181
+ SidekiqUniqueJobs.config.max_history #=> 1_000
182
+ ```
183
+
184
+ The max_history setting can be used to tweak the number of changelogs generated. It can also be completely turned off if performance suffers or if you are just not interested in using the changelog.
110
185
 
111
- Since v6.0.11 the other locks will expire after the server is done processing.
186
+ This is a log that can be accessed by a lock to see what happened for that lock. Any items after the configured `max_history` will be automatically deleted as new items are added.
187
+
188
+ ### reaper
112
189
 
113
190
  ```ruby
114
- sidekiq_options lock_expiration: nil # default - don't expire keys
115
- sidekiq_options lock_expiration: 20.days.to_i # expire this lock in 20 days
191
+ SidekiqUniqueJobs.config.reaper #=> :ruby
116
192
  ```
117
193
 
118
- ### Lock Timeout
194
+ If using the orphans cleanup process it is critical to be aware of the following. The `:ruby` job is much slower but the `:lua` job locks redis while executing. While doing intense processing it is best to avoid locking redis with a lua script. There for the batch size (controlled by the `reaper_count` setting) needs to be reduced.
195
+
196
+ In my benchmarks deleting 1000 orphaned locks with lua performs around 65% faster than deleting 1000 keys in ruby.
197
+
198
+ On the other hand if I increase it to 10 000 orphaned locks per cleanup (`reaper_count: 10_0000`) then redis starts throwing:
199
+
200
+ > BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (Redis::CommandError)
201
+
202
+ ### reaper_count
203
+
204
+ ```ruby
205
+ SidekiqUniqueJobs.config.reaper_count #=> 1_000
206
+ ```
207
+
208
+ The reaper_count setting configures how many orphans at a time will be cleaned up by the orphan cleanup job. This might have to be tweaked depending on which orphan job is running.
209
+
210
+ ### reaper_interval
211
+
212
+ ```ruby
213
+ SidekiqUniqueJobs.config.reaper_interval #=> 600
214
+ ```
215
+
216
+ The number of seconds between reaping.
217
+
218
+ ### reaper_timeout
219
+
220
+ ```ruby
221
+ SidekiqUniqueJobs.config.reaper_timeout #=> 10
222
+ ```
223
+
224
+ The number of seconds to wait for the reaper to finish before raising a TimeoutError. This is done to ensure that the next time we reap isn't getting stuck due to the previous process already running.
225
+
226
+ ### unique_prefix
227
+
228
+ ```ruby
229
+ SidekiqUniqueJobs.config.unique_prefix #=> "uniquejobs"
230
+ ```
231
+
232
+ Use if you want a different key prefix for the keys in redis.
233
+
234
+ ### lock_info
235
+
236
+ ```ruby
237
+ SidekiqUniqueJobs.config.lock_info #=> false
238
+ ```
239
+
240
+ Using lock info will create an additional key for the lock with a json object containing information about the lock. This will be presented in the web interface and might help track down why some jobs are getting stuck.
241
+
242
+ ## Worker Configuration
243
+
244
+ ### lock_ttl
245
+
246
+ Lock TTL decides how long to wait after the job has been successfully processed before making it possible to reuse that lock.
247
+
248
+ Since `v6.0.11` the other locks will expire after the server is done processing.
249
+
250
+ ```ruby
251
+ sidekiq_options lock_ttl: nil # default - don't expire keys
252
+ sidekiq_options lock_ttl: 20.days.to_i # expire this lock in 20 days
253
+ ```
254
+
255
+ ### lock_timeout
119
256
 
120
257
  This is the timeout (how long to wait) when creating the lock. By default we don't use a timeout so we won't wait for the lock to be created. If you want it is possible to set this like below.
121
258
 
@@ -125,7 +262,7 @@ sidekiq_options lock_timeout: 5 # wait 5 seconds
125
262
  sidekiq_options lock_timeout: nil # lock indefinitely, this process won't continue until it gets a lock. VERY DANGEROUS!!
126
263
  ```
127
264
 
128
- ### Unique Across Queues
265
+ ### unique_across_queues
129
266
 
130
267
  This configuration option is slightly misleading. It doesn't disregard the queue on other jobs. Just on itself, this means that a worker that might schedule jobs into multiple queues will be able to have uniqueness enforced on all queues it is pushed to.
131
268
 
@@ -141,9 +278,9 @@ end
141
278
 
142
279
  Now if you push override the queue with `Worker.set(queue: 'another').perform_async(1)` it will still be considered unique when compared to `Worker.perform_async(1)` (that was actually pushed to the queue `default`).
143
280
 
144
- ### Unique Across Workers
281
+ ### unique_across_workers
145
282
 
146
- This configuration option is slightly misleading. It doesn't disregard the worker class on other jobs. Just on itself, this means that a worker that the worker class won't be used for generating the unique digest. The only way this option really makes sense is when you want to have uniqueness between two different worker classes.
283
+ This configuration option is slightly misleading. It doesn't disregard the worker class on other jobs. Just on itself, this means that the worker class won't be used for generating the unique digest. The only way this option really makes sense is when you want to have uniqueness between two different worker classes.
147
284
 
148
285
  ```ruby
149
286
  class WorkerOne
@@ -174,53 +311,55 @@ WorkerTwo.perform_async(1)
174
311
 
175
312
  ### Until Executing
176
313
 
314
+ ```ruby
315
+ sidekiq_options lock: :until_executing
316
+ ```
317
+
177
318
  Locks from when the client pushes the job to the queue. Will be unlocked before the server starts processing the job.
178
319
 
179
320
  **NOTE** this is probably not so good for jobs that shouldn't be running simultaneously (aka slow jobs).
180
321
 
181
- ```ruby
182
- sidekiq_options lock: :until_executing
183
- ```
322
+ The reason this type of lock exists is to fix the following problem: [sidekiq/issues/3471](https://github.com/mperham/sidekiq/issues/3471#issuecomment-300866335)
184
323
 
185
324
  ### Until Executed
186
325
 
187
- Locks from when the client pushes the job to the queue. Will be unlocked when the server has successfully processed the job.
188
-
189
326
  ```ruby
190
327
  sidekiq_options lock: :until_executed
191
328
  ```
192
329
 
193
- ### Until Timeout
330
+ Locks from when the client pushes the job to the queue. Will be unlocked when the server has successfully processed the job.
194
331
 
195
- Locks from when the client pushes the job to the queue. Will be unlocked when the specified timeout has been reached.
332
+ ### Until Expired
196
333
 
197
334
  ```ruby
198
335
  sidekiq_options lock: :until_expired
199
336
  ```
200
337
 
201
- ### Unique Until And While Executing
338
+ Locks from when the client pushes the job to the queue. Will be unlocked when the specified timeout has been reached.
202
339
 
203
- Locks when the client pushes the job to the queue. The queue will be unlocked when the server starts processing the job. The server then goes on to creating a runtime lock for the job to prevent simultaneous jobs from being executed. As soon as the server starts processing a job, the client can push the same job to the queue.
340
+ ### Until And While Executing
204
341
 
205
342
  ```ruby
206
343
  sidekiq_options lock: :until_and_while_executing
207
344
  ```
208
345
 
346
+ Locks when the client pushes the job to the queue. The queue will be unlocked when the server starts processing the job. The server then goes on to creating a runtime lock for the job to prevent simultaneous jobs from being executed. As soon as the server starts processing a job, the client can push the same job to the queue.
347
+
209
348
  ### While Executing
210
349
 
350
+ ```ruby
351
+ sidekiq_options lock: :while_executing, lock_timeout: 10
352
+ ```
353
+
211
354
  With this lock type it is possible to put any number of these jobs on the queue, but as the server pops the job from the queue it will create a lock and then wait until other locks are done processing. It _looks_ like multiple jobs are running at the same time but in fact the second job will only be waiting for the first job to finish.
212
355
 
213
356
  **NOTE** Unless this job is configured with a `lock_timeout: nil` or `lock_timeout: > 0` then all jobs that are attempted to be executed will just be dropped without waiting.
214
357
 
215
- ```ruby
216
- sidekiq_options lock: :while_executing, lock_timeout: nil
217
- ```
218
-
219
- There is an example of this to try it out in the `rails_example` application. Run `foreman start` in the root of the directory and open the url: `localhost:5000/work/duplicate_while_executing`.
358
+ There is an example of this to try it out in the `my_app` application. Run `foreman start` in the root of the directory and open the url: `localhost:5000/work/duplicate_while_executing`.
220
359
 
221
360
  In the console you should see something like:
222
361
 
223
- ```
362
+ ```bash
224
363
  0:32:24 worker.1 | 2017-04-23T08:32:24.955Z 84404 TID-ougq4thko WhileExecutingWorker JID-400ec51c9523f41cd4a35058 INFO: start
225
364
  10:32:24 worker.1 | 2017-04-23T08:32:24.956Z 84404 TID-ougq8csew WhileExecutingWorker JID-8d6d9168368eedaed7f75763 INFO: start
226
365
  10:32:24 worker.1 | 2017-04-23T08:32:24.957Z 84404 TID-ougq8crt8 WhileExecutingWorker JID-affcd079094c9b26e8b9ba60 INFO: start
@@ -235,31 +374,76 @@ In the console you should see something like:
235
374
  10:33:04 worker.1 | 2017-04-23T08:33:04.973Z 84404 TID-ougq8cs8s WhileExecutingWorker JID-9e197460c067b22eb1b5d07f INFO: done: 40.014 sec
236
375
  ```
237
376
 
377
+ ### Custom Locks
378
+
379
+ You may need to define some custom lock. You can define it in one project folder:
380
+
381
+ ```ruby
382
+ # lib/locks/my_custom_lock.rb
383
+ module Locks
384
+ class MyCustomLock < SidekiqUniqueJobs::Lock::BaseLock
385
+ def execute
386
+ # Do something ...
387
+ end
388
+ end
389
+ end
390
+ ```
391
+
392
+ You can refer on all the locks defined in `lib/sidekiq_unique_jobs/lock/*.rb`.
393
+
394
+ In order to make it available, you should call in your project startup:
395
+
396
+ ```ruby
397
+ # For rails application
398
+ # config/initializers/sidekiq_unique_jobs.rb
399
+ # For other projects, whenever you prefer
400
+
401
+ SidekiqUniqueJobs.configure do |config|
402
+ config.add_lock :my_custom_lock, Locks::MyCustomLock
403
+ end
404
+ ```
405
+
406
+ And then you can use it in the jobs definition:
407
+
408
+ `sidekiq_options lock: :my_custom_lock, on_conflict: :log`
409
+
410
+ Please not that if you try to override a default lock, an `ArgumentError` will be raised.
411
+
238
412
  ## Conflict Strategy
239
413
 
240
414
  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.
241
415
 
242
416
  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
243
417
 
244
- ### Log
418
+ ### log
419
+
420
+ ```ruby
421
+ sidekiq_options on_conflict: :log`
422
+ ```
245
423
 
246
424
  This strategy is intended to be used with `UntilExecuted` and `UntilExpired`. It will log a line about that this is job is a duplicate of another.
247
425
 
248
- `sidekiq_options lock: :until_executed, on_conflict: :log`
426
+ ### raise
249
427
 
250
- ### Raise
428
+ ```ruby
429
+ sidekiq_options on_conflict: :raise`
430
+ ```
251
431
 
252
432
  This strategy is intended to be used with `WhileExecuting`. Basically it will allow us to let the server process crash with a specific error message and be retried without messing up the Sidekiq stats.
253
433
 
254
- `sidekiq_options lock: :while_executing, on_conflict: :raise, retry: 10`
434
+ ### reject
255
435
 
256
- ### Reject
436
+ ```ruby
437
+ sidekiq_options on_conflict: :reject`
438
+ ```
257
439
 
258
440
  This strategy is intended to be used with `WhileExecuting` and will push the job to the dead queue on conflict.
259
441
 
260
- `sidekiq_options lock: :while_executing, on_conflict: :reject`
442
+ ### replace
261
443
 
262
- ### Replace
444
+ ```ruby
445
+ sidekiq_options on_conflict: :replace`
446
+ ```
263
447
 
264
448
  This strategy is intended to be used with client locks like `UntilExecuted`.
265
449
  It will delete any existing job for these arguments from retry, schedule and
@@ -268,13 +452,50 @@ queue and retry the lock again.
268
452
  This is slightly dangerous and should probably only be used for jobs that are
269
453
  always scheduled in the future. Currently only attempting to retry one time.
270
454
 
271
- `sidekiq_options lock: :until_executed, on_conflict: :replace`
272
-
273
455
  ### Reschedule
274
456
 
457
+ ```ruby
458
+ sidekiq_options on_conflict: :reschedule`
459
+ ```
460
+
275
461
  This strategy is intended to be used with `WhileExecuting` and will delay the job to be tried again in 5 seconds. This will mess up the sidekiq stats but will prevent exceptions from being logged and confuse your sysadmins.
276
462
 
277
- `sidekiq_options lock: :while_executing, on_conflict: :reschedule`
463
+ ### Custom Strategies
464
+
465
+ You may need to define some custom strategy. You can define it in one project folder:
466
+
467
+ ```ruby
468
+ # lib/strategies/my_custom_strategy.rb
469
+ module Strategies
470
+ class MyCustomStrategy < OnConflict::Strategy
471
+ def call
472
+ # Do something ...
473
+ end
474
+ end
475
+ end
476
+ ```
477
+
478
+ You can refer on all the startegies defined in `lib/sidekiq_unique_jobs/on_conflict`.
479
+
480
+ In order to make it available, you should call in your project startup:
481
+
482
+ ```ruby
483
+ # For rails application
484
+ # config/initializers/sidekiq_unique_jobs.rb
485
+ # For other projects, whenever you prefer
486
+
487
+ SidekiqUniqueJobs.configure do |config|
488
+ config.add_strategy :my_custom_strategy, Strategies::MyCustomStrategy
489
+ end
490
+ ```
491
+
492
+ And then you can use it in the jobs definition:
493
+
494
+ ```ruby
495
+ sidekiq_options lock: :while_executing, on_conflict: :my_custom_strategy
496
+ ```
497
+
498
+ Please not that if you try to override a default lock, an `ArgumentError` will be raised.
278
499
 
279
500
  ## Usage
280
501
 
@@ -290,7 +511,9 @@ Requiring the gem in your gemfile should be sufficient to enable unique jobs.
290
511
 
291
512
  Sometimes it is desired to have a finer control over which arguments are used in determining uniqueness of the job, and others may be _transient_. For this use-case, you need to define either a `unique_args` method, or a ruby proc.
292
513
 
293
- The unique_args method need to return an array of values to use for uniqueness check.
514
+ *NOTE:* The unique_args method need to return an array of values to use for uniqueness check.
515
+
516
+ *NOTE:* The arguments passed to the proc or the method is always an array. If your method takes a single array as argument the value of args will be `[[...]]`.
294
517
 
295
518
  The method or the proc can return a modified version of args without the transient arguments included, as shown below:
296
519
 
@@ -318,7 +541,7 @@ class UniqueJobWithFilterProc
318
541
  end
319
542
  ```
320
543
 
321
- It is also quite possible to ensure different types of unique args based on context. I can't vouch for the below example but see [#203](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/203) for the discussion.
544
+ It is possible to ensure different types of unique args based on context. I can't vouch for the below example but see [#203](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/203) for the discussion.
322
545
 
323
546
  ```ruby
324
547
  class UniqueJobWithFilterMethod
@@ -341,20 +564,14 @@ end
341
564
 
342
565
  If you need to perform any additional work after the lock has been released you can provide an `#after_unlock` instance method. The method will be called when the lock has been unlocked. Most times this means after yield but there are two exceptions to that.
343
566
 
344
- **Exception 1:** UntilExecuting unlocks and calls back before yielding.
567
+ **Exception 1:** UntilExecuting unlocks and uses callback before yielding.
345
568
  **Exception 2:** UntilExpired expires eventually, no after_unlock hook is called.
346
569
 
347
- **NOTE:** _It is also possible to write this code as a class method._
348
-
349
570
  ```ruby
350
571
  class UniqueJobWithFilterMethod
351
572
  include Sidekiq::Worker
352
573
  sidekiq_options lock: :while_executing,
353
574
 
354
- def self.after_unlock
355
- # block has yielded and lock is released
356
- end
357
-
358
575
  def after_unlock
359
576
  # block has yielded and lock is released
360
577
  end
@@ -370,7 +587,7 @@ To see logging in sidekiq when duplicate payload has been filtered out you can e
370
587
  class UniqueJobWithFilterMethod
371
588
  include Sidekiq::Worker
372
589
  sidekiq_options lock: :while_executing,
373
- log_duplicate_payload: true
590
+ log_duplicate: true
374
591
 
375
592
  ...
376
593
 
@@ -384,7 +601,7 @@ For sidekiq versions before 5.1 a `sidekiq_retries_exhausted` block is required
384
601
  ```ruby
385
602
  class MyWorker
386
603
  sidekiq_retries_exhausted do |msg, _ex|
387
- SidekiqUniqueJobs::Digests.delete_by_digest(msg['unique_digest']) if msg['unique_digest']
604
+ SidekiqUniqueJobs::Digests.del(digest: msg['unique_digest']) if msg['unique_digest']
388
605
  end
389
606
  end
390
607
  ```
@@ -395,29 +612,11 @@ Starting in v5.1, Sidekiq can also fire a global callback when a job dies:
395
612
  # this goes in your initializer
396
613
  Sidekiq.configure_server do |config|
397
614
  config.death_handlers << ->(job, _ex) do
398
- SidekiqUniqueJobs::Digests.delete_by_digest(job['unique_digest']) if job['unique_digest']
615
+ SidekiqUniqueJobs::Digests.del(digest: job['unique_digest']) if job['unique_digest']
399
616
  end
400
617
  end
401
618
  ```
402
619
 
403
- ### Other Sidekiq gems
404
-
405
- #### sidekiq-global_id
406
-
407
- It was reported in [#235](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/235) that the order of the Sidekiq middleware needs to be as follows.
408
-
409
- ```ruby
410
- Sidekiq.client_middleware do |chain|
411
- chain.add Sidekiq::GlobalId::ClientMiddleware
412
- chain.add SidekiqUniqueJobs::Client::Middleware
413
- end
414
-
415
- Sidekiq.server_middleware do |chain|
416
- chain.add SidekiqUniqueJobs::Server::Middleware
417
- chain.add Sidekiq::GlobalId::ServerMiddleware
418
- end
419
- ```
420
-
421
620
  ## Debugging
422
621
 
423
622
  There are several ways of removing keys that are stuck. The prefered way is by using the unique extension to `Sidekiq::Web`. The old console and command line versions still work but might be deprecated in the future. It is better to search for the digest itself and delete the keys matching that digest.
@@ -437,13 +636,13 @@ already does this.
437
636
 
438
637
  To filter/search for keys we can use the wildcard `*`. If we have a unique digest `'uniquejobs:9e9b5ce5d423d3ea470977004b50ff84` we can search for it by enter `*ff84` and it should return all digests that end with `ff84`.
439
638
 
440
- #### Show Unique Digests
639
+ #### Show Locks
441
640
 
442
- ![Unique Digests](assets/unique_digests_1.png)
641
+ ![Locks](assets/unique_digests_1.png)
443
642
 
444
- #### Show keys for digest
643
+ #### Show Lock
445
644
 
446
- ![Unique Digests](assets/unique_digests_2.png)
645
+ ![Lock](assets/unique_digests_2.png)
447
646
 
448
647
  ## Communication
449
648
 
@@ -451,9 +650,46 @@ There is a [![Join the chat at https://gitter.im/mhenrixon/sidekiq-unique-jobs](
451
650
 
452
651
  ## Testing
453
652
 
454
- 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.
653
+ ### Unique Sidekiq Configuration
455
654
 
456
- [Enterprise unique jobs][]
655
+ 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.
656
+
657
+ Let's take a _bad_ worker:
658
+
659
+ ```ruby
660
+ # app/workers/bad_worker.rb
661
+ class BadWorker
662
+ sidekiq_options lock: :while_executing, on_conflict: :replace
663
+ end
664
+
665
+ # spec/workers/bad_worker_spec.rb
666
+
667
+ require "sidekiq_unique_jobs/testing"
668
+ # OR
669
+ require "sidekiq_unique_jobs/rspec/matchers"
670
+
671
+ RSpec.describe BadWorker do
672
+ specify { expect(described_class).to have_valid_sidekiq_options }
673
+ end
674
+ ```
675
+
676
+ This gives us a helpful error message for a wrongly configured worker:
677
+
678
+ ```bash
679
+ Expected BadWorker to have valid sidekiq options but found the following problems:
680
+ on_server_conflict: :replace is incompatible with the server process
681
+ ```
682
+
683
+ If you are not using RSpec (a lot of people prefer minitest or test unit) you can do something like:
684
+
685
+ ```ruby
686
+ assert SidekiqUniqueJobs.validate_worker!(BadWorker.get_sidekiq_options)
687
+ ```
688
+
689
+
690
+ ### Uniqueness
691
+
692
+ 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][]
457
693
 
458
694
  ```ruby
459
695
  SidekiqUniqueJobs.configure do |config|
@@ -464,6 +700,8 @@ end
464
700
  If you truly wanted to test the sidekiq client push you could do something like below. Note that it will only work for the jobs that lock when the client pushes the job to redis (UntilExecuted, UntilAndWhileExecuting and UntilExpired).
465
701
 
466
702
  ```ruby
703
+ require "sidekiq_unique_jobs/testing"
704
+
467
705
  RSpec.describe Workers::CoolOne do
468
706
  before do
469
707
  SidekiqUniqueJobs.config.enabled = false
@@ -491,7 +729,7 @@ RSpec.describe Workers::CoolOne do
491
729
  end
492
730
  ```
493
731
 
494
- I would strongly suggest you let this gem test uniqueness. If you care about how the gem is integration tested have a look at the following specs:
732
+ It is recommened to leave the uniqueness testing to the gem maintainers. If you care about how the gem is integration tested have a look at the following specs:
495
733
 
496
734
  - [spec/integration/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/integration/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb)
497
735
  - [spec/integration/sidekiq_unique_jobs/lock/until_executed_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/integration/sidekiq_unique_jobs/lock/until_executed_spec.rb)
@@ -516,4 +754,3 @@ You can find a list of contributors over on [Contributors][]
516
754
  [Sidekiq requirements]: https://github.com/mperham/sidekiq#requirements
517
755
  [Enterprise unique jobs]: https://www.dailydrip.com/topics/sidekiq/drips/sidekiq-enterprise-unique-jobs
518
756
  [Contributors]: https://github.com/mhenrixon/sidekiq-unique-jobs/graphs/contributors
519
- [Paypal link https://paypal.me/mhenrixon]: https://paypal.me/mhenrixon