sidekiq-unique-jobs 6.0.24 → 7.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.

Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +707 -25
  3. data/README.md +516 -105
  4. data/lib/sidekiq_unique_jobs.rb +48 -7
  5. data/lib/sidekiq_unique_jobs/batch_delete.rb +123 -0
  6. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  7. data/lib/sidekiq_unique_jobs/cli.rb +34 -31
  8. data/lib/sidekiq_unique_jobs/config.rb +263 -0
  9. data/lib/sidekiq_unique_jobs/connection.rb +6 -5
  10. data/lib/sidekiq_unique_jobs/constants.rb +46 -24
  11. data/lib/sidekiq_unique_jobs/core_ext.rb +80 -0
  12. data/lib/sidekiq_unique_jobs/digests.rb +71 -100
  13. data/lib/sidekiq_unique_jobs/exceptions.rb +78 -12
  14. data/lib/sidekiq_unique_jobs/job.rb +41 -12
  15. data/lib/sidekiq_unique_jobs/json.rb +40 -0
  16. data/lib/sidekiq_unique_jobs/key.rb +93 -0
  17. data/lib/sidekiq_unique_jobs/lock.rb +325 -0
  18. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +66 -50
  19. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  20. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  21. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +7 -10
  22. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +6 -6
  23. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +1 -1
  24. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +4 -21
  25. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  26. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +13 -9
  27. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +3 -3
  28. data/lib/sidekiq_unique_jobs/lock_args.rb +123 -0
  29. data/lib/sidekiq_unique_jobs/lock_config.rb +122 -0
  30. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  31. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  32. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  33. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  34. data/lib/sidekiq_unique_jobs/locksmith.rb +261 -101
  35. data/lib/sidekiq_unique_jobs/logging.rb +149 -23
  36. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  37. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  38. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +42 -0
  39. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  40. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  41. data/lib/sidekiq_unique_jobs/lua/lock.lua +93 -0
  42. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  43. data/lib/sidekiq_unique_jobs/lua/queue.lua +87 -0
  44. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +94 -0
  45. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  46. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  47. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  54. data/lib/sidekiq_unique_jobs/lua/unlock.lua +95 -0
  55. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  56. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  57. data/lib/sidekiq_unique_jobs/middleware.rb +29 -31
  58. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  59. data/lib/sidekiq_unique_jobs/middleware/server.rb +27 -0
  60. data/lib/sidekiq_unique_jobs/normalizer.rb +4 -4
  61. data/lib/sidekiq_unique_jobs/on_conflict.rb +23 -10
  62. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +9 -5
  63. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +1 -1
  64. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  65. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +61 -15
  66. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +54 -14
  67. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +12 -5
  68. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +25 -6
  69. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +41 -27
  70. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  71. data/lib/sidekiq_unique_jobs/orphans/manager.rb +212 -0
  72. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  73. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  74. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +114 -0
  75. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +201 -0
  76. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  77. data/lib/sidekiq_unique_jobs/redis/entity.rb +106 -0
  78. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  79. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  80. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  81. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +86 -0
  82. data/lib/sidekiq_unique_jobs/redis/string.rb +49 -0
  83. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  84. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  85. data/lib/sidekiq_unique_jobs/script.rb +15 -0
  86. data/lib/sidekiq_unique_jobs/script/caller.rb +125 -0
  87. data/lib/sidekiq_unique_jobs/server.rb +48 -0
  88. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +92 -65
  89. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +185 -34
  90. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +11 -5
  91. data/lib/sidekiq_unique_jobs/testing.rb +62 -21
  92. data/lib/sidekiq_unique_jobs/timer_task.rb +78 -0
  93. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  94. data/lib/sidekiq_unique_jobs/unlockable.rb +20 -4
  95. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  96. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +155 -0
  97. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  98. data/lib/sidekiq_unique_jobs/version_check.rb +23 -4
  99. data/lib/sidekiq_unique_jobs/web.rb +50 -27
  100. data/lib/sidekiq_unique_jobs/web/helpers.rb +125 -10
  101. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  102. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  103. data/lib/sidekiq_unique_jobs/web/views/locks.erb +52 -0
  104. data/lib/tasks/changelog.rake +5 -5
  105. metadata +117 -177
  106. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  107. data/lib/sidekiq_unique_jobs/scripts.rb +0 -118
  108. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  109. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  110. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  111. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -150
  112. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  113. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  114. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  115. data/redis/acquire_lock.lua +0 -21
  116. data/redis/convert_legacy_lock.lua +0 -13
  117. data/redis/delete.lua +0 -14
  118. data/redis/delete_by_digest.lua +0 -23
  119. data/redis/delete_job_by_digest.lua +0 -60
  120. data/redis/lock.lua +0 -62
  121. data/redis/release_stale_locks.lua +0 -90
  122. data/redis/unlock.lua +0 -35
data/README.md CHANGED
@@ -1,45 +1,66 @@
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.svg?branch=master)](https://travis-ci.org/mhenrixon/sidekiq-unique-jobs) [![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)
2
2
 
3
3
  <!-- MarkdownTOC -->
4
4
 
5
5
  - [Introduction](#introduction)
6
- - [Documentation](#documentation)
7
- - [Requirements](#requirements)
8
- - [ActiveJob](#activejob)
9
- - [redis-namespace](#redis-namespace)
10
- - [Installation](#installation)
6
+ - [Usage](#usage)
7
+ - [Installation](#installation)
8
+ - [Add the middleware](#add-the-middleware)
9
+ - [Your first worker](#your-first-worker)
11
10
  - [Support Me](#support-me)
11
+ - [Requirements](#requirements)
12
12
  - [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)
13
+ - [Global Configuration](#global-configuration)
14
+ - [debug_lua](#debug_lua)
15
+ - [lock_timeout](#lock_timeout)
16
+ - [lock_ttl](#lock_ttl)
17
+ - [enabled](#enabled)
18
+ - [logger](#logger)
19
+ - [max_history](#max_history)
20
+ - [reaper](#reaper)
21
+ - [reaper_count](#reaper_count)
22
+ - [reaper_interval](#reaper_interval)
23
+ - [reaper_timeout](#reaper_timeout)
24
+ - [lock_prefix](#lock_prefix)
25
+ - [lock_info](#lock_info)
26
+ - [Worker Configuration](#worker-configuration)
27
+ - [lock_info](#lock_info-1)
28
+ - [lock_prefix](#lock_prefix-1)
29
+ - [lock_ttl](#lock_ttl-1)
30
+ - [lock_timeout](#lock_timeout-1)
31
+ - [unique_across_queues](#unique_across_queues)
32
+ - [unique_across_workers](#unique_across_workers)
18
33
  - [Locks](#locks)
19
34
  - [Until Executing](#until-executing)
20
35
  - [Until Executed](#until-executed)
21
- - [Until Timeout](#until-timeout)
22
- - [Unique Until And While Executing](#unique-until-and-while-executing)
36
+ - [Until Expired](#until-expired)
37
+ - [Until And While Executing](#until-and-while-executing)
23
38
  - [While Executing](#while-executing)
39
+ - [Custom Locks](#custom-locks)
24
40
  - [Conflict Strategy](#conflict-strategy)
25
- - [Log](#log)
26
- - [Raise](#raise)
27
- - [Reject](#reject)
28
- - [Replace](#replace)
41
+ - [log](#log)
42
+ - [raise](#raise)
43
+ - [reject](#reject)
44
+ - [replace](#replace)
29
45
  - [Reschedule](#reschedule)
30
- - [Usage](#usage)
46
+ - [Custom Strategies](#custom-strategies)
47
+ - [Usage](#usage-1)
31
48
  - [Finer Control over Uniqueness](#finer-control-over-uniqueness)
32
49
  - [After Unlock Callback](#after-unlock-callback)
33
50
  - [Logging](#logging)
34
51
  - [Cleanup Dead Locks](#cleanup-dead-locks)
35
52
  - [Other Sidekiq gems](#other-sidekiq-gems)
53
+ - [apartment-sidekiq](#apartment-sidekiq)
36
54
  - [sidekiq-global_id](#sidekiq-global_id)
55
+ - [sidekiq-status](#sidekiq-status)
37
56
  - [Debugging](#debugging)
38
57
  - [Sidekiq Web](#sidekiq-web)
39
- - [Show Unique Digests](#show-unique-digests)
40
- - [Show keys for digest](#show-keys-for-digest)
58
+ - [Show Locks](#show-locks)
59
+ - [Show Lock](#show-lock)
41
60
  - [Communication](#communication)
42
61
  - [Testing](#testing)
62
+ - [Unique Sidekiq Configuration](#unique-sidekiq-configuration)
63
+ - [Uniqueness](#uniqueness)
43
64
  - [Contributing](#contributing)
44
65
  - [Contributors](#contributors)
45
66
 
@@ -47,75 +68,282 @@
47
68
 
48
69
  ## Introduction
49
70
 
50
- 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.
71
+ This gem adds unique constraints to the sidekiq queues. The uniqueness is achieved by acquiring locks for a hash of a queue name, a worker class, and job's arguments. 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 `on_conflict`strategy.
72
+
73
+ This is the documentation for the master branch. You can find the documentation for each release by navigating to its tag.
51
74
 
52
- ## Documentation
75
+ Here are links to some of the old versions
53
76
 
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][]
77
+ - [v6.0.25](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v6.0.25)
78
+ - [v5.0.10](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v5.0.10)
79
+ - [v4.0.18](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v4.0.18)
55
80
 
56
- Below are links to the latest major versions (4 & 5):
81
+ ## Usage
82
+
83
+ ### Installation
84
+
85
+ Add this line to your application's Gemfile:
57
86
 
58
- - [v5.0.10][]
59
- - [v4.0.18][]
87
+ ```ruby
88
+ gem 'sidekiq-unique-jobs'
89
+ ```
90
+
91
+ And then execute:
92
+
93
+ ```bash
94
+ bundle
95
+ ```
96
+
97
+ ### Add the middleware
98
+
99
+ Before v7, the middleware was configured automatically. Since some people reported issues with other gems (see [Other Sidekiq Gems](#other-sidekiq-gems)) it was decided to give full control over to the user.
100
+
101
+ *NOTE* if you want to use the reaper you also need to configure the server middleware.
102
+
103
+ [A full example](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/myapp/config/initializers/sidekiq.rb#L12)
104
+
105
+ ```ruby
106
+ Sidekiq.configure_server do |config|
107
+ config.redis = { url: ENV["REDIS_URL"], driver: :hiredis }
108
+
109
+ config.client_middleware do |chain|
110
+ chain.add SidekiqUniqueJobs::Middleware::Client
111
+ end
112
+
113
+ config.server_middleware do |chain|
114
+ chain.add SidekiqUniqueJobs::Middleware::Server
115
+ end
116
+
117
+ SidekiqUniqueJobs::Server.configure(config)
118
+ end
119
+
120
+ Sidekiq.configure_client do |config|
121
+ config.redis = { url: ENV["REDIS_URL"], driver: :hiredis }
122
+
123
+ config.client_middleware do |chain|
124
+ chain.add SidekiqUniqueJobs::Middleware::Client
125
+ end
126
+ end
127
+ ```
128
+
129
+ ### Your first worker
130
+
131
+ ```ruby
132
+ # frozen_string_literal: true
133
+
134
+ class UntilExecutedWorker
135
+ include Sidekiq::Worker
136
+
137
+ sidekiq_options queue: :special,
138
+ retry: false,
139
+ lock: :until_executed,
140
+ lock_info: true,
141
+ lock_timeout: 0,
142
+ lock_prefix: "special",
143
+ lock_ttl: 0,
144
+ lock_limit: 5
145
+
146
+ def perform
147
+ logger.info("cowboy")
148
+ sleep(1) # hardcore processing
149
+ logger.info("beebop")
150
+ end
151
+ end
152
+
153
+ ```
154
+
155
+ You can read more about the worker configuration in [Worker Configuration](#worker-configuration) below.
156
+
157
+ ## Support Me
158
+
159
+ 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.
60
160
 
61
161
  ## Requirements
62
162
 
63
- See [Sidekiq requirements][] for what is required. Starting from 5.0.0 only sidekiq >= 4 and MRI >= 2.2. ActiveJob is not supported
163
+ - Sidekiq `>= 5.0` (`>= 5.2` recommended)
164
+ - Ruby:
165
+ - MRI `>= 2.5` (`>= 2.6` recommended)
166
+ - JRuby `>= 9.0` (`>= 9.2` recommended)
167
+ - Truffleruby
168
+ - Redis Server `>= 3.2` (`>= 5.0` recommended)
169
+ - [ActiveJob officially not supported][48]
170
+ - [redis-namespace officially not supported][49]
64
171
 
65
- ### ActiveJob
172
+ See [Sidekiq requirements][24] for detailed requirements of Sidekiq itself (be sure to check the right sidekiq version).
66
173
 
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.
174
+ ## General Information
68
175
 
69
- ### redis-namespace
176
+ See [Interaction w/ Sidekiq](https://github.com/mhenrixon/sidekiq-unique-jobs/wiki/How-this-gem-interacts-with-Sidekiq) on how the gem interacts with Sidekiq.
70
177
 
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.
178
+ 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.
72
179
 
73
- [Read this](http://www.mikeperham.com/2017/04/10/migrating-from-redis-namespace/) for how to migrate away from namespacing.
180
+ ## Global Configuration
74
181
 
75
- ## Installation
182
+ The gem supports a few different configuration options that might be of interest if you run into some weird issues.
76
183
 
77
- Add this line to your application's Gemfile:
184
+ Configure SidekiqUniqueJobs in an initializer or the sidekiq initializer on application startup.
78
185
 
186
+ ```ruby
187
+ SidekiqUniqueJobs.configure do |config|
188
+ config.logger = Sidekiq.logger # default, change at your own discretion
189
+ config.debug_lua = false # Turn on when debugging
190
+ config.lock_info = false # Turn on when debugging
191
+ config.lock_ttl = 600 # Expire locks after 10 minutes
192
+ config.lock_timeout = nil # turn off lock timeout
193
+ config.max_history = 0 # Turn on when debugging
194
+ config.reaper = :ruby # :ruby, :lua or :none/nil
195
+ config.reaper_count = 1000 # Stop reaping after this many keys
196
+ config.reaper_interval = 600 # Reap orphans every 10 minutes
197
+ config.reaper_timeout = 150 # Timeout reaper after 2.5 minutes
198
+ end
79
199
  ```
80
- gem 'sidekiq-unique-jobs'
200
+
201
+ ### debug_lua
202
+
203
+ ```ruby
204
+ SidekiqUniqueJobs.config.debug_lua #=> false
81
205
  ```
82
206
 
83
- And then execute:
207
+ 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.
84
208
 
209
+ ### lock_timeout
210
+
211
+ ```ruby
212
+ SidekiqUniqueJobs.config.lock_timeout #=> 0
85
213
  ```
86
- bundle
214
+
215
+ Set a global lock_timeout to use for all jobs that don't otherwise specify a lock_timeout.
216
+
217
+ 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.
218
+
219
+ ### lock_ttl
220
+
221
+ ```ruby
222
+ SidekiqUniqueJobs.config.lock_ttl #=> nil
87
223
  ```
88
224
 
89
- Or install it yourself as:
225
+ Set a global lock_ttl to use for all jobs that don't otherwise specify a lock_ttl.
90
226
 
227
+ Lock TTL decides how long to wait after the job has been successfully processed before making it possible to reuse that lock.
228
+
229
+ ### enabled
230
+
231
+ ```ruby
232
+ SidekiqUniqueJobs.config.enabled #=> true
91
233
  ```
92
- gem install sidekiq-unique-jobs
234
+
235
+ Globally turn the locking mechanism on or off.
236
+
237
+ ### logger
238
+
239
+ ```ruby
240
+ SidekiqUniqueJobs.config.logger #=> #<Sidekiq::Logger:0x00007fdc1f96d180>
93
241
  ```
94
242
 
95
- ## Support Me
243
+ 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.
96
244
 
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.
245
+ ### max_history
98
246
 
99
- ## General Information
247
+ ```ruby
248
+ SidekiqUniqueJobs.config.max_history #=> 1_000
249
+ ```
100
250
 
101
- See [Interaction w/ Sidekiq](https://github.com/mhenrixon/sidekiq-unique-jobs/wiki/How-this-gem-interacts-with-Sidekiq) on how the gem interacts with Sidekiq.
251
+ 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.
102
252
 
103
- 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.
253
+ 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.
254
+
255
+ ### reaper
256
+
257
+ ```ruby
258
+ SidekiqUniqueJobs.config.reaper #=> :ruby
259
+ ```
260
+
261
+ 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.
262
+
263
+ In my benchmarks deleting 1000 orphaned locks with lua performs around 65% faster than deleting 1000 keys in ruby.
264
+
265
+ On the other hand if I increase it to 10 000 orphaned locks per cleanup (`reaper_count: 10_0000`) then redis starts throwing:
266
+
267
+ > BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (Redis::CommandError)
268
+
269
+ If you want to disable the reaper set it to `:none`, `nil` or `false`. Actually, any value that isn't `:ruby` or `:lua` will disable the reaping.
270
+
271
+ ```ruby
272
+ SidekiqUniqueJobs.config.reaper = :none
273
+ SidekiqUniqueJobs.config.reaper = nil
274
+ SidekiqUniqueJobs.config.reaper = false
275
+ ```
276
+
277
+ ### reaper_count
278
+
279
+ ```ruby
280
+ SidekiqUniqueJobs.config.reaper_count #=> 1_000
281
+ ```
282
+
283
+ 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.
284
+
285
+ ### reaper_interval
286
+
287
+ ```ruby
288
+ SidekiqUniqueJobs.config.reaper_interval #=> 600
289
+ ```
290
+
291
+ The number of seconds between reaping.
292
+
293
+ ### reaper_timeout
294
+
295
+ ```ruby
296
+ SidekiqUniqueJobs.config.reaper_timeout #=> 10
297
+ ```
298
+
299
+ 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.
300
+
301
+ ### lock_prefix
302
+
303
+ ```ruby
304
+ SidekiqUniqueJobs.config.lock_prefix #=> "uniquejobs"
305
+ ```
104
306
 
105
- ## Options
307
+ Use if you want a different key prefix for the keys in redis.
308
+
309
+ ### lock_info
310
+
311
+ ```ruby
312
+ SidekiqUniqueJobs.config.lock_info #=> false
313
+ ```
314
+
315
+ 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.
316
+
317
+ ## Worker Configuration
318
+
319
+ ### lock_info
320
+
321
+ Lock info gathers information about a specific lock. It collects things like which `lock_args` where used to compute the `lock_digest` that is used for maintaining uniqueness.
322
+
323
+ ```ruby
324
+ sidekiq_options lock_info: false # this is the default, set to true to turn on
325
+ ```
326
+
327
+ ### lock_prefix
328
+
329
+ Use if you want a different key prefix for the keys in redis.
330
+
331
+ ```ruby
332
+ sidekiq_options lock_prefix: "uniquejobs" # this is the default value
333
+ ```
106
334
 
107
- ### Lock Expiration
335
+ ### lock_ttl
108
336
 
109
- Lock expiration is used for two things. For the `UntilExpired` job releases the lock upon expiry. This is done from the client.
337
+ Lock TTL decides how long to wait after the job has been successfully processed before making it possible to reuse that lock.
110
338
 
111
- Since v6.0.11 the other locks will expire after the server is done processing.
339
+ Starting from `v7` the expiration will take place when the job is pushed to the queue.
112
340
 
113
341
  ```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
342
+ sidekiq_options lock_ttl: nil # default - don't expire keys
343
+ sidekiq_options lock_ttl: 20.days.to_i # expire this lock in 20 days
116
344
  ```
117
345
 
118
- ### Lock Timeout
346
+ ### lock_timeout
119
347
 
120
348
  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
349
 
@@ -125,10 +353,12 @@ sidekiq_options lock_timeout: 5 # wait 5 seconds
125
353
  sidekiq_options lock_timeout: nil # lock indefinitely, this process won't continue until it gets a lock. VERY DANGEROUS!!
126
354
  ```
127
355
 
128
- ### Unique Across Queues
356
+ ### unique_across_queues
129
357
 
130
358
  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
359
 
360
+ This is mainly intended for `Worker.set(queue: :another).perform_async`.
361
+
132
362
  ```ruby
133
363
  class Worker
134
364
  include Sidekiq::Worker
@@ -141,9 +371,9 @@ end
141
371
 
142
372
  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
373
 
144
- ### Unique Across Workers
374
+ ### unique_across_workers
145
375
 
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.
376
+ 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
377
 
148
378
  ```ruby
149
379
  class WorkerOne
@@ -174,53 +404,55 @@ WorkerTwo.perform_async(1)
174
404
 
175
405
  ### Until Executing
176
406
 
407
+ ```ruby
408
+ sidekiq_options lock: :until_executing
409
+ ```
410
+
177
411
  Locks from when the client pushes the job to the queue. Will be unlocked before the server starts processing the job.
178
412
 
179
413
  **NOTE** this is probably not so good for jobs that shouldn't be running simultaneously (aka slow jobs).
180
414
 
181
- ```ruby
182
- sidekiq_options lock: :until_executing
183
- ```
415
+ 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
416
 
185
417
  ### Until Executed
186
418
 
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
419
  ```ruby
190
420
  sidekiq_options lock: :until_executed
191
421
  ```
192
422
 
193
- ### Until Timeout
423
+ Locks from when the client pushes the job to the queue. Will be unlocked when the server has successfully processed the job.
194
424
 
195
- Locks from when the client pushes the job to the queue. Will be unlocked when the specified timeout has been reached.
425
+ ### Until Expired
196
426
 
197
427
  ```ruby
198
428
  sidekiq_options lock: :until_expired
199
429
  ```
200
430
 
201
- ### Unique Until And While Executing
431
+ Locks from when the client pushes the job to the queue. Will be unlocked when the specified timeout has been reached.
202
432
 
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.
433
+ ### Until And While Executing
204
434
 
205
435
  ```ruby
206
436
  sidekiq_options lock: :until_and_while_executing
207
437
  ```
208
438
 
439
+ 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.
440
+
209
441
  ### While Executing
210
442
 
443
+ ```ruby
444
+ sidekiq_options lock: :while_executing, lock_timeout: 10
445
+ ```
446
+
211
447
  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
448
 
213
449
  **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
450
 
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`.
451
+ There is an example of this to try it out in the `myapp` application. Run `foreman start` in the root of the directory and open the url: `localhost:5000/work/duplicate_while_executing`.
220
452
 
221
453
  In the console you should see something like:
222
454
 
223
- ```
455
+ ```bash
224
456
  0:32:24 worker.1 | 2017-04-23T08:32:24.955Z 84404 TID-ougq4thko WhileExecutingWorker JID-400ec51c9523f41cd4a35058 INFO: start
225
457
  10:32:24 worker.1 | 2017-04-23T08:32:24.956Z 84404 TID-ougq8csew WhileExecutingWorker JID-8d6d9168368eedaed7f75763 INFO: start
226
458
  10:32:24 worker.1 | 2017-04-23T08:32:24.957Z 84404 TID-ougq8crt8 WhileExecutingWorker JID-affcd079094c9b26e8b9ba60 INFO: start
@@ -235,31 +467,81 @@ In the console you should see something like:
235
467
  10:33:04 worker.1 | 2017-04-23T08:33:04.973Z 84404 TID-ougq8cs8s WhileExecutingWorker JID-9e197460c067b22eb1b5d07f INFO: done: 40.014 sec
236
468
  ```
237
469
 
470
+ ### Custom Locks
471
+
472
+ You may need to define some custom lock. You can define it in one project folder:
473
+
474
+ ```ruby
475
+ # lib/locks/my_custom_lock.rb
476
+ module Locks
477
+ class MyCustomLock < SidekiqUniqueJobs::Lock::BaseLock
478
+ def execute
479
+ # Do something ...
480
+ end
481
+ end
482
+ end
483
+ ```
484
+
485
+ You can refer on all the locks defined in `lib/sidekiq_unique_jobs/lock/*.rb`.
486
+
487
+ In order to make it available, you should call in your project startup:
488
+
489
+ (For rails application config/initializers/sidekiq_unique_jobs.rb or other projects, wherever you prefer)
490
+
491
+ ```ruby
492
+ SidekiqUniqueJobs.configure do |config|
493
+ config.add_lock :my_custom_lock, Locks::MyCustomLock
494
+ end
495
+ ```
496
+
497
+ And then you can use it in the jobs definition:
498
+
499
+ `sidekiq_options lock: :my_custom_lock, on_conflict: :log`
500
+
501
+ Please not that if you try to override a default lock, an `ArgumentError` will be raised.
502
+
238
503
  ## Conflict Strategy
239
504
 
240
505
  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
506
 
242
- 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
507
+ 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.
508
+
509
+ It is possible for locks to have different conflict strategy for the client and server. This is useful for `:until_and_while_executing`.
510
+
511
+ ```ruby
512
+ sidekiq_options lock: :until_and_while_executing,
513
+ on_conflict: { client: :log, server: :reject }
514
+ ```
515
+
516
+ ### log
243
517
 
244
- ### Log
518
+ ```ruby
519
+ sidekiq_options on_conflict: :log
520
+ ```
245
521
 
246
522
  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
523
 
248
- `sidekiq_options lock: :until_executed, on_conflict: :log`
524
+ ### raise
249
525
 
250
- ### Raise
526
+ ```ruby
527
+ sidekiq_options on_conflict: :raise
528
+ ```
251
529
 
252
530
  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
531
 
254
- `sidekiq_options lock: :while_executing, on_conflict: :raise, retry: 10`
532
+ ### reject
255
533
 
256
- ### Reject
534
+ ```ruby
535
+ sidekiq_options on_conflict: :reject
536
+ ```
257
537
 
258
538
  This strategy is intended to be used with `WhileExecuting` and will push the job to the dead queue on conflict.
259
539
 
260
- `sidekiq_options lock: :while_executing, on_conflict: :reject`
540
+ ### replace
261
541
 
262
- ### Replace
542
+ ```ruby
543
+ sidekiq_options on_conflict: :replace
544
+ ```
263
545
 
264
546
  This strategy is intended to be used with client locks like `UntilExecuted`.
265
547
  It will delete any existing job for these arguments from retry, schedule and
@@ -268,13 +550,48 @@ queue and retry the lock again.
268
550
  This is slightly dangerous and should probably only be used for jobs that are
269
551
  always scheduled in the future. Currently only attempting to retry one time.
270
552
 
271
- `sidekiq_options lock: :until_executed, on_conflict: :replace`
272
-
273
553
  ### Reschedule
274
554
 
555
+ ```ruby
556
+ sidekiq_options on_conflict: :reschedule
557
+ ```
558
+
275
559
  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
560
 
277
- `sidekiq_options lock: :while_executing, on_conflict: :reschedule`
561
+ ### Custom Strategies
562
+
563
+ You may need to define some custom strategy. You can define it in one project folder:
564
+
565
+ ```ruby
566
+ # lib/strategies/my_custom_strategy.rb
567
+ module Strategies
568
+ class MyCustomStrategy < OnConflict::Strategy
569
+ def call
570
+ # Do something ...
571
+ end
572
+ end
573
+ end
574
+ ```
575
+
576
+ You can refer to all the strategies defined in `lib/sidekiq_unique_jobs/on_conflict`.
577
+
578
+ In order to make it available, you should call in your project startup:
579
+
580
+ (For rails application config/initializers/sidekiq_unique_jobs.rb for other projects, wherever you prefer)
581
+
582
+ ```ruby
583
+ SidekiqUniqueJobs.configure do |config|
584
+ config.add_strategy :my_custom_strategy, Strategies::MyCustomStrategy
585
+ end
586
+ ```
587
+
588
+ And then you can use it in the jobs definition:
589
+
590
+ ```ruby
591
+ sidekiq_options lock: :while_executing, on_conflict: :my_custom_strategy
592
+ ```
593
+
594
+ Please not that if you try to override a default lock, an `ArgumentError` will be raised.
278
595
 
279
596
  ## Usage
280
597
 
@@ -288,9 +605,11 @@ Requiring the gem in your gemfile should be sufficient to enable unique jobs.
288
605
 
289
606
  ### Finer Control over Uniqueness
290
607
 
291
- 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.
608
+ 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 `lock_args` method, or a ruby proc.
292
609
 
293
- The unique_args method need to return an array of values to use for uniqueness check.
610
+ *NOTE:* The lock_args method need to return an array of values to use for uniqueness check.
611
+
612
+ *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
613
 
295
614
  The method or the proc can return a modified version of args without the transient arguments included, as shown below:
296
615
 
@@ -298,9 +617,9 @@ The method or the proc can return a modified version of args without the transie
298
617
  class UniqueJobWithFilterMethod
299
618
  include Sidekiq::Worker
300
619
  sidekiq_options lock: :until_and_while_executing,
301
- unique_args: :unique_args # this is default and will be used if such a method is defined
620
+ lock_args_method: :lock_args # this is default and will be used if such a method is defined
302
621
 
303
- def self.unique_args(args)
622
+ def self.lock_args(args)
304
623
  [ args[0], args[2][:type] ]
305
624
  end
306
625
 
@@ -311,21 +630,21 @@ end
311
630
  class UniqueJobWithFilterProc
312
631
  include Sidekiq::Worker
313
632
  sidekiq_options lock: :until_executed,
314
- unique_args: ->(args) { [ args.first ] }
633
+ lock_args_method: ->(args) { [ args.first ] }
315
634
 
316
635
  ...
317
636
 
318
637
  end
319
638
  ```
320
639
 
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.
640
+ 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
641
 
323
642
  ```ruby
324
643
  class UniqueJobWithFilterMethod
325
644
  include Sidekiq::Worker
326
- sidekiq_options lock: :until_and_while_executing, unique_args: :unique_args
645
+ sidekiq_options lock: :until_and_while_executing, lock_args_method: :lock_args
327
646
 
328
- def self.unique_args(args)
647
+ def self.lock_args(args)
329
648
  if Sidekiq::ProcessSet.new.size > 1
330
649
  # sidekiq runtime; uniqueness for the object (first arg)
331
650
  args.first
@@ -341,7 +660,7 @@ end
341
660
 
342
661
  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
662
 
344
- **Exception 1:** UntilExecuting unlocks and calls back before yielding.
663
+ **Exception 1:** UntilExecuting unlocks and uses callback before yielding.
345
664
  **Exception 2:** UntilExpired expires eventually, no after_unlock hook is called.
346
665
 
347
666
  **NOTE:** _It is also possible to write this code as a class method._
@@ -370,7 +689,7 @@ To see logging in sidekiq when duplicate payload has been filtered out you can e
370
689
  class UniqueJobWithFilterMethod
371
690
  include Sidekiq::Worker
372
691
  sidekiq_options lock: :while_executing,
373
- log_duplicate_payload: true
692
+ log_duplicate: true
374
693
 
375
694
  ...
376
695
 
@@ -384,40 +703,95 @@ For sidekiq versions before 5.1 a `sidekiq_retries_exhausted` block is required
384
703
  ```ruby
385
704
  class MyWorker
386
705
  sidekiq_retries_exhausted do |msg, _ex|
387
- SidekiqUniqueJobs::Digests.delete_by_digest(msg['unique_digest']) if msg['unique_digest']
706
+ digest = msg['lock_digest']
707
+ SidekiqUniqueJobs::Digests.new.delete_by_digest(digest) if digest
388
708
  end
389
709
  end
390
710
  ```
391
711
 
392
- Starting in v5.1, Sidekiq can also fire a global callback when a job dies:
712
+ Starting in v5.1, Sidekiq can also fire a global callback when a job dies: In version 7, this is handled automatically for you. You don't need to add a death handler, if you configure v7 like in [Add the middleware](#add-the-middleware) you don't have to worry about the below.
393
713
 
394
714
  ```ruby
395
- # this goes in your initializer
396
715
  Sidekiq.configure_server do |config|
397
716
  config.death_handlers << ->(job, _ex) do
398
- SidekiqUniqueJobs::Digests.delete_by_digest(job['unique_digest']) if job['unique_digest']
717
+ digest = job['lock_digest']
718
+ SidekiqUniqueJobs::Digests.new.delete_by_digest(digest) if digest
399
719
  end
400
720
  end
401
721
  ```
402
722
 
403
723
  ### Other Sidekiq gems
404
724
 
725
+ #### apartment-sidekiq
726
+
727
+ It was reported in [#536](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/536) that the order of the Sidekiq middleware needs to be as follows.
728
+
729
+ ```ruby
730
+ Sidekiq.client_middleware do |chain|
731
+ chain.add Apartment::Sidekiq::Middleware::Client
732
+ chain.add SidekiqUniqueJobs::Middleware::Client
733
+ end
734
+
735
+ Sidekiq.server_middleware do |chain|
736
+ chain.add Apartment::Sidekiq::Middleware::Server
737
+ chain.add SidekiqUniqueJobs::Middleware::Server
738
+ end
739
+ ```
740
+
741
+ The reason being that this gem needs to be configured AFTER the apartment gem or the apartment will not be able to be considered for uniqueness
742
+
405
743
  #### sidekiq-global_id
406
744
 
407
745
  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
746
 
747
+ For a working setup check the following [file](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/myapp/config/sidekiq.rb#L12).
748
+
409
749
  ```ruby
410
750
  Sidekiq.client_middleware do |chain|
411
751
  chain.add Sidekiq::GlobalId::ClientMiddleware
412
- chain.add SidekiqUniqueJobs::Client::Middleware
752
+ chain.add SidekiqUniqueJobs::Middleware::Client
413
753
  end
414
754
 
415
755
  Sidekiq.server_middleware do |chain|
416
- chain.add SidekiqUniqueJobs::Server::Middleware
417
756
  chain.add Sidekiq::GlobalId::ServerMiddleware
757
+ chain.add SidekiqUniqueJobs::Middleware::Server
758
+ end
759
+ ```
760
+
761
+ The reason for this is that the global id needs to be set before the unique jobs middleware runs. Otherwise that won't be available for uniqueness.
762
+
763
+ #### sidekiq-status
764
+
765
+ It was reported in [#564](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/564) that the order of the middleware needs to be as follows.
766
+
767
+ ```ruby
768
+ # Thanks to @ArturT for the correction
769
+
770
+ Sidekiq.configure_server do |config|
771
+ config.client_middleware do |chain|
772
+ chain.add SidekiqUniqueJobs::Middleware::Client
773
+ chain.add Sidekiq::Status::ClientMiddleware, expiration: 30.minutes
774
+ end
775
+
776
+ config.server_middleware do |chain|
777
+ chain.add Sidekiq::Status::ServerMiddleware, expiration: 30.minutes
778
+ chain.add SidekiqUniqueJobs::Middleware::Server
779
+ end
780
+
781
+ SidekiqUniqueJobs::Server.configure(config)
782
+ end
783
+
784
+
785
+ Sidekiq.configure_client do |config|
786
+ config.client_middleware do |chain|
787
+ chain.add SidekiqUniqueJobs::Middleware::Client
788
+ chain.add Sidekiq::Status::ClientMiddleware, expiration: 30.minutes
789
+ end
418
790
  end
419
791
  ```
420
792
 
793
+ The reason for this is that if a job is duplicated it shouldn't end up with the status middleware at all. Status is just a monitor so to prevent clashes, leftovers and ensure cleanup. The status middleware should run after uniqueness on client and before on server. This will lead to less surprises.
794
+
421
795
  ## Debugging
422
796
 
423
797
  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.
@@ -427,7 +801,7 @@ There are several ways of removing keys that are stuck. The prefered way is by u
427
801
  To use the web extension you need to require it in your routes.
428
802
 
429
803
  ```ruby
430
- # app/config/routes.rb
804
+ #app/config/routes.rb
431
805
  require 'sidekiq_unique_jobs/web'
432
806
  mount Sidekiq::Web, at: '/sidekiq'
433
807
  ```
@@ -437,13 +811,13 @@ already does this.
437
811
 
438
812
  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
813
 
440
- #### Show Unique Digests
814
+ #### Show Locks
441
815
 
442
- ![Unique Digests](assets/unique_digests_1.png)
816
+ ![Locks](assets/unique_digests_1.png)
443
817
 
444
- #### Show keys for digest
818
+ #### Show Lock
445
819
 
446
- ![Unique Digests](assets/unique_digests_2.png)
820
+ ![Lock](assets/unique_digests_2.png)
447
821
 
448
822
  ## Communication
449
823
 
@@ -451,9 +825,45 @@ There is a [![Join the chat at https://gitter.im/mhenrixon/sidekiq-unique-jobs](
451
825
 
452
826
  ## Testing
453
827
 
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.
828
+ ### Unique Sidekiq Configuration
829
+
830
+ 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.
831
+
832
+ Let's take a _bad_ worker:
833
+
834
+ ```ruby
835
+ #app/workers/bad_worker.rb
836
+ class BadWorker
837
+ sidekiq_options lock: :while_executing, on_conflict: :replace
838
+ end
839
+
840
+ #spec/workers/bad_worker_spec.rb
841
+
842
+ require "sidekiq_unique_jobs/testing"
843
+ #OR
844
+ require "sidekiq_unique_jobs/rspec/matchers"
845
+
846
+ RSpec.describe BadWorker do
847
+ specify { expect(described_class).to have_valid_sidekiq_options }
848
+ end
849
+ ```
850
+
851
+ This gives us a helpful error message for a wrongly configured worker:
455
852
 
456
- [Enterprise unique jobs][]
853
+ ```bash
854
+ Expected BadWorker to have valid sidekiq options but found the following problems:
855
+ on_server_conflict: :replace is incompatible with the server process
856
+ ```
857
+
858
+ If you are not using RSpec (a lot of people prefer minitest or test unit) you can do something like:
859
+
860
+ ```ruby
861
+ assert SidekiqUniqueJobs.validate_worker!(BadWorker.get_sidekiq_options)
862
+ ```
863
+
864
+ ### Uniqueness
865
+
866
+ 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
867
 
458
868
  ```ruby
459
869
  SidekiqUniqueJobs.configure do |config|
@@ -464,6 +874,8 @@ end
464
874
  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
875
 
466
876
  ```ruby
877
+ require "sidekiq_unique_jobs/testing"
878
+
467
879
  RSpec.describe Workers::CoolOne do
468
880
  before do
469
881
  SidekiqUniqueJobs.config.enabled = false
@@ -491,7 +903,7 @@ RSpec.describe Workers::CoolOne do
491
903
  end
492
904
  ```
493
905
 
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:
906
+ It is recommended 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
907
 
496
908
  - [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
909
  - [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 +928,3 @@ You can find a list of contributors over on [Contributors][]
516
928
  [Sidekiq requirements]: https://github.com/mperham/sidekiq#requirements
517
929
  [Enterprise unique jobs]: https://www.dailydrip.com/topics/sidekiq/drips/sidekiq-enterprise-unique-jobs
518
930
  [Contributors]: https://github.com/mhenrixon/sidekiq-unique-jobs/graphs/contributors
519
- [Paypal link https://paypal.me/mhenrixon]: https://paypal.me/mhenrixon