sidekiq-unique-jobs 4.0.18 → 7.1.28

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 (296) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1889 -94
  3. data/LICENSE.txt +21 -0
  4. data/README.md +927 -119
  5. data/bin/uniquejobs +7 -0
  6. data/lib/sidekiq-unique-jobs.rb +2 -78
  7. data/lib/sidekiq_unique_jobs/batch_delete.rb +124 -0
  8. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  9. data/lib/sidekiq_unique_jobs/cli.rb +79 -21
  10. data/lib/sidekiq_unique_jobs/config.rb +310 -14
  11. data/lib/sidekiq_unique_jobs/connection.rb +23 -0
  12. data/lib/sidekiq_unique_jobs/constants.rb +52 -16
  13. data/lib/sidekiq_unique_jobs/core_ext.rb +114 -33
  14. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  15. data/lib/sidekiq_unique_jobs/digests.rb +111 -0
  16. data/lib/sidekiq_unique_jobs/exceptions.rb +105 -0
  17. data/lib/sidekiq_unique_jobs/expiring_digests.rb +14 -0
  18. data/lib/sidekiq_unique_jobs/job.rb +58 -0
  19. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  20. data/lib/sidekiq_unique_jobs/key.rb +98 -0
  21. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +165 -0
  22. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  23. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  24. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +65 -7
  25. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +34 -64
  26. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +37 -4
  27. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +42 -0
  28. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  29. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +53 -35
  30. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +21 -0
  31. data/lib/sidekiq_unique_jobs/lock.rb +338 -6
  32. data/lib/sidekiq_unique_jobs/lock_args.rb +127 -0
  33. data/lib/sidekiq_unique_jobs/lock_config.rb +126 -0
  34. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  35. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  36. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  37. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  38. data/lib/sidekiq_unique_jobs/locksmith.rb +383 -0
  39. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  40. data/lib/sidekiq_unique_jobs/logging.rb +236 -0
  41. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  42. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +42 -0
  43. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  44. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  45. data/lib/sidekiq_unique_jobs/lua/lock.lua +99 -0
  46. data/lib/sidekiq_unique_jobs/lua/lock_until_expired.lua +92 -0
  47. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  48. data/lib/sidekiq_unique_jobs/lua/queue.lua +87 -0
  49. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +122 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  54. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  55. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  56. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  57. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  58. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  59. data/lib/sidekiq_unique_jobs/lua/unlock.lua +107 -0
  60. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  61. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  62. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  63. data/lib/sidekiq_unique_jobs/middleware/server.rb +31 -0
  64. data/lib/sidekiq_unique_jobs/middleware.rb +34 -29
  65. data/lib/sidekiq_unique_jobs/normalizer.rb +11 -1
  66. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +24 -0
  67. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +16 -0
  68. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +17 -0
  69. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +118 -0
  70. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +82 -0
  71. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +35 -0
  72. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +51 -0
  73. data/lib/sidekiq_unique_jobs/on_conflict.rb +44 -0
  74. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +54 -31
  75. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  76. data/lib/sidekiq_unique_jobs/orphans/manager.rb +241 -0
  77. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  78. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  79. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +114 -0
  80. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  81. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +298 -0
  82. data/lib/sidekiq_unique_jobs/redis/entity.rb +112 -0
  83. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  84. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  85. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  86. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +86 -0
  87. data/lib/sidekiq_unique_jobs/redis/string.rb +51 -0
  88. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  89. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  90. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  91. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  92. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  93. data/lib/sidekiq_unique_jobs/script/caller.rb +127 -0
  94. data/lib/sidekiq_unique_jobs/script.rb +15 -0
  95. data/lib/sidekiq_unique_jobs/server.rb +61 -0
  96. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +116 -104
  97. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +304 -0
  98. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +84 -0
  99. data/lib/sidekiq_unique_jobs/testing.rb +115 -50
  100. data/lib/sidekiq_unique_jobs/timer_task.rb +299 -0
  101. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  102. data/lib/sidekiq_unique_jobs/unlockable.rb +29 -33
  103. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  104. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +155 -0
  105. data/lib/sidekiq_unique_jobs/version.rb +5 -1
  106. data/lib/sidekiq_unique_jobs/version_check.rb +114 -0
  107. data/lib/sidekiq_unique_jobs/web/helpers.rb +173 -0
  108. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +10 -0
  109. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  110. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  111. data/lib/sidekiq_unique_jobs/web/views/locks.erb +54 -0
  112. data/lib/sidekiq_unique_jobs/web.rb +109 -0
  113. data/lib/sidekiq_unique_jobs.rb +81 -0
  114. data/lib/tasks/changelog.rake +23 -0
  115. metadata +173 -251
  116. data/.codeclimate.yml +0 -22
  117. data/.dockerignore +0 -4
  118. data/.editorconfig +0 -14
  119. data/.fasterer.yml +0 -23
  120. data/.gitignore +0 -14
  121. data/.rspec +0 -3
  122. data/.rubocop.yml +0 -45
  123. data/.simplecov +0 -12
  124. data/.travis.yml +0 -43
  125. data/Appraisals +0 -31
  126. data/Gemfile +0 -16
  127. data/LICENSE +0 -13
  128. data/Rakefile +0 -12
  129. data/bin/bench +0 -19
  130. data/bin/jobs +0 -6
  131. data/circle.yml +0 -38
  132. data/gemfiles/sidekiq_2.15.gemfile +0 -9
  133. data/gemfiles/sidekiq_2.16.gemfile +0 -9
  134. data/gemfiles/sidekiq_2.17.gemfile +0 -19
  135. data/gemfiles/sidekiq_3.0.gemfile +0 -19
  136. data/gemfiles/sidekiq_3.1.gemfile +0 -19
  137. data/gemfiles/sidekiq_3.2.gemfile +0 -19
  138. data/gemfiles/sidekiq_3.3.gemfile +0 -19
  139. data/gemfiles/sidekiq_4.0.gemfile +0 -19
  140. data/gemfiles/sidekiq_4.1.gemfile +0 -19
  141. data/gemfiles/sidekiq_develop.gemfile +0 -19
  142. data/lib/sidekiq/simulator.rb +0 -75
  143. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -41
  144. data/lib/sidekiq_unique_jobs/lock/until_timeout.rb +0 -15
  145. data/lib/sidekiq_unique_jobs/run_lock_failed.rb +0 -1
  146. data/lib/sidekiq_unique_jobs/script_mock.rb +0 -68
  147. data/lib/sidekiq_unique_jobs/scripts.rb +0 -49
  148. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -34
  149. data/lib/sidekiq_unique_jobs/testing/sidekiq_overrides.rb +0 -70
  150. data/lib/sidekiq_unique_jobs/timeout_calculator.rb +0 -65
  151. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -134
  152. data/lib/sidekiq_unique_jobs/util.rb +0 -91
  153. data/rails_example/.env +0 -12
  154. data/rails_example/.gitignore +0 -13
  155. data/rails_example/.rspec +0 -2
  156. data/rails_example/Gemfile +0 -34
  157. data/rails_example/Procfile +0 -2
  158. data/rails_example/README.rdoc +0 -28
  159. data/rails_example/Rakefile +0 -6
  160. data/rails_example/app/assets/images/.keep +0 -0
  161. data/rails_example/app/assets/javascripts/application.js +0 -16
  162. data/rails_example/app/assets/stylesheets/application.css +0 -15
  163. data/rails_example/app/channels/appearance_channel.rb +0 -17
  164. data/rails_example/app/channels/application_cable/channel.rb +0 -4
  165. data/rails_example/app/channels/application_cable/connection.rb +0 -9
  166. data/rails_example/app/channels/post_channel.rb +0 -5
  167. data/rails_example/app/controllers/application_controller.rb +0 -5
  168. data/rails_example/app/controllers/concerns/.keep +0 -0
  169. data/rails_example/app/controllers/work_controller.rb +0 -13
  170. data/rails_example/app/helpers/application_helper.rb +0 -2
  171. data/rails_example/app/mailers/.keep +0 -0
  172. data/rails_example/app/models/.keep +0 -0
  173. data/rails_example/app/models/application_record.rb +0 -3
  174. data/rails_example/app/models/concerns/.keep +0 -0
  175. data/rails_example/app/models/guest.rb +0 -21
  176. data/rails_example/app/models/post.rb +0 -2
  177. data/rails_example/app/views/layouts/application.html.erb +0 -15
  178. data/rails_example/app/workers/simple_worker.rb +0 -12
  179. data/rails_example/app/workers/spawn_simple_worker.rb +0 -7
  180. data/rails_example/bin/bundle +0 -3
  181. data/rails_example/bin/check_or_setup_db +0 -57
  182. data/rails_example/bin/docker-setup +0 -20
  183. data/rails_example/bin/rails +0 -4
  184. data/rails_example/bin/rake +0 -4
  185. data/rails_example/bin/setup +0 -34
  186. data/rails_example/bin/update +0 -29
  187. data/rails_example/cable.ru +0 -6
  188. data/rails_example/common-services.yml +0 -50
  189. data/rails_example/config/application.rb +0 -27
  190. data/rails_example/config/boot.rb +0 -3
  191. data/rails_example/config/cable.yml +0 -9
  192. data/rails_example/config/database.docker.yml +0 -12
  193. data/rails_example/config/database.yml +0 -19
  194. data/rails_example/config/environment.rb +0 -5
  195. data/rails_example/config/environments/development.rb +0 -54
  196. data/rails_example/config/environments/production.rb +0 -86
  197. data/rails_example/config/environments/test.rb +0 -42
  198. data/rails_example/config/initializers/application_controller_renderer.rb +0 -6
  199. data/rails_example/config/initializers/assets.rb +0 -11
  200. data/rails_example/config/initializers/backtrace_silencers.rb +0 -9
  201. data/rails_example/config/initializers/cookies_serializer.rb +0 -5
  202. data/rails_example/config/initializers/filter_parameter_logging.rb +0 -4
  203. data/rails_example/config/initializers/inflections.rb +0 -16
  204. data/rails_example/config/initializers/mime_types.rb +0 -4
  205. data/rails_example/config/initializers/new_framework_defaults.rb +0 -23
  206. data/rails_example/config/initializers/session_store.rb +0 -3
  207. data/rails_example/config/initializers/sidekiq.rb +0 -13
  208. data/rails_example/config/initializers/wrap_parameters.rb +0 -14
  209. data/rails_example/config/locales/en.yml +0 -23
  210. data/rails_example/config/puma.rb +0 -38
  211. data/rails_example/config/routes.rb +0 -6
  212. data/rails_example/config/secrets.yml +0 -22
  213. data/rails_example/config/sidekiq.yml +0 -6
  214. data/rails_example/config/spring.rb +0 -6
  215. data/rails_example/config.ru +0 -4
  216. data/rails_example/db/migrate/20160724111322_create_posts.rb +0 -12
  217. data/rails_example/db/schema.rb +0 -25
  218. data/rails_example/db/seeds.rb +0 -7
  219. data/rails_example/dev-entrypoint.sh +0 -55
  220. data/rails_example/dev.env +0 -12
  221. data/rails_example/docker/rails.Dockerfile +0 -27
  222. data/rails_example/docker-compose.yml +0 -90
  223. data/rails_example/lib/assets/.keep +0 -0
  224. data/rails_example/lib/tasks/.keep +0 -0
  225. data/rails_example/log/.keep +0 -0
  226. data/rails_example/public/404.html +0 -67
  227. data/rails_example/public/422.html +0 -67
  228. data/rails_example/public/500.html +0 -66
  229. data/rails_example/public/favicon.ico +0 -0
  230. data/rails_example/public/robots.txt +0 -5
  231. data/rails_example/simple.ru +0 -12
  232. data/rails_example/spec/controllers/work_controller_spec.rb +0 -172
  233. data/rails_example/spec/factories/posts.rb +0 -8
  234. data/rails_example/spec/models/post_spec.rb +0 -4
  235. data/rails_example/spec/rails_helper.rb +0 -21
  236. data/rails_example/spec/spec_helper.rb +0 -20
  237. data/rails_example/spec/support/sidekiq_meta.rb +0 -12
  238. data/rails_example/spec/workers/simple_worker_spec.rb +0 -4
  239. data/rails_example/vendor/assets/javascripts/.keep +0 -0
  240. data/rails_example/vendor/assets/stylesheets/.keep +0 -0
  241. data/redis/acquire_lock.lua +0 -19
  242. data/redis/release_lock.lua +0 -16
  243. data/redis/synchronize.lua +0 -16
  244. data/sidekiq-unique-jobs.gemspec +0 -25
  245. data/spec/celluloid_with_fallback.rb +0 -18
  246. data/spec/jobs/another_unique_job.rb +0 -13
  247. data/spec/jobs/custom_queue_job.rb +0 -6
  248. data/spec/jobs/custom_queue_job_with_filter_method.rb +0 -7
  249. data/spec/jobs/custom_queue_job_with_filter_proc.rb +0 -10
  250. data/spec/jobs/expiring_job.rb +0 -4
  251. data/spec/jobs/inline_worker.rb +0 -8
  252. data/spec/jobs/just_a_worker.rb +0 -8
  253. data/spec/jobs/long_running_job.rb +0 -7
  254. data/spec/jobs/main_job.rb +0 -8
  255. data/spec/jobs/my_job.rb +0 -12
  256. data/spec/jobs/my_unique_job.rb +0 -12
  257. data/spec/jobs/my_unique_job_with_filter_method.rb +0 -17
  258. data/spec/jobs/my_unique_job_with_filter_proc.rb +0 -15
  259. data/spec/jobs/notify_worker.rb +0 -10
  260. data/spec/jobs/plain_class.rb +0 -4
  261. data/spec/jobs/simple_worker.rb +0 -11
  262. data/spec/jobs/spawn_simple_worker.rb +0 -8
  263. data/spec/jobs/test_class.rb +0 -4
  264. data/spec/jobs/unique_job_with_filter_method.rb +0 -18
  265. data/spec/jobs/unique_on_all_queues_job.rb +0 -13
  266. data/spec/jobs/until_and_while_executing_job.rb +0 -8
  267. data/spec/jobs/until_executed_job.rb +0 -17
  268. data/spec/jobs/until_executing_job.rb +0 -8
  269. data/spec/jobs/until_global_timeout_job.rb +0 -8
  270. data/spec/jobs/until_timeout_job.rb +0 -8
  271. data/spec/jobs/while_executing_job.rb +0 -12
  272. data/spec/lib/sidekiq_unique_jobs/client/middleware_spec.rb +0 -321
  273. data/spec/lib/sidekiq_unique_jobs/core_ext_spec.rb +0 -25
  274. data/spec/lib/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb +0 -43
  275. data/spec/lib/sidekiq_unique_jobs/lock/until_executed_spec.rb +0 -37
  276. data/spec/lib/sidekiq_unique_jobs/lock/until_timeout_spec.rb +0 -26
  277. data/spec/lib/sidekiq_unique_jobs/lock/while_executing_spec.rb +0 -86
  278. data/spec/lib/sidekiq_unique_jobs/normalizer_spec.rb +0 -21
  279. data/spec/lib/sidekiq_unique_jobs/options_with_fallback_spec.rb +0 -113
  280. data/spec/lib/sidekiq_unique_jobs/queue_lock_timeout_calculator_spec.rb +0 -47
  281. data/spec/lib/sidekiq_unique_jobs/run_lock_timeout_calculator_spec.rb +0 -36
  282. data/spec/lib/sidekiq_unique_jobs/script_mock_spec.rb +0 -84
  283. data/spec/lib/sidekiq_unique_jobs/scripts_spec.rb +0 -77
  284. data/spec/lib/sidekiq_unique_jobs/server/middleware_spec.rb +0 -83
  285. data/spec/lib/sidekiq_unique_jobs/sidekiq_testing_enabled_spec.rb +0 -181
  286. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_ext_spec.rb +0 -80
  287. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_jobs_spec.rb +0 -41
  288. data/spec/lib/sidekiq_unique_jobs/timeout_calculator_spec.rb +0 -70
  289. data/spec/lib/sidekiq_unique_jobs/unique_args_spec.rb +0 -131
  290. data/spec/lib/sidekiq_unique_jobs/unlockable_spec.rb +0 -4
  291. data/spec/lib/sidekiq_unique_jobs/util_spec.rb +0 -53
  292. data/spec/spec_helper.rb +0 -65
  293. data/spec/support/matchers/redis_matchers.rb +0 -19
  294. data/spec/support/ruby_meta.rb +0 -10
  295. data/spec/support/sidekiq_meta.rb +0 -31
  296. data/spec/support/unique_macros.rb +0 -71
data/README.md CHANGED
@@ -1,245 +1,1053 @@
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)
1
+ # SidekiqUniqueJobs
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)
4
+
5
+ ## Support Me
6
+
7
+ 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.
8
+
9
+ <!-- MarkdownTOC -->
10
+
11
+ - [Introduction](#introduction)
12
+ - [Usage](#usage)
13
+ - [Installation](#installation)
14
+ - [Add the middleware](#add-the-middleware)
15
+ - [Your first worker](#your-first-worker)
16
+ - [Requirements](#requirements)
17
+ - [Locks](#locks)
18
+ - [Until Executing](#until-executing)
19
+ - [Example worker](#example-worker)
20
+ - [Until Executed](#until-executed)
21
+ - [Example worker](#example-worker-1)
22
+ - [Until Expired](#until-expired)
23
+ - [Example worker](#example-worker-2)
24
+ - [Until And While Executing](#until-and-while-executing)
25
+ - [Example worker](#example-worker-3)
26
+ - [While Executing](#while-executing)
27
+ - [Example worker](#example-worker-4)
28
+ - [Custom Locks](#custom-locks)
29
+ - [Conflict Strategy](#conflict-strategy)
30
+ - [log](#log)
31
+ - [raise](#raise)
32
+ - [reject](#reject)
33
+ - [replace](#replace)
34
+ - [reschedule](#reschedule)
35
+ - [Custom Strategies](#custom-strategies)
36
+ - [3 Cleanup Dead Locks](#3-cleanup-dead-locks)
37
+ - [Debugging](#debugging)
38
+ - [Sidekiq Web](#sidekiq-web)
39
+ - [Reflections \(metrics, logging, etc.\)](#reflections-metrics-logging-etc)
40
+ - [after_unlock_callback_failed](#after_unlock_callback_failed)
41
+ - [error](#error)
42
+ - [execution_failed](#execution_failed)
43
+ - [lock_failed](#lock_failed)
44
+ - [locked](#locked)
45
+ - [reschedule_failed](#reschedule_failed)
46
+ - [rescheduled](#rescheduled)
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)
53
+ - [Testing](#testing)
54
+ - [Validating Worker Configuration](#validating-worker-configuration)
55
+ - [Uniqueness](#uniqueness)
56
+ - [Configuration](#configuration)
57
+ - [Other Sidekiq gems](#other-sidekiq-gems)
58
+ - [apartment-sidekiq](#apartment-sidekiq)
59
+ - [sidekiq-global_id](#sidekiq-global_id)
60
+ - [sidekiq-status](#sidekiq-status)
61
+ - [Global Configuration](#global-configuration)
62
+ - [debug_lua](#debug_lua)
63
+ - [lock_timeout](#lock_timeout)
64
+ - [lock_ttl](#lock_ttl)
65
+ - [enabled](#enabled)
66
+ - [logger](#logger)
67
+ - [max_history](#max_history)
68
+ - [reaper](#reaper)
69
+ - [reaper_count](#reaper_count)
70
+ - [reaper_interval](#reaper_interval)
71
+ - [reaper_timeout](#reaper_timeout)
72
+ - [lock_prefix](#lock_prefix)
73
+ - [lock_info](#lock_info)
74
+ - [Worker Configuration](#worker-configuration)
75
+ - [lock_info](#lock_info-1)
76
+ - [lock_prefix](#lock_prefix-1)
77
+ - [lock_ttl](#lock_ttl-1)
78
+ - [lock_timeout](#lock_timeout-1)
79
+ - [unique_across_queues](#unique_across_queues)
80
+ - [unique_across_workers](#unique_across_workers)
81
+ - [Finer Control over Uniqueness](#finer-control-over-uniqueness)
82
+ - [After Unlock Callback](#after-unlock-callback)
83
+ - [Communication](#communication)
84
+ - [Contributing](#contributing)
85
+ - [Contributors](#contributors)
86
+
87
+ <!-- /MarkdownTOC -->
88
+
89
+ ## Introduction
90
+
91
+ This gem adds unique constraints to sidekiq jobs. The uniqueness is achieved by creating a set of keys in redis based off of `queue`, `class`, `args` (in the sidekiq job hash).
92
+
93
+ By default, only one lock for a given hash can be acquired. What happens when a lock can't be acquired is governed by a chosen [Conflict Strategy](#conflict-strategy) strategy. Unless a conflict strategy is chosen (?)
94
+
95
+ This is the documentation for the `main` branch. You can find the documentation for each release by navigating to its tag.
96
+
97
+ Here are links to some of the old versions
98
+
99
+ - [v7.0.12](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.0.12)
100
+ - [v6.0.25](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v6.0.25)
101
+ - [v5.0.10](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v5.0.10)
102
+ - [v4.0.18](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v4.0.18)
2
103
 
3
- The missing unique jobs for sidekiq
104
+ ## Usage
105
+
106
+ ### Installation
107
+
108
+ Add this line to your application's Gemfile:
109
+
110
+ ```ruby
111
+ gem 'sidekiq-unique-jobs'
112
+ ```
113
+
114
+ And then execute:
115
+
116
+ ```bash
117
+ bundle
118
+ ```
119
+
120
+ ### Add the middleware
121
+
122
+ 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.
123
+
124
+ *NOTE* if you want to use the reaper you also need to configure the server middleware.
125
+
126
+ The following shows how to modify your `config/initializers/sidekiq.rb` file to use the middleware. [Here is a full example.](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/myapp/config/initializers/sidekiq.rb#L12)
127
+
128
+ ```ruby
129
+ require "sidekiq-unique-jobs"
130
+
131
+ Sidekiq.configure_server do |config|
132
+ config.redis = { url: ENV["REDIS_URL"], driver: :hiredis }
133
+
134
+ config.client_middleware do |chain|
135
+ chain.add SidekiqUniqueJobs::Middleware::Client
136
+ end
137
+
138
+ config.server_middleware do |chain|
139
+ chain.add SidekiqUniqueJobs::Middleware::Server
140
+ end
141
+
142
+ SidekiqUniqueJobs::Server.configure(config)
143
+ end
144
+
145
+ Sidekiq.configure_client do |config|
146
+ config.redis = { url: ENV["REDIS_URL"], driver: :hiredis }
147
+
148
+ config.client_middleware do |chain|
149
+ chain.add SidekiqUniqueJobs::Middleware::Client
150
+ end
151
+ end
152
+ ```
153
+
154
+ ### Your first worker
155
+
156
+ The lock type most likely to be is `:until_executed`. This type of lock creates a lock from when `UntilExecutedWorker.perform_async` is called until right after `UntilExecutedWorker.new.perform` has been called.
157
+
158
+ ```ruby
159
+ # frozen_string_literal: true
160
+
161
+ class UntilExecutedWorker
162
+ include Sidekiq::Worker
163
+
164
+ sidekiq_options lock: :until_executed
165
+
166
+ def perform
167
+ logger.info("cowboy")
168
+ sleep(1) # hardcore processing
169
+ logger.info("beebop")
170
+ end
171
+ end
172
+ ```
173
+
174
+ You can read more about the worker configuration in [Worker Configuration](#worker-configuration) below.
4
175
 
5
176
  ## Requirements
6
177
 
178
+ - Sidekiq `>= 5.0` (`>= 5.2` recommended)
179
+ - Ruby:
180
+ - MRI `>= 2.5` (`>= 2.6` recommended)
181
+ - JRuby `>= 9.0` (`>= 9.2` recommended)
182
+ - Truffleruby
183
+ - Redis Server `>= 3.2` (`>= 5.0` recommended)
184
+ - [ActiveJob officially not supported][48]
185
+ - [redis-namespace officially not supported][49]
7
186
 
8
- See https://github.com/mperham/sidekiq#requirements for what is required. Starting from 3.0.13 only sidekiq 3 is supported and support for MRI 1.9 is dropped (it might work but won't be worked on)
187
+ See [Sidekiq requirements][24] for detailed requirements of Sidekiq itself (be sure to check the right sidekiq version).
9
188
 
10
- ### Version 4 Upgrade instructions
189
+ ## Locks
11
190
 
12
- Version 4 requires redis >= 2.6.2!! Don't upgrade to version 4 unless you are on redis >= 2.6.2.
191
+ ### Until Executing
13
192
 
14
- Easy path - Drop all your unique jobs before upgrading the gem!
193
+ A lock is created when `UntilExecuting.perform_async` is called. Then it is either unlocked when `lock_ttl` is hit or before Sidekiq calls the `perform` method on your worker.
15
194
 
16
- Hard path - See below... Start with a clean slate :)
195
+ #### Example worker
17
196
 
18
- ## Installation
197
+ ```ruby
198
+ class UntilExecuting
199
+ include Sidekiq::Workers
19
200
 
20
- Add this line to your application's Gemfile:
201
+ sidekiq_options lock: :until_executing
21
202
 
22
- gem 'sidekiq-unique-jobs'
203
+ def perform(id)
204
+ # Do work
205
+ end
206
+ end
207
+ ```
23
208
 
24
- And then execute:
209
+ **NOTE** this is probably not so good for jobs that shouldn't be running simultaneously (aka slow jobs).
210
+
211
+ 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)
212
+
213
+ ### Until Executed
214
+
215
+ A lock is created when `UntilExecuted.perform_async` is called. Then it is either unlocked when `lock_ttl` is hit or when Sidekiq has called the `perform` method on your worker.
25
216
 
26
- $ bundle
217
+ #### Example worker
218
+
219
+ ```ruby
220
+ class UntilExecuted
221
+ include Sidekiq::Workers
27
222
 
28
- Or install it yourself as:
223
+ sidekiq_options lock: :until_executed
29
224
 
30
- $ gem install sidekiq-unique-jobs
225
+ def perform(id)
226
+ # Do work
227
+ end
228
+ end
229
+ ```
31
230
 
32
- ## Locking
231
+ ### Until Expired
33
232
 
34
- Like @mperham mentions on [this wiki page](https://github.com/mperham/sidekiq/wiki/Related-Projects#unique-jobs) it is hard to enforce uniqueness with redis in a distributed redis setting.
233
+ This lock behaves identically to the [Until Executed](#until-executed) except for one thing. This job won't be unlocked until the expiration is hit. For jobs that need to run only once per day, this would be the perfect lock. This way, we can't create more jobs until one day after this job was first pushed.
35
234
 
36
- To make things worse there are many ways of wanting to enforce uniqueness.
235
+ #### Example worker
236
+
237
+ ```ruby
238
+ class UntilExpired
239
+ include Sidekiq::Workers
240
+
241
+ sidekiq_options lock: :until_expired, lock_ttl: 1.day
242
+
243
+ def perform
244
+ # Do work
245
+ end
246
+ end
247
+ ```
248
+
249
+ ### Until And While Executing
250
+
251
+ This lock is a combination of two locks (`:until_executing` and `:while_executing`). Please see the configuration for [Until Executing](#until-executing) and [While Executing](#while-executing)
252
+
253
+ #### Example worker
254
+
255
+ ```ruby
256
+ class UntilAndWhileExecutingWorker
257
+ include Sidekiq::Workers
258
+
259
+ sidekiq_options lock: :until_and_while_executing,
260
+ lock_timeout: 2,
261
+ on_conflict: {
262
+ client: :log,
263
+ server: :raise
264
+ }
265
+ def perform(id)
266
+ # Do work
267
+ end
268
+ end
269
+ ```
37
270
 
38
271
  ### While Executing
39
272
 
40
- Due to discoverability of the different lock types `unique` sidekiq option it was decided to use the `while_executing` as a default. Most people will note that scheduling any number of jobs with the same arguments is possible.
273
+ These locks are put on a queue without any type of locking mechanism, the locking doesn't happen until Sidekiq pops the job from the queue and starts processing it.
274
+
275
+ #### Example worker
41
276
 
42
277
  ```ruby
43
- sidekiq_options unique: :while_executing
278
+ class WhileExecutingWorker
279
+ include Sidekiq::Workers
280
+
281
+ sidekiq_options lock: :while_executing,
282
+ lock_timeout: 2,
283
+ on_conflict: {
284
+ server: :raise
285
+ }
286
+ def perform(id)
287
+ # Do work
288
+ end
289
+ end
44
290
  ```
45
291
 
46
- Is to make sure that a job can be scheduled any number of times but only executed a single time per argument provided to the job we call this runtime uniqueness. This is probably most useful for background jobs that are fast to execute. (See mhenrixon/sidekiq-unique-jobs#111 for a great example of when this would be right.) While the job is executing/performing no other jobs can be executed at the same time.
292
+ **NOTE** Unless a conflict strategy of `:raise` is specified, if lock fails, the job will be dropped without notice. When told to raise, the job will be put back and retried. It would also be possible to use `:reschedule` with this lock.
47
293
 
48
- ### Until Executing
294
+ **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.
295
+
296
+ 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`.
297
+
298
+ In the console you should see something like:
299
+
300
+ ```bash
301
+ 0:32:24 worker.1 | 2017-04-23T08:32:24.955Z 84404 TID-ougq4thko WhileExecutingWorker JID-400ec51c9523f41cd4a35058 INFO: start
302
+ 10:32:24 worker.1 | 2017-04-23T08:32:24.956Z 84404 TID-ougq8csew WhileExecutingWorker JID-8d6d9168368eedaed7f75763 INFO: start
303
+ 10:32:24 worker.1 | 2017-04-23T08:32:24.957Z 84404 TID-ougq8crt8 WhileExecutingWorker JID-affcd079094c9b26e8b9ba60 INFO: start
304
+ 10:32:24 worker.1 | 2017-04-23T08:32:24.959Z 84404 TID-ougq8cs8s WhileExecutingWorker JID-9e197460c067b22eb1b5d07f INFO: start
305
+ 10:32:24 worker.1 | 2017-04-23T08:32:24.959Z 84404 TID-ougq4thko WhileExecutingWorker JID-400ec51c9523f41cd4a35058 WhileExecutingWorker INFO: perform(1, 2)
306
+ 10:32:34 worker.1 | 2017-04-23T08:32:34.964Z 84404 TID-ougq4thko WhileExecutingWorker JID-400ec51c9523f41cd4a35058 INFO: done: 10.009 sec
307
+ 10:32:34 worker.1 | 2017-04-23T08:32:34.965Z 84404 TID-ougq8csew WhileExecutingWorker JID-8d6d9168368eedaed7f75763 WhileExecutingWorker INFO: perform(1, 2)
308
+ 10:32:44 worker.1 | 2017-04-23T08:32:44.965Z 84404 TID-ougq8crt8 WhileExecutingWorker JID-affcd079094c9b26e8b9ba60 WhileExecutingWorker INFO: perform(1, 2)
309
+ 10:32:44 worker.1 | 2017-04-23T08:32:44.965Z 84404 TID-ougq8csew WhileExecutingWorker JID-8d6d9168368eedaed7f75763 INFO: done: 20.009 sec
310
+ 10:32:54 worker.1 | 2017-04-23T08:32:54.970Z 84404 TID-ougq8cs8s WhileExecutingWorker JID-9e197460c067b22eb1b5d07f WhileExecutingWorker INFO: perform(1, 2)
311
+ 10:32:54 worker.1 | 2017-04-23T08:32:54.969Z 84404 TID-ougq8crt8 WhileExecutingWorker JID-affcd079094c9b26e8b9ba60 INFO: done: 30.012 sec
312
+ 10:33:04 worker.1 | 2017-04-23T08:33:04.973Z 84404 TID-ougq8cs8s WhileExecutingWorker JID-9e197460c067b22eb1b5d07f INFO: done: 40.014 sec
313
+ ```
314
+
315
+ ### Custom Locks
316
+
317
+ You may need to define some custom lock. You can define it in one project folder:
49
318
 
50
319
  ```ruby
51
- sidekiq_options unique: :until_executing
320
+ # lib/locks/my_custom_lock.rb
321
+ module Locks
322
+ class MyCustomLock < SidekiqUniqueJobs::Lock::BaseLock
323
+ def execute
324
+ # Do something ...
325
+ end
326
+ end
327
+ end
52
328
  ```
53
329
 
54
- This means that a job can only be scheduled into redis once per whatever the configuration of unique arguments. Any jobs added until the first one of the same arguments has been unlocked will just be dropped. This is what was tripping many people up. They would schedule a job to run in the future and it would be impossible to schedule new jobs with those same arguments even immediately. There was some forth and back between also locking jobs on the scheduled queue and the regular queues but in the end I decided it was best to separate these two features out into different locking mechanisms. I think what most people are after is to be able to lock a job while executing or that seems to be what people are most missing at the moment.
330
+ You can refer on all the locks defined in `lib/sidekiq_unique_jobs/lock/*.rb`.
55
331
 
56
- ### Until Executed
332
+ In order to make it available, you should call in your project startup:
333
+
334
+ (For rails application config/initializers/sidekiq_unique_jobs.rb or other projects, wherever you prefer)
57
335
 
58
336
  ```ruby
59
- sidekiq_options unique: :until_executed
337
+ SidekiqUniqueJobs.configure do |config|
338
+ config.add_lock :my_custom_lock, Locks::MyCustomLock
339
+ end
60
340
  ```
61
341
 
62
- This is the combination of the two above. First we lock the job until it executes, then as the job begins executes we keep the lock so that no other jobs with the same arguments can execute at the same time.
342
+ And then you can use it in the jobs definition:
343
+
344
+ `sidekiq_options lock: :my_custom_lock, on_conflict: :log`
63
345
 
64
- ### Until Timeout
346
+ Please not that if you try to override a default lock, an `ArgumentError` will be raised.
347
+
348
+ ## Conflict Strategy
349
+
350
+ 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.
351
+
352
+ Furthermore, `log` can be be used with the lock `UntilExecuted` and `UntilExpired`. Now we write a log entry saying the job could not be pushed because it is a duplicate of another job with the same arguments.
353
+
354
+ It is possible for locks to have different conflict strategy for the client and server. This is useful for `:until_and_while_executing`.
355
+
356
+ ```ruby
357
+ sidekiq_options lock: :until_and_while_executing,
358
+ on_conflict: { client: :log, server: :reject }
359
+ ```
360
+
361
+ ### log
65
362
 
66
363
  ```ruby
67
- sidekiq_options unique: :until_timeout
364
+ sidekiq_options on_conflict: :log
68
365
  ```
69
366
 
70
- The job won't be unlocked until the timeout/expiry runs out.
367
+ This strategy is intended to be used with `UntilExecuted` and `UntilExpired`. It will log a line that this job is a duplicate of another.
71
368
 
72
- ### Unique Until And While Executing
369
+ ### raise
73
370
 
74
371
  ```ruby
75
- sidekiq_options unique: :until_and_while_executing
372
+ sidekiq_options on_conflict: :raise
76
373
  ```
77
374
 
78
- This lock is exactly what you would expect. It is considered unique in a way until executing begins and it is locked while executing so what differs from `UntilExecuted`?
375
+ 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.
79
376
 
80
- The difference is that this job has two types of uniqueness:
81
- 1. It is unique until execution
82
- 2. It is unique while executing
377
+ ### reject
83
378
 
84
- That means it locks for any job with the same arguments to be persisted into redis and just like you would expect it will only ever allow one job of the same unique arguments to run at any given time but as soon as the runtime lock has been acquired the schedule/async lock is released.
379
+ ```ruby
380
+ sidekiq_options on_conflict: :reject
381
+ ```
85
382
 
86
- ### Uniqueness Scope
383
+ This strategy is intended to be used with `WhileExecuting` and will push the job to the dead queue on conflict.
87
384
 
88
- - Queue specific locks
89
- - Across all queues.
90
- - Timed / Scheduled jobs
385
+ ### replace
91
386
 
92
- ## Usage
387
+ ```ruby
388
+ sidekiq_options on_conflict: :replace
389
+ ```
390
+
391
+ This strategy is intended to be used with client locks like `UntilExecuted`.
392
+ It will delete any existing job for these arguments from retry, schedule and
393
+ queue and retry the lock again.
93
394
 
94
- All that is required is that you specifically set the sidekiq option for *unique* to a valid value like below:
395
+ This is slightly dangerous and should probably only be used for jobs that are
396
+ always scheduled in the future. Currently only attempting to retry one time.
397
+
398
+ ### reschedule
95
399
 
96
400
  ```ruby
97
- sidekiq_options unique: :while_executing
401
+ sidekiq_options on_conflict: :reschedule
98
402
  ```
99
403
 
100
- For jobs scheduled in the future it is possible to set for how long the job
101
- should be unique. The job will be unique for the number of seconds configured (default 30 minutes)
102
- or until the job has been completed. Thus, the job will be unique for the shorter of the two. Note that Sidekiq versions before 3.0 will remove job keys after an hour, which means jobs can remain unique for at most an hour.
404
+ 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.
103
405
 
104
- If you want the unique job to stick around even after it has been successfully
105
- processed then just set `unique: :until_timeout`.
406
+ ### Custom Strategies
106
407
 
107
- You can also control the expiration length of the uniqueness check. If you want to enforce uniqueness over a longer period than the default of 30 minutes then you can pass the number of seconds you want to use to the sidekiq options:
408
+ You may need to define some custom strategy. You can define it in one project folder:
108
409
 
109
410
  ```ruby
110
- sidekiq_options unique: :until_timeout, unique_expiration: 120 * 60 # 2 hours
411
+ # lib/strategies/my_custom_strategy.rb
412
+ module Strategies
413
+ class MyCustomStrategy < SidekiqUniqueJobs::OnConflict::Strategy
414
+ def call
415
+ # Do something ...
416
+ end
417
+ end
418
+ end
111
419
  ```
112
420
 
113
- For locking modes (`:while_executing` and `:until_and_while_executing`) you can control the expiration length of the runtime uniqueness. If you want to enforce uniqueness over a longer period than the default of 60 seconds, then you can pass the number of seconds you want to use to the sidekiq options:
421
+ You can refer to all the strategies defined in `lib/sidekiq_unique_jobs/on_conflict`.
422
+
423
+ In order to make it available, you should call in your project startup:
424
+
425
+ (For rails application config/initializers/sidekiq_unique_jobs.rb for other projects, wherever you prefer)
114
426
 
115
427
  ```ruby
116
- sidekiq_options unique: :while_executing, run_lock_expiration: 2 * 60 # 2 minutes
428
+ SidekiqUniqueJobs.configure do |config|
429
+ config.add_strategy :my_custom_strategy, Strategies::MyCustomStrategy
430
+ end
431
+ ```
432
+
433
+ And then you can use it in the jobs definition:
434
+
435
+ ```ruby
436
+ sidekiq_options lock: :while_executing, on_conflict: :my_custom_strategy
117
437
  ```
118
438
 
119
- Requiring the gem in your gemfile should be sufficient to enable unique jobs.
439
+ Please not that if you try to override a default lock, an `ArgumentError` will be raised.
440
+
441
+ ### 3 Cleanup Dead Locks
120
442
 
121
- ### Usage with ActiveJob
443
+ For sidekiq versions < 5.1 a `sidekiq_retries_exhausted` block is required per worker class. This is deprecated in Sidekiq 6.0
122
444
 
123
445
  ```ruby
124
- Sidekiq.default_worker_options = {
125
- unique: :until_executing,
126
- unique_args: ->(args) { args.first.except('job_id') }
127
- }
446
+ class MyWorker
447
+ sidekiq_retries_exhausted do |msg, _ex|
448
+ digest = msg['lock_digest']
449
+ SidekiqUniqueJobs::Digests.new.delete_by_digest(digest) if digest
450
+ end
451
+ end
128
452
  ```
129
453
 
454
+ 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.
130
455
 
131
- ### Finer Control over Uniqueness
456
+ ```ruby
457
+ Sidekiq.configure_server do |config|
458
+ config.death_handlers << ->(job, _ex) do
459
+ digest = job['lock_digest']
460
+ SidekiqUniqueJobs::Digests.new.delete_by_digest(digest) if digest
461
+ end
462
+ end
463
+ ```
132
464
 
133
- 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.
465
+ ## Debugging
134
466
 
135
- The unique_args method need to return an array of values to use for uniqueness check.
467
+ 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.
136
468
 
137
- The method or the proc can return a modified version of args without the transient arguments included, as shown below:
469
+ ### Sidekiq Web
470
+
471
+ To use the web extension you need to require it in your routes.
138
472
 
139
473
  ```ruby
140
- class UniqueJobWithFilterMethod
141
- include Sidekiq::Worker
142
- sidekiq_options unique: :until_and_during_execution,
143
- unique_args: :unique_args
474
+ #app/config/routes.rb
475
+ require 'sidekiq_unique_jobs/web'
476
+ mount Sidekiq::Web, at: '/sidekiq'
477
+ ```
478
+
479
+ There is no need to `require 'sidekiq/web'` since `sidekiq_unique_jobs/web`
480
+ already does this.
481
+
482
+ 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`.
483
+
484
+ ### Reflections (metrics, logging, etc.)
144
485
 
145
- def self.unique_args(name, id, options)
146
- [ name, options[:type] ]
486
+ To be able to gather some insights on what is going on inside this gem. I provide a reflection API that can be used.
487
+
488
+ To setup reflections for logging or metrics, use the following API:
489
+
490
+ ```ruby
491
+
492
+ def extract_log_from_job(message, job_hash)
493
+ worker = job_hash['class']
494
+ args = job_hash['args']
495
+ lock_args = job_hash['lock_args']
496
+ queue = job_hash['queue']
497
+ {
498
+ message: message,
499
+ worker: worker,
500
+ args: args,
501
+ lock_args: lock_args,
502
+ queue: queue
503
+ }
504
+ end
505
+
506
+ SidekiqUniqueJobs.reflect do |on|
507
+ on.lock_failed do |job_hash|
508
+ message = extract_log_from_job('Lock Failed', job_hash)
509
+ Sidekiq.logger.warn(message)
147
510
  end
511
+ end
512
+ ```
148
513
 
149
- ...
514
+ #### after_unlock_callback_failed
515
+
516
+ This is called when you have configured a custom callback for when a lock has been released.
517
+
518
+ #### error
519
+
520
+ Not in use yet but will be used deep into the stack to provide a means to catch and report errors inside the gem.
521
+
522
+ #### execution_failed
523
+
524
+ When the sidekiq processor picks the job of the queue for certain jobs but your job raised an error to the middleware. This will be the reflection. It is probably nothing to worry about. When your worker raises an error, we need to handle some edge cases for until and while executing.
525
+
526
+ #### lock_failed
527
+
528
+ If we can't achieve a lock, this will be the reflection. It most likely is nothing to worry about. We just couldn't retrieve a lock in a timely fashion.
529
+
530
+ The biggest reason for this reflection would be to gather metrics on which workers fail the most at the locking step for example.
531
+
532
+ #### locked
533
+
534
+ For when a lock has been successful. Again, mostly useful for metrics I suppose.
535
+
536
+ #### reschedule_failed
537
+
538
+ For when the reschedule strategy failed to reschedule the job.
150
539
 
540
+ #### rescheduled
541
+
542
+ For when a job was successfully rescheduled
543
+
544
+ #### timeout
545
+
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
+
548
+ ### unlock_failed
549
+
550
+ This is not got, this is worth
551
+
552
+ ### unlocked
553
+
554
+ Also mostly useful for reporting purposes. The job was successfully unlocked.
555
+
556
+ ### unknown_sidekiq_worker
557
+
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
+
560
+ #### Show Locks
561
+
562
+ ![Locks](assets/unique_digests_1.png)
563
+
564
+ #### Show Lock
565
+
566
+ ![Lock](assets/unique_digests_2.png)
567
+
568
+ ## Testing
569
+
570
+ ### Validating Worker Configuration
571
+
572
+ 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.
573
+
574
+ Let's take a _bad_ worker:
575
+
576
+ ```ruby
577
+ #app/workers/bad_worker.rb
578
+ class BadWorker
579
+ sidekiq_options lock: :while_executing, on_conflict: :replace
151
580
  end
152
581
 
153
- class UniqueJobWithFilterProc
582
+ #spec/workers/bad_worker_spec.rb
583
+
584
+ require "sidekiq_unique_jobs/testing"
585
+ #OR
586
+ require "sidekiq_unique_jobs/rspec/matchers"
587
+
588
+ RSpec.describe BadWorker do
589
+ specify { expect(described_class).to have_valid_sidekiq_options }
590
+ end
591
+ ```
592
+
593
+ This gives us a helpful error message for a wrongly configured worker:
594
+
595
+ ```bash
596
+ Expected BadWorker to have valid sidekiq options but found the following problems:
597
+ on_server_conflict: :replace is incompatible with the server process
598
+ ```
599
+
600
+ If you are not using RSpec (a lot of people prefer minitest or test unit) you can do something like:
601
+
602
+ ```ruby
603
+ assert_raise(InvalidWorker){ SidekiqUniqueJobs.validate_worker!(BadWorker.get_sidekiq_options) }
604
+ ```
605
+
606
+ ### Uniqueness
607
+
608
+ 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][](?)
609
+
610
+ ```ruby
611
+ SidekiqUniqueJobs.configure do |config|
612
+ config.enabled = !Rails.env.test?
613
+ config.logger_enabled = !Rails.env.test?
614
+ end
615
+ ```
616
+
617
+ 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).
618
+
619
+ ```ruby
620
+ require "sidekiq_unique_jobs/testing"
621
+
622
+ RSpec.describe Workers::CoolOne do
623
+ before do
624
+ SidekiqUniqueJobs.config.enabled = false
625
+ end
626
+
627
+ # ... your tests that don't test uniqueness
628
+
629
+ context 'when Sidekiq::Testing.disabled?' do
630
+ before do
631
+ Sidekiq::Testing.disable!
632
+ Sidekiq.redis(&:flushdb)
633
+ end
634
+
635
+ after do
636
+ Sidekiq.redis(&:flushdb)
637
+ end
638
+
639
+ it 'prevents duplicate jobs from being scheduled' do
640
+ SidekiqUniqueJobs.use_config(enabled: true) do
641
+ expect(described_class.perform_in(3600, 1)).not_to eq(nil)
642
+ expect(described_class.perform_async(1)).to eq(nil)
643
+ end
644
+ end
645
+ end
646
+ end
647
+ ```
648
+
649
+ 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:
650
+
651
+ - [spec/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb)
652
+ - [spec/sidekiq_unique_jobs/lock/until_executed_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/sidekiq_unique_jobs/lock/until_executed_spec.rb)
653
+ - [spec/sidekiq_unique_jobs/lock/until_expired_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/sidekiq_unique_jobs/lock/until_expired_spec.rb)
654
+ - [spec/sidekiq_unique_jobs/lock/while_executing_reject_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/sidekiq_unique_jobs/lock/while_executing_reject_spec.rb)
655
+ - [spec/sidekiq_unique_jobs/lock/while_executing_spec.rb](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/spec/sidekiq_unique_jobs/lock/while_executing_spec.rb)
656
+
657
+ ## Configuration
658
+
659
+ ### Other Sidekiq gems
660
+
661
+ #### apartment-sidekiq
662
+
663
+ 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.
664
+
665
+ ```ruby
666
+ Sidekiq.client_middleware do |chain|
667
+ chain.add Apartment::Sidekiq::Middleware::Client
668
+ chain.add SidekiqUniqueJobs::Middleware::Client
669
+ end
670
+
671
+ Sidekiq.server_middleware do |chain|
672
+ chain.add Apartment::Sidekiq::Middleware::Server
673
+ chain.add SidekiqUniqueJobs::Middleware::Server
674
+ end
675
+ ```
676
+
677
+ 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
678
+
679
+ #### sidekiq-global_id
680
+
681
+ 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.
682
+
683
+ For a working setup check the following [file](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/myapp/config/sidekiq.rb#L12).
684
+
685
+ ```ruby
686
+ Sidekiq.client_middleware do |chain|
687
+ chain.add Sidekiq::GlobalId::ClientMiddleware
688
+ chain.add SidekiqUniqueJobs::Middleware::Client
689
+ end
690
+
691
+ Sidekiq.server_middleware do |chain|
692
+ chain.add Sidekiq::GlobalId::ServerMiddleware
693
+ chain.add SidekiqUniqueJobs::Middleware::Server
694
+ end
695
+ ```
696
+
697
+ 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.
698
+
699
+ #### sidekiq-status
700
+
701
+ 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.
702
+
703
+ ```ruby
704
+ # Thanks to @ArturT for the correction
705
+
706
+ Sidekiq.configure_server do |config|
707
+ config.client_middleware do |chain|
708
+ chain.add SidekiqUniqueJobs::Middleware::Client
709
+ chain.add Sidekiq::Status::ClientMiddleware, expiration: 30.minutes
710
+ end
711
+
712
+ config.server_middleware do |chain|
713
+ chain.add Sidekiq::Status::ServerMiddleware, expiration: 30.minutes
714
+ chain.add SidekiqUniqueJobs::Middleware::Server
715
+ end
716
+
717
+ SidekiqUniqueJobs::Server.configure(config)
718
+ end
719
+
720
+
721
+ Sidekiq.configure_client do |config|
722
+ config.client_middleware do |chain|
723
+ chain.add SidekiqUniqueJobs::Middleware::Client
724
+ chain.add Sidekiq::Status::ClientMiddleware, expiration: 30.minutes
725
+ end
726
+ end
727
+ ```
728
+
729
+ 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.
730
+
731
+ ### Global Configuration
732
+
733
+ The gem supports a few different configuration options that might be of interest if you run into some weird issues.
734
+
735
+ Configure SidekiqUniqueJobs in an initializer or the sidekiq initializer on application startup.
736
+
737
+ ```ruby
738
+ SidekiqUniqueJobs.configure do |config|
739
+ config.logger = Sidekiq.logger # default, change at your own discretion
740
+ config.logger_enabled = true # default, disable for test environments
741
+ config.debug_lua = false # Turn on when debugging
742
+ config.lock_info = false # Turn on when debugging
743
+ config.lock_ttl = 600 # Expire locks after 10 minutes
744
+ config.lock_timeout = nil # turn off lock timeout
745
+ config.max_history = 0 # Turn on when debugging
746
+ config.reaper = :ruby # :ruby, :lua or :none/nil
747
+ config.reaper_count = 1000 # Stop reaping after this many keys
748
+ config.reaper_interval = 600 # Reap orphans every 10 minutes
749
+ config.reaper_timeout = 150 # Timeout reaper after 2.5 minutes
750
+ end
751
+ ```
752
+
753
+ #### debug_lua
754
+
755
+ ```ruby
756
+ SidekiqUniqueJobs.config.debug_lua #=> false
757
+ ```
758
+
759
+ 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.
760
+
761
+ #### lock_timeout
762
+
763
+ ```ruby
764
+ SidekiqUniqueJobs.config.lock_timeout #=> 0
765
+ ```
766
+
767
+ Set a global lock_timeout to use for all jobs that don't otherwise specify a lock_timeout.
768
+
769
+ 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.
770
+
771
+ #### lock_ttl
772
+
773
+ ```ruby
774
+ SidekiqUniqueJobs.config.lock_ttl #=> nil
775
+ ```
776
+
777
+ Set a global lock_ttl to use for all jobs that don't otherwise specify a lock_ttl.
778
+
779
+ Lock TTL decides how long to wait at most before considering a lock to be expired and making it possible to reuse that lock.
780
+
781
+ #### enabled
782
+
783
+ ```ruby
784
+ SidekiqUniqueJobs.config.enabled #=> true
785
+ ```
786
+
787
+ Globally turn the locking mechanism on or off.
788
+
789
+ #### logger
790
+
791
+ ```ruby
792
+ SidekiqUniqueJobs.config.logger #=> #<Sidekiq::Logger:0x00007fdc1f96d180>
793
+ ```
794
+
795
+ 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.
796
+
797
+ #### max_history
798
+
799
+ ```ruby
800
+ SidekiqUniqueJobs.config.max_history #=> 1_000
801
+ ```
802
+
803
+ 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.
804
+
805
+ 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.
806
+
807
+ #### reaper
808
+
809
+ ```ruby
810
+ SidekiqUniqueJobs.config.reaper #=> :ruby
811
+ ```
812
+
813
+ 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.
814
+
815
+ In my benchmarks deleting 1000 orphaned locks with lua performs around 65% faster than deleting 1000 keys in ruby.
816
+
817
+ On the other hand if I increase it to 10 000 orphaned locks per cleanup (`reaper_count: 10_0000`) then redis starts throwing:
818
+
819
+ > BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (Redis::CommandError)
820
+
821
+ 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.
822
+
823
+ ```ruby
824
+ SidekiqUniqueJobs.config.reaper = :none
825
+ SidekiqUniqueJobs.config.reaper = nil
826
+ SidekiqUniqueJobs.config.reaper = false
827
+ ```
828
+
829
+ #### reaper_count
830
+
831
+ ```ruby
832
+ SidekiqUniqueJobs.config.reaper_count #=> 1_000
833
+ ```
834
+
835
+ 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.
836
+
837
+ #### reaper_interval
838
+
839
+ ```ruby
840
+ SidekiqUniqueJobs.config.reaper_interval #=> 600
841
+ ```
842
+
843
+ The number of seconds between reaping.
844
+
845
+ #### reaper_timeout
846
+
847
+ ```ruby
848
+ SidekiqUniqueJobs.config.reaper_timeout #=> 10
849
+ ```
850
+
851
+ 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.
852
+
853
+ #### lock_prefix
854
+
855
+ ```ruby
856
+ SidekiqUniqueJobs.config.lock_prefix #=> "uniquejobs"
857
+ ```
858
+
859
+ Use if you want a different key prefix for the keys in redis.
860
+
861
+ ### lock_info
862
+
863
+ ```ruby
864
+ SidekiqUniqueJobs.config.lock_info #=> false
865
+ ```
866
+
867
+ 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.
868
+
869
+ ### Worker Configuration
870
+
871
+ #### lock_info
872
+
873
+ 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.
874
+
875
+ ```ruby
876
+ sidekiq_options lock_info: false # this is the default, set to true to turn on
877
+ ```
878
+
879
+ #### lock_prefix
880
+
881
+ Use if you want a different key prefix for the keys in redis.
882
+
883
+ ```ruby
884
+ sidekiq_options lock_prefix: "uniquejobs" # this is the default value
885
+ ```
886
+
887
+ #### lock_ttl
888
+
889
+ Lock TTL decides how long to wait at most before considering a lock to be expired and making it possible to reuse that lock.
890
+
891
+ Starting from `v7` the expiration will take place when the job is pushed to the queue.
892
+
893
+ ```ruby
894
+ sidekiq_options lock_ttl: nil # default - don't expire keys
895
+ sidekiq_options lock_ttl: 20.days.to_i # expire this lock in 20 days
896
+ ```
897
+
898
+ #### lock_timeout
899
+
900
+ 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.
901
+
902
+ ```ruby
903
+ sidekiq_options lock_timeout: 0 # default - don't wait at all
904
+ sidekiq_options lock_timeout: 5 # wait 5 seconds
905
+ sidekiq_options lock_timeout: nil # lock indefinitely, this process won't continue until it gets a lock. VERY DANGEROUS!!
906
+ ```
907
+
908
+ #### unique_across_queues
909
+
910
+ 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.
911
+
912
+ This is mainly intended for `Worker.set(queue: :another).perform_async`.
913
+
914
+ ```ruby
915
+ class Worker
154
916
  include Sidekiq::Worker
155
- sidekiq_options unique: :until_executed,
156
- unique_args: ->(args) { [ args.first ] }
157
917
 
158
- ...
918
+ sidekiq_options unique_across_queues: true, queue: 'default'
159
919
 
920
+ def perform(args); end
160
921
  end
161
922
  ```
162
923
 
163
- The previous problems with unique args being string in server and symbol in client is no longer a problem because the `UniqueArgs` class accounts for this and converts everything to json now. If you find an edge case please provide and example so that we can add coverage and fix it.
924
+ 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`).
164
925
 
165
- ### After Unlock Callback
926
+ #### unique_across_workers
166
927
 
167
- If you are using :after_yield as your unlock ordering, Unique Job offers a callback to perform some work after the block is yielded.
928
+ 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.
168
929
 
169
930
  ```ruby
170
- class UniqueJobWithFilterMethod
931
+ class WorkerOne
171
932
  include Sidekiq::Worker
172
- sidekiq_options unique: :while_executing,
173
933
 
174
- def after_unlock
175
- # block has yielded and lock is released
176
- end
177
- ...
178
- end.
934
+ sidekiq_options unique_across_workers: true, queue: 'default'
935
+
936
+ def perform(args); end
937
+ end
179
938
 
939
+ class WorkerTwo
940
+ include Sidekiq::Worker
941
+
942
+ sidekiq_options unique_across_workers: true, queue: 'default'
943
+
944
+ def perform(args); end
945
+ end
946
+
947
+
948
+ WorkerOne.perform_async(1)
949
+ # => 'the jobs unique id'
950
+
951
+ WorkerTwo.perform_async(1)
952
+ # => nil because WorkerOne just stole the lock
180
953
  ```
181
954
 
182
- ### Logging
955
+ ### Finer Control over Uniqueness
956
+
957
+ 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.
958
+
959
+ *NOTE:* The lock_args method need to return an array of values to use for uniqueness check.
183
960
 
184
- To see logging in sidekiq when duplicate payload has been filtered out you can enable on a per worker basis using the sidekiq options. The default value is false
961
+ *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 `[[...]]`.
962
+
963
+ The method or the proc can return a modified version of args without the transient arguments included, as shown below:
185
964
 
186
965
  ```ruby
187
966
  class UniqueJobWithFilterMethod
188
967
  include Sidekiq::Worker
189
- sidekiq_options unique: :while_executing,
190
- log_duplicate_payload: true
968
+ sidekiq_options lock: :until_and_while_executing,
969
+ lock_args_method: :lock_args # this is default and will be used if such a method is defined
970
+
971
+ def self.lock_args(args)
972
+ [ args[0], args[2][:type] ]
973
+ end
191
974
 
192
975
  ...
193
976
 
194
977
  end
195
- ```
196
978
 
197
- ## Debugging
198
- There are two ways to display and remove keys regarding uniqueness. The console way and the command line way.
979
+ class UniqueJobWithFilterProc
980
+ include Sidekiq::Worker
981
+ sidekiq_options lock: :until_executed,
982
+ lock_args_method: ->(args) { [ args.first ] }
983
+
984
+ ...
985
+
986
+ end
987
+ ```
199
988
 
200
- ### Console
201
- Start the console with the following command `bundle exec jobs console`.
989
+ 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.
202
990
 
203
- #### List Unique Keys
204
- `keys '*', 100`
991
+ ```ruby
992
+ class UniqueJobWithFilterMethod
993
+ include Sidekiq::Worker
994
+ sidekiq_options lock: :until_and_while_executing, lock_args_method: :lock_args
995
+
996
+ def self.lock_args(args)
997
+ if Sidekiq::ProcessSet.new.size > 1
998
+ # sidekiq runtime; uniqueness for the object (first arg)
999
+ args.first
1000
+ else
1001
+ # queuing from the app; uniqueness for all params
1002
+ args
1003
+ end
1004
+ end
1005
+ end
1006
+ ```
205
1007
 
206
- #### Remove Unique Keys
207
- `del '*', 100, false` the dry_run and count parameters are both required. This is to have some type of protection against clearing out all uniqueness.
1008
+ ### After Unlock Callback
208
1009
 
209
- ### Command Line
1010
+ 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.
210
1011
 
211
- `bundle exec jobs` displays help on how to use the unique jobs command line.
1012
+ **Exception 1:** UntilExecuting unlocks and uses callback before yielding.
1013
+ **Exception 2:** UntilExpired expires eventually, no after_unlock hook is called.
212
1014
 
213
- ## Communication
1015
+ **NOTE:** _It is also possible to write this code as a class method._
214
1016
 
215
- There is a [![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) for praise or scorn. This would be a good place to have lengthy discuss or brilliant suggestions or simply just nudge me if I forget about anything.
1017
+ ```ruby
1018
+ class UniqueJobWithFilterMethod
1019
+ include Sidekiq::Worker
1020
+ sidekiq_options lock: :while_executing,
216
1021
 
217
- ## Testing
1022
+ def self.after_unlock
1023
+ # block has yielded and lock is released
1024
+ end
218
1025
 
219
- To enable the testing for `sidekiq-unique-jobs`, add `require 'sidekiq_unique_jobs/testing'` to your testing helper.
1026
+ def after_unlock
1027
+ # block has yielded and lock is released
1028
+ end
1029
+ ...
1030
+ end.
1031
+ ```
220
1032
 
221
- You can if you want use `gem 'mock_redis'` to prevent sidekiq unique jobs using redis.
1033
+ ## Communication
222
1034
 
223
- See https://github.com/mhenrixon/sidekiq-unique-jobs/tree/master/rails_example/spec/controllers/work_controller_spec.rb for an example of how to configure sidekiq and unique jobs without redis.
1035
+ There is a [![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) for praise or scorn. This would be a good place to have lengthy discuss or brilliant suggestions or simply just nudge me if I forget about anything.
224
1036
 
225
1037
  ## Contributing
226
1038
 
227
1039
  1. Fork it
228
- 2. Create your feature branch (`git checkout -b my-new-feature`)
229
- 3. Commit your changes (`git commit -am 'Add some feature'`)
230
- 4. Push to the branch (`git push origin my-new-feature`)
231
- 5. Create new Pull Request
1040
+ 1. Create your feature branch (`git checkout -b my-new-feature`)
1041
+ 1. Commit your changes (`git commit -am 'Add some feature'`)
1042
+ 1. Push to the branch (`git push origin my-new-feature`)
1043
+ 1. Create new Pull Request
232
1044
 
233
1045
  ## Contributors
234
1046
 
235
- In no particular order:
236
-
237
- - https://github.com/salrepe
238
- - https://github.com/rickenharp
239
- - https://github.com/sax
240
- - https://github.com/eduardosasso
241
- - https://github.com/KensoDev
242
- - https://github.com/adstage-david
243
- - https://github.com/jprincipe
244
- - https://github.com/crberube
245
- - https://github.com/simonoff
1047
+ You can find a list of contributors over on [Contributors][]
1048
+
1049
+ [Enterprise unique jobs]: https://github.com/mperham/sidekiq/wiki/Ent-Unique-Jobs
1050
+ [Contributors]: https://github.com/mhenrixon/sidekiq-unique-jobs/graphs/contributors
1051
+ [v4.0.18]: https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v4.0.18
1052
+ [v5.0.10]: https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v5.0.10.
1053
+ [Sidekiq requirements]: https://github.com/mperham/sidekiq#requirements