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
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Class Info provides information about a lock
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ #
9
+ class LockInfo < Redis::String
10
+ #
11
+ # Returns the value for this key as a hash
12
+ #
13
+ #
14
+ # @return [Hash]
15
+ #
16
+ def value
17
+ @value ||= load_json(super)
18
+ end
19
+
20
+ #
21
+ # Check if this redis string is blank
22
+ #
23
+ #
24
+ # @return [Boolean]
25
+ #
26
+ def none?
27
+ value.nil? || value.empty?
28
+ end
29
+
30
+ #
31
+ # Check if this redis string has a value
32
+ #
33
+ #
34
+ # @return [Boolean]
35
+ #
36
+ def present?
37
+ !none?
38
+ end
39
+
40
+ #
41
+ # Quick access to the hash members for the value
42
+ #
43
+ # @param [String, Symbol] key the key who's value to retrieve
44
+ #
45
+ # @return [Object]
46
+ #
47
+ def [](key)
48
+ value[key.to_s] if value.is_a?(Hash)
49
+ end
50
+
51
+ #
52
+ # Writes the lock info to redis
53
+ #
54
+ # @param [Hash] obj the information to store at key
55
+ #
56
+ # @return [Hash]
57
+ #
58
+ def set(obj, pipeline = nil)
59
+ return unless SidekiqUniqueJobs.config.lock_info
60
+ raise InvalidArgument, "argument `obj` (#{obj}) needs to be a hash" unless obj.is_a?(Hash)
61
+
62
+ json = dump_json(obj)
63
+ @value = load_json(json)
64
+ super(json, pipeline)
65
+ value
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ # Calculates timeout and expiration
5
+ #
6
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
7
+ class LockTimeout
8
+ # includes "SidekiqUniqueJobs::SidekiqWorkerMethods"
9
+ # @!parse include SidekiqUniqueJobs::SidekiqWorkerMethods
10
+ include SidekiqUniqueJobs::SidekiqWorkerMethods
11
+
12
+ #
13
+ # Calculates the timeout for a Sidekiq job
14
+ #
15
+ # @param [Hash] item sidekiq job hash
16
+ #
17
+ # @return [Integer] timeout in seconds
18
+ #
19
+ def self.calculate(item)
20
+ new(item).calculate
21
+ end
22
+
23
+ # @!attribute [r] item
24
+ # @return [Hash] the Sidekiq job hash
25
+ attr_reader :item
26
+
27
+ # @param [Hash] item the Sidekiq job hash
28
+ # @option item [Integer, nil] :lock_ttl the configured lock expiration
29
+ # @option item [Integer, nil] :lock_timeout the configured lock timeout
30
+ # @option item [String] :class the class of the sidekiq worker
31
+ # @option item [Float] :at the unix time the job is scheduled at
32
+ def initialize(item)
33
+ @item = item
34
+ self.job_class = item[CLASS]
35
+ end
36
+
37
+ #
38
+ # Finds a lock timeout in either of
39
+ # default worker options, {default_lock_timeout} or provided worker_options
40
+ #
41
+ #
42
+ # @return [Integer, nil]
43
+ #
44
+ def calculate
45
+ timeout = default_job_options[LOCK_TIMEOUT]
46
+ timeout = default_lock_timeout if default_lock_timeout
47
+ timeout = job_options[LOCK_TIMEOUT] if job_options.key?(LOCK_TIMEOUT)
48
+ timeout
49
+ end
50
+
51
+ #
52
+ # The configured default_lock_timeout
53
+ # @see SidekiqUniqueJobs::Config#lock_timeout
54
+ #
55
+ #
56
+ # @return [Integer, nil]
57
+ #
58
+ def default_lock_timeout
59
+ SidekiqUniqueJobs.config.lock_timeout
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ # Calculates timeout and expiration
5
+ #
6
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
7
+ class LockTTL
8
+ # includes "SidekiqUniqueJobs::SidekiqWorkerMethods"
9
+ # @!parse include SidekiqUniqueJobs::SidekiqWorkerMethods
10
+ include SidekiqUniqueJobs::SidekiqWorkerMethods
11
+
12
+ #
13
+ # Computes lock ttl from job arguments, sidekiq_options.
14
+ # Falls back to {SidekiqUniqueJobs::Config#lock_ttl}
15
+ #
16
+ # @note this method takes into consideration the time
17
+ # until a job is scheduled
18
+ #
19
+ #
20
+ # @return [Integer] the number of seconds to live
21
+ #
22
+ def self.calculate(item)
23
+ new(item).calculate
24
+ end
25
+
26
+ # @!attribute [r] item
27
+ # @return [Hash] the Sidekiq job hash
28
+ attr_reader :item
29
+
30
+ # @param [Hash] item the Sidekiq job hash
31
+ # @option item [Integer, nil] :lock_ttl the configured lock expiration
32
+ # @option item [Integer, nil] :lock_timeout the configured lock timeout
33
+ # @option item [String] :class the class of the sidekiq worker
34
+ # @option item [Float] :at the unix time the job is scheduled at
35
+ def initialize(item)
36
+ @item = item
37
+ self.job_class = item[CLASS]
38
+ end
39
+
40
+ #
41
+ # Calculates the time until the job is scheduled starting from now
42
+ #
43
+ #
44
+ # @return [Integer] the number of seconds until job is scheduled
45
+ #
46
+ def time_until_scheduled
47
+ return 0 unless scheduled_at
48
+
49
+ scheduled_at.to_i - Time.now.utc.to_i
50
+ end
51
+
52
+ # The time a job is scheduled
53
+ # @return [Float] the exact unix time the job is scheduled at
54
+ def scheduled_at
55
+ @scheduled_at ||= item[AT]
56
+ end
57
+
58
+ #
59
+ # Computes lock ttl from job arguments, sidekiq_options.
60
+ # Falls back to {SidekiqUniqueJobs::Config#lock_ttl}
61
+ #
62
+ # @note this method takes into consideration the time
63
+ # until a job is scheduled
64
+ #
65
+ #
66
+ # @return [Integer] the number of seconds to live
67
+ #
68
+ def calculate
69
+ ttl = item[LOCK_TTL]
70
+ ttl ||= job_options[LOCK_TTL]
71
+ ttl ||= item[LOCK_EXPIRATION] # TODO: Deprecate at some point
72
+ ttl ||= job_options[LOCK_EXPIRATION] # TODO: Deprecate at some point
73
+ ttl ||= SidekiqUniqueJobs.config.lock_ttl
74
+ ttl && (ttl.to_i + time_until_scheduled)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,383 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ # Lock manager class that handles all the various locks
5
+ #
6
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
7
+ class Locksmith # rubocop:disable Metrics/ClassLength
8
+ # includes "SidekiqUniqueJobs::Connection"
9
+ # @!parse include SidekiqUniqueJobs::Connection
10
+ include SidekiqUniqueJobs::Connection
11
+
12
+ # includes "SidekiqUniqueJobs::Logging"
13
+ # @!parse include SidekiqUniqueJobs::Logging
14
+ include SidekiqUniqueJobs::Logging
15
+
16
+ # includes "SidekiqUniqueJobs::Reflectable"
17
+ # @!parse include SidekiqUniqueJobs::Reflectable
18
+ include SidekiqUniqueJobs::Reflectable
19
+
20
+ # includes "SidekiqUniqueJobs::Timing"
21
+ # @!parse include SidekiqUniqueJobs::Timing
22
+ include SidekiqUniqueJobs::Timing
23
+
24
+ # includes "SidekiqUniqueJobs::Script::Caller"
25
+ # @!parse include SidekiqUniqueJobs::Script::Caller
26
+ include SidekiqUniqueJobs::Script::Caller
27
+
28
+ # includes "SidekiqUniqueJobs::JSON"
29
+ # @!parse include SidekiqUniqueJobs::JSON
30
+ include SidekiqUniqueJobs::JSON
31
+
32
+ #
33
+ # @return [Float] used to take into consideration the inaccuracy of redis timestamps
34
+ CLOCK_DRIFT_FACTOR = 0.01
35
+ NETWORK_FACTOR = 0.04
36
+
37
+ #
38
+ # @!attribute [r] key
39
+ # @return [Key] the key used for locking
40
+ attr_reader :key
41
+ #
42
+ # @!attribute [r] job_id
43
+ # @return [String] a sidekiq JID
44
+ attr_reader :job_id
45
+ #
46
+ # @!attribute [r] config
47
+ # @return [LockConfig] the configuration for this lock
48
+ attr_reader :config
49
+ #
50
+ # @!attribute [r] item
51
+ # @return [Hash] a sidekiq job hash
52
+ attr_reader :item
53
+
54
+ #
55
+ # Initialize a new Locksmith instance
56
+ #
57
+ # @param [Hash] item a Sidekiq job hash
58
+ # @option item [Integer] :lock_ttl the configured expiration
59
+ # @option item [String] :jid the sidekiq job id
60
+ # @option item [String] :unique_digest the unique digest (See: {LockDigest#lock_digest})
61
+ # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
62
+ #
63
+ def initialize(item, redis_pool = nil)
64
+ @item = item
65
+ @key = Key.new(item[LOCK_DIGEST] || item[UNIQUE_DIGEST]) # fallback until can be removed
66
+ @job_id = item[JID]
67
+ @config = LockConfig.new(item)
68
+ @redis_pool = redis_pool
69
+ end
70
+
71
+ #
72
+ # Deletes the lock unless it has a pttl set
73
+ #
74
+ #
75
+ def delete
76
+ return if config.pttl.positive?
77
+
78
+ delete!
79
+ end
80
+
81
+ #
82
+ # Deletes the lock regardless of if it has a pttl set
83
+ #
84
+ def delete!
85
+ call_script(:delete, key.to_a, [job_id, config.pttl, config.type, config.limit]).to_i.positive?
86
+ end
87
+
88
+ #
89
+ # Create a lock for the Sidekiq job
90
+ #
91
+ # @return [String] the Sidekiq job_id that was locked/queued
92
+ #
93
+ def lock(wait: nil)
94
+ method_name = wait ? :primed_async : :primed_sync
95
+ redis(redis_pool) do |conn|
96
+ lock!(conn, method(method_name), wait) do
97
+ return job_id
98
+ end
99
+ end
100
+ end
101
+
102
+ def execute(&block)
103
+ raise SidekiqUniqueJobs::InvalidArgument, "#execute needs a block" unless block
104
+
105
+ redis(redis_pool) do |conn|
106
+ lock!(conn, method(:primed_async), &block)
107
+ end
108
+ end
109
+
110
+ #
111
+ # Removes the lock keys from Redis if locked by the provided jid/token
112
+ #
113
+ # @return [false] unless locked?
114
+ # @return [String] Sidekiq job_id (jid) if successful
115
+ #
116
+ def unlock(conn = nil)
117
+ return false unless locked?(conn)
118
+
119
+ unlock!(conn)
120
+ end
121
+
122
+ #
123
+ # Removes the lock keys from Redis
124
+ #
125
+ # @return [false] unless locked?
126
+ # @return [String] Sidekiq job_id (jid) if successful
127
+ #
128
+ def unlock!(conn = nil)
129
+ call_script(:unlock, key.to_a, argv, conn) do |unlocked_jid|
130
+ reflect(:debug, :unlocked, item, unlocked_jid) if unlocked_jid == job_id
131
+
132
+ unlocked_jid
133
+ end
134
+ end
135
+
136
+ # Checks if this instance is considered locked
137
+ #
138
+ # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
139
+ #
140
+ # @return [true, false] true when the :LOCKED hash contains the job_id
141
+ #
142
+ def locked?(conn = nil)
143
+ return taken?(conn) if conn
144
+
145
+ redis { |rcon| taken?(rcon) }
146
+ end
147
+
148
+ #
149
+ # Nicely formatted string with information about self
150
+ #
151
+ #
152
+ # @return [String]
153
+ #
154
+ def to_s
155
+ "Locksmith##{object_id}(digest=#{key} job_id=#{job_id} locked=#{locked?})"
156
+ end
157
+
158
+ #
159
+ # @see to_s
160
+ #
161
+ def inspect
162
+ to_s
163
+ end
164
+
165
+ #
166
+ # Compare this locksmith with another
167
+ #
168
+ # @param [Locksmith] other the locksmith to compare with
169
+ #
170
+ # @return [true, false]
171
+ #
172
+ def ==(other)
173
+ key == other.key && job_id == other.job_id
174
+ end
175
+
176
+ private
177
+
178
+ attr_reader :redis_pool
179
+
180
+ #
181
+ # Used to reduce some duplication from the two methods
182
+ #
183
+ # @see lock
184
+ # @see execute
185
+ #
186
+ # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
187
+ # @param [Method] primed_method reference to the method to use for getting a primed token
188
+ # @param [nil, Integer, Float] time to wait before timeout
189
+ #
190
+ # @yieldparam [string] job_id the sidekiq JID
191
+ # @yieldreturn [void] whatever the calling block returns
192
+ def lock!(conn, primed_method, wait = nil)
193
+ return yield if locked?(conn)
194
+
195
+ enqueue(conn) do |queued_jid|
196
+ reflect(:debug, :queued, item, queued_jid)
197
+
198
+ primed_method.call(conn, wait) do |primed_jid|
199
+ reflect(:debug, :primed, item, primed_jid)
200
+ locked_jid = call_script(:lock, key.to_a, argv, conn)
201
+
202
+ if locked_jid
203
+ reflect(:debug, :locked, item, locked_jid)
204
+ return yield
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ #
211
+ # Prepares all the various lock data
212
+ #
213
+ # @param [Redis] conn a redis connection
214
+ #
215
+ # @return [nil] when redis was already prepared for this lock
216
+ # @return [yield<String>] when successfully enqueued
217
+ #
218
+ def enqueue(conn)
219
+ queued_jid, elapsed = timed do
220
+ call_script(:queue, key.to_a, argv, conn)
221
+ end
222
+
223
+ return unless queued_jid
224
+ return unless [job_id, "1"].include?(queued_jid)
225
+
226
+ validity = config.pttl - elapsed - drift(config.pttl)
227
+ return unless validity >= 0 || config.pttl.zero?
228
+
229
+ write_lock_info(conn)
230
+ yield job_id
231
+ end
232
+
233
+ #
234
+ # Pops an enqueued token
235
+ # @note Used for runtime locks to avoid problems with blocking commands
236
+ # in current thread
237
+ #
238
+ # @param [Redis] conn a redis connection
239
+ #
240
+ # @return [nil] when lock was not possible
241
+ # @return [Object] whatever the block returns when lock was acquired
242
+ #
243
+ def primed_async(conn, wait = nil, &block)
244
+ timeout = (wait || config.timeout).to_i
245
+ timeout = 1 if timeout.zero?
246
+
247
+ brpoplpush_timeout = timeout
248
+ concurrent_timeout = add_drift(timeout)
249
+
250
+ reflect(:debug, :timeouts, item,
251
+ timeouts: { brpoplpush_timeout: brpoplpush_timeout, concurrent_timeout: concurrent_timeout })
252
+
253
+ primed_jid = Concurrent::Promises
254
+ .future(conn) { |red_con| pop_queued(red_con, timeout) }
255
+ .value
256
+
257
+ handle_primed(primed_jid, &block)
258
+ end
259
+
260
+ #
261
+ # Pops an enqueued token
262
+ # @note Used for non-runtime locks
263
+ #
264
+ # @param [Redis] conn a redis connection
265
+ #
266
+ # @return [nil] when lock was not possible
267
+ # @return [Object] whatever the block returns when lock was acquired
268
+ #
269
+ def primed_sync(conn, wait = nil, &block)
270
+ primed_jid = pop_queued(conn, wait)
271
+ handle_primed(primed_jid, &block)
272
+ end
273
+
274
+ def handle_primed(primed_jid)
275
+ return yield job_id if [job_id, "1"].include?(primed_jid)
276
+
277
+ reflect(:timeout, item) unless config.wait_for_lock?
278
+ end
279
+
280
+ #
281
+ # Does the actual popping of the enqueued token
282
+ #
283
+ # @param [Redis] conn a redis connection
284
+ #
285
+ # @return [String] a previously enqueued token (now taken off the queue)
286
+ #
287
+ def pop_queued(conn, wait = 1)
288
+ wait ||= config.timeout if config.wait_for_lock?
289
+
290
+ if wait.nil?
291
+ rpoplpush(conn)
292
+ else
293
+ brpoplpush(conn, wait)
294
+ end
295
+ end
296
+
297
+ #
298
+ # @api private
299
+ #
300
+ def brpoplpush(conn, wait)
301
+ # passing timeout 0 to brpoplpush causes it to block indefinitely
302
+ raise InvalidArgument, "wait must be an integer" unless wait.is_a?(Integer)
303
+ return conn.brpoplpush(key.queued, key.primed, wait) if conn.class.to_s == "Redis::Namespace"
304
+
305
+ if VersionCheck.satisfied?(redis_version, ">= 6.2.0") && conn.respond_to?(:blmove)
306
+ conn.blmove(key.queued, key.primed, "RIGHT", "LEFT", timeout: wait)
307
+ else
308
+ conn.brpoplpush(key.queued, key.primed, timeout: wait)
309
+ end
310
+ end
311
+
312
+ #
313
+ # @api private
314
+ #
315
+ def rpoplpush(conn)
316
+ conn.rpoplpush(key.queued, key.primed)
317
+ end
318
+
319
+ #
320
+ # Writes lock information to redis.
321
+ # The lock information contains information about worker, queue, limit etc.
322
+ #
323
+ #
324
+ # @return [void]
325
+ #
326
+ def write_lock_info(conn)
327
+ return unless config.lock_info?
328
+
329
+ conn.set(key.info, lock_info)
330
+ end
331
+
332
+ #
333
+ # Used to combat redis imprecision with ttl/pttl
334
+ #
335
+ # @param [Integer] val the value to compute drift for
336
+ #
337
+ # @return [Integer] a computed drift value
338
+ #
339
+ def drift(val)
340
+ # Add 2 milliseconds to the drift to account for Redis expires
341
+ # precision, which is 1 millisecond, plus 1 millisecond min drift
342
+ # for small TTLs.
343
+ (val + 2).to_f * CLOCK_DRIFT_FACTOR
344
+ end
345
+
346
+ def add_drift(val)
347
+ val = val.to_f
348
+ val + drift(val)
349
+ end
350
+
351
+ #
352
+ # Checks if the lock has been taken
353
+ #
354
+ # @param [Redis] conn a redis connection
355
+ #
356
+ # @return [true, false]
357
+ #
358
+ def taken?(conn)
359
+ conn.hexists(key.locked, job_id)
360
+ end
361
+
362
+ def argv
363
+ [job_id, config.pttl, config.type, config.limit]
364
+ end
365
+
366
+ def lock_info
367
+ @lock_info ||= dump_json(
368
+ WORKER => item[CLASS],
369
+ QUEUE => item[QUEUE],
370
+ LIMIT => item[LOCK_LIMIT],
371
+ TIMEOUT => item[LOCK_TIMEOUT],
372
+ TTL => item[LOCK_TTL],
373
+ TYPE => config.type,
374
+ LOCK_ARGS => item[LOCK_ARGS],
375
+ TIME => now_f,
376
+ )
377
+ end
378
+
379
+ def redis_version
380
+ @redis_version ||= SidekiqUniqueJobs.config.redis_version
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Provides the sidekiq middleware that makes the gem work
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ #
9
+ module Logging
10
+ #
11
+ # Context aware logging for Sidekiq Middlewares
12
+ #
13
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
14
+ #
15
+ module Middleware
16
+ include Logging
17
+
18
+ def self.included(base)
19
+ base.class_eval do
20
+ extend Logging::Middleware
21
+ end
22
+ end
23
+
24
+ #
25
+ # Provides a logging context for Sidekiq Middlewares
26
+ #
27
+ #
28
+ # @return [Hash] when logger responds to `:with_context`
29
+ # @return [String] when logger does not responds to `:with_context`
30
+ #
31
+ def logging_context
32
+ middleware = is_a?(SidekiqUniqueJobs::Middleware::Client) ? :client : :server
33
+ digest = item[LOCK_DIGEST]
34
+ lock_type = item[LOCK]
35
+
36
+ if logger_context_hash?
37
+ { "uniquejobs" => middleware, lock_type => digest }
38
+ else
39
+ "uniquejobs-#{middleware} #{"DIG-#{digest}" if digest}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end