sidekiq-unique-jobs 6.0.7 → 7.1.33

Sign up to get free protection for your applications and to get access to all the features.
Files changed (194) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1880 -156
  3. data/README.md +818 -238
  4. data/bin/uniquejobs +2 -2
  5. data/lib/sidekiq-unique-jobs.rb +1 -1
  6. data/lib/sidekiq_unique_jobs/batch_delete.rb +124 -0
  7. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  8. data/lib/sidekiq_unique_jobs/cli.rb +68 -25
  9. data/lib/sidekiq_unique_jobs/config.rb +319 -0
  10. data/lib/sidekiq_unique_jobs/connection.rb +6 -5
  11. data/lib/sidekiq_unique_jobs/constants.rb +50 -20
  12. data/lib/sidekiq_unique_jobs/core_ext.rb +80 -0
  13. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  14. data/lib/sidekiq_unique_jobs/digests.rb +70 -87
  15. data/lib/sidekiq_unique_jobs/exceptions.rb +88 -12
  16. data/lib/sidekiq_unique_jobs/expiring_digests.rb +14 -0
  17. data/lib/sidekiq_unique_jobs/job.rb +47 -13
  18. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  19. data/lib/sidekiq_unique_jobs/key.rb +98 -0
  20. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +111 -82
  21. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  22. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  23. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +45 -5
  24. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +30 -4
  25. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +26 -2
  26. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +27 -15
  27. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  28. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +26 -7
  29. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +3 -11
  30. data/lib/sidekiq_unique_jobs/lock.rb +342 -0
  31. data/lib/sidekiq_unique_jobs/lock_args.rb +127 -0
  32. data/lib/sidekiq_unique_jobs/lock_config.rb +126 -0
  33. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  34. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  35. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  36. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  37. data/lib/sidekiq_unique_jobs/lock_type.rb +37 -0
  38. data/lib/sidekiq_unique_jobs/locksmith.rb +324 -95
  39. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  40. data/lib/sidekiq_unique_jobs/logging.rb +208 -30
  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 +33 -30
  65. data/lib/sidekiq_unique_jobs/normalizer.rb +4 -4
  66. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +9 -5
  67. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +1 -1
  68. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +2 -2
  69. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +63 -17
  70. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +54 -14
  71. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +16 -5
  72. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +26 -7
  73. data/lib/sidekiq_unique_jobs/on_conflict.rb +34 -16
  74. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +39 -36
  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 +115 -66
  97. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +304 -0
  98. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +51 -29
  99. data/lib/sidekiq_unique_jobs/testing.rb +104 -31
  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 +20 -4
  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 +3 -1
  106. data/lib/sidekiq_unique_jobs/version_check.rb +114 -0
  107. data/lib/sidekiq_unique_jobs/web/helpers.rb +140 -15
  108. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +4 -4
  109. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  110. data/lib/sidekiq_unique_jobs/web/views/lock.erb +110 -0
  111. data/lib/sidekiq_unique_jobs/web/views/locks.erb +54 -0
  112. data/lib/sidekiq_unique_jobs/web.rb +86 -29
  113. data/lib/sidekiq_unique_jobs.rb +78 -105
  114. data/lib/tasks/changelog.rake +23 -0
  115. metadata +154 -218
  116. data/.codeclimate.yml +0 -35
  117. data/.csslintrc +0 -2
  118. data/.dockerignore +0 -4
  119. data/.editorconfig +0 -14
  120. data/.eslintignore +0 -1
  121. data/.eslintrc +0 -213
  122. data/.fasterer.yml +0 -23
  123. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
  124. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  125. data/.gitignore +0 -28
  126. data/.reek.yml +0 -87
  127. data/.rspec +0 -2
  128. data/.rubocop.yml +0 -127
  129. data/.simplecov +0 -20
  130. data/.travis.yml +0 -47
  131. data/.yardopts +0 -7
  132. data/Appraisals +0 -29
  133. data/CODE_OF_CONDUCT.md +0 -74
  134. data/Gemfile +0 -24
  135. data/Guardfile +0 -55
  136. data/Rakefile +0 -12
  137. data/_config.yml +0 -1
  138. data/assets/unique_digests_1.png +0 -0
  139. data/assets/unique_digests_2.png +0 -0
  140. data/bin/bench +0 -20
  141. data/examples/another_unique_job.rb +0 -15
  142. data/examples/custom_queue_job.rb +0 -12
  143. data/examples/custom_queue_job_with_filter_method.rb +0 -13
  144. data/examples/custom_queue_job_with_filter_proc.rb +0 -16
  145. data/examples/expiring_job.rb +0 -12
  146. data/examples/inline_worker.rb +0 -12
  147. data/examples/just_a_worker.rb +0 -13
  148. data/examples/long_running_job.rb +0 -14
  149. data/examples/main_job.rb +0 -14
  150. data/examples/my_job.rb +0 -12
  151. data/examples/my_unique_job.rb +0 -15
  152. data/examples/my_unique_job_with_filter_method.rb +0 -21
  153. data/examples/my_unique_job_with_filter_proc.rb +0 -19
  154. data/examples/notify_worker.rb +0 -14
  155. data/examples/plain_class.rb +0 -13
  156. data/examples/simple_worker.rb +0 -15
  157. data/examples/spawn_simple_worker.rb +0 -12
  158. data/examples/test_class.rb +0 -9
  159. data/examples/unique_across_workers_job.rb +0 -20
  160. data/examples/unique_job_on_conflict_raise.rb +0 -14
  161. data/examples/unique_job_on_conflict_reject.rb +0 -14
  162. data/examples/unique_job_on_conflict_reschedule.rb +0 -14
  163. data/examples/unique_job_with_conditional_parameter.rb +0 -18
  164. data/examples/unique_job_with_filter_method.rb +0 -21
  165. data/examples/unique_job_with_nil_unique_args.rb +0 -20
  166. data/examples/unique_job_with_no_unique_args_method.rb +0 -16
  167. data/examples/unique_job_withthout_unique_args_parameter.rb +0 -18
  168. data/examples/unique_on_all_queues_job.rb +0 -16
  169. data/examples/until_and_while_executing_job.rb +0 -17
  170. data/examples/until_executed_2_job.rb +0 -24
  171. data/examples/until_executed_job.rb +0 -25
  172. data/examples/until_executing_job.rb +0 -11
  173. data/examples/until_expired_job.rb +0 -12
  174. data/examples/until_global_expired_job.rb +0 -12
  175. data/examples/while_executing_job.rb +0 -15
  176. data/examples/while_executing_reject_job.rb +0 -14
  177. data/examples/without_argument_job.rb +0 -13
  178. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  179. data/lib/sidekiq_unique_jobs/scripts.rb +0 -89
  180. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -40
  181. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  182. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  183. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -149
  184. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  185. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  186. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -42
  187. data/redis/acquire_lock.lua +0 -21
  188. data/redis/delete.lua +0 -14
  189. data/redis/delete_by_digest.lua +0 -24
  190. data/redis/delete_job_by_digest.lua +0 -60
  191. data/redis/lock.lua +0 -58
  192. data/redis/release_stale_locks.lua +0 -90
  193. data/redis/unlock.lua +0 -32
  194. data/sidekiq-unique-jobs.gemspec +0 -42
@@ -3,162 +3,391 @@
3
3
  module SidekiqUniqueJobs
4
4
  # Lock manager class that handles all the various locks
5
5
  #
6
- # @author Mikael Henriksson <mikael@zoolutions.se>
7
- class Locksmith
6
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
7
+ class Locksmith # rubocop:disable Metrics/ClassLength
8
+ # includes "SidekiqUniqueJobs::Connection"
9
+ # @!parse include SidekiqUniqueJobs::Connection
8
10
  include SidekiqUniqueJobs::Connection
9
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
+ #
10
57
  # @param [Hash] item a Sidekiq job hash
11
- # @option item [Integer] :lock_expiration the configured expiration
58
+ # @option item [Integer] :lock_ttl the configured expiration
12
59
  # @option item [String] :jid the sidekiq job id
13
- # @option item [String] :unique_digest the unique digest (See: {UniqueArgs#unique_digest})
60
+ # @option item [String] :unique_digest the unique digest (See: {LockDigest#lock_digest})
14
61
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
62
+ #
15
63
  def initialize(item, redis_pool = nil)
16
- @concurrency = 1 # removed in a0cff5bc42edbe7190d6ede7e7f845074d2d7af6
17
- @expiration = item[LOCK_EXPIRATION_KEY]
18
- @jid = item[JID_KEY]
19
- @unique_digest = item[UNIQUE_DIGEST_KEY]
20
- @redis_pool = redis_pool
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
21
69
  end
22
70
 
23
- # Checks if the exists key is created in redis
24
- # @return [true, false]
25
- def exists?
26
- redis(redis_pool) { |conn| conn.exists(exists_key) }
27
- end
28
-
29
- # The number of available resourced for this lock
30
- # @return [Integer] the number of available resources
31
- def available_count
32
- return concurrency unless exists?
33
-
34
- redis(redis_pool) { |conn| conn.llen(available_key) }
35
- end
36
-
37
- # Deletes the lock unless it has an expiration set
71
+ #
72
+ # Deletes the lock unless it has a pttl set
73
+ #
74
+ #
38
75
  def delete
39
- return if expiration
76
+ return if config.pttl.positive?
40
77
 
41
78
  delete!
42
79
  end
43
80
 
44
- # Deletes the lock regardless of if it has an expiration set
81
+ #
82
+ # Deletes the lock regardless of if it has a pttl set
83
+ #
45
84
  def delete!
46
- Scripts.call(
47
- :delete,
48
- redis_pool,
49
- keys: [exists_key, grabbed_key, available_key, version_key, UNIQUE_SET, unique_digest],
50
- )
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
51
100
  end
52
101
 
53
- # Create a lock for the item
54
- # @param [Integer] timeout the number of seconds to wait for a lock.
55
- # nil means wait indefinitely
56
- # @yield the block to execute if a lock is successful
57
- # @return the Sidekiq job_id (jid)
58
- def lock(timeout = nil, &block)
59
- Scripts.call(:lock, redis_pool,
60
- keys: [exists_key, grabbed_key, available_key, UNIQUE_SET, unique_digest],
61
- argv: [jid, expiration])
102
+ def execute(&block)
103
+ raise SidekiqUniqueJobs::InvalidArgument, "#execute needs a block" unless block
62
104
 
63
- grab_token(timeout) do |token|
64
- touch_grabbed_token(token)
65
- return_token_or_block_value(token, &block)
105
+ redis(redis_pool) do |conn|
106
+ lock!(conn, method(:primed_async), &block)
66
107
  end
67
108
  end
68
- alias wait lock
69
109
 
110
+ #
70
111
  # Removes the lock keys from Redis if locked by the provided jid/token
112
+ #
71
113
  # @return [false] unless locked?
72
114
  # @return [String] Sidekiq job_id (jid) if successful
73
- def unlock(token = nil)
74
- token ||= jid
75
- return false unless locked?(token)
115
+ #
116
+ def unlock(conn = nil)
117
+ return false unless locked?(conn)
76
118
 
77
- unlock!(token)
119
+ unlock!(conn)
78
120
  end
79
121
 
122
+ #
80
123
  # Removes the lock keys from Redis
124
+ #
81
125
  # @return [false] unless locked?
82
126
  # @return [String] Sidekiq job_id (jid) if successful
83
- def unlock!(token = nil)
84
- token ||= jid
85
-
86
- Scripts.call(
87
- :unlock,
88
- redis_pool,
89
- keys: [exists_key, grabbed_key, available_key, version_key, UNIQUE_SET, unique_digest],
90
- argv: [token, expiration],
91
- )
127
+ #
128
+ def unlock!(conn = nil)
129
+ call_script(:unlock, key.to_a, argv, conn) do |unlocked_jid|
130
+ if unlocked_jid == job_id
131
+ reflect(:debug, :unlocked, item, unlocked_jid)
132
+ reflect(:unlocked, item)
133
+ end
134
+
135
+ unlocked_jid
136
+ end
92
137
  end
93
138
 
94
139
  # Checks if this instance is considered locked
95
- # @param [String] token the unique token to check for a lock.
96
- # nil will default to the jid provided in the initializer
140
+ #
141
+ # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
142
+ #
143
+ # @return [true, false] true when the :LOCKED hash contains the job_id
144
+ #
145
+ def locked?(conn = nil)
146
+ return taken?(conn) if conn
147
+
148
+ redis { |rcon| taken?(rcon) }
149
+ end
150
+
151
+ #
152
+ # Nicely formatted string with information about self
153
+ #
154
+ #
155
+ # @return [String]
156
+ #
157
+ def to_s
158
+ "Locksmith##{object_id}(digest=#{key} job_id=#{job_id} locked=#{locked?})"
159
+ end
160
+
161
+ #
162
+ # @see to_s
163
+ #
164
+ def inspect
165
+ to_s
166
+ end
167
+
168
+ #
169
+ # Compare this locksmith with another
170
+ #
171
+ # @param [Locksmith] other the locksmith to compare with
172
+ #
97
173
  # @return [true, false]
98
- def locked?(token = nil)
99
- token ||= jid
100
- redis(redis_pool) { |conn| conn.hexists(grabbed_key, token) }
174
+ #
175
+ def ==(other)
176
+ key == other.key && job_id == other.job_id
101
177
  end
102
178
 
103
179
  private
104
180
 
105
- attr_reader :concurrency, :unique_digest, :expiration, :jid, :redis_pool
181
+ attr_reader :redis_pool
106
182
 
107
- def grab_token(timeout = nil)
108
- redis(redis_pool) do |conn|
109
- if timeout.nil? || timeout.positive?
110
- # passing timeout 0 to blpop causes it to block
111
- _key, token = conn.blpop(available_key, timeout || 0)
112
- else
113
- token = conn.lpop(available_key)
183
+ #
184
+ # Used to reduce some duplication from the two methods
185
+ #
186
+ # @see lock
187
+ # @see execute
188
+ #
189
+ # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
190
+ # @param [Method] primed_method reference to the method to use for getting a primed token
191
+ # @param [nil, Integer, Float] time to wait before timeout
192
+ #
193
+ # @yieldparam [string] job_id the sidekiq JID
194
+ # @yieldreturn [void] whatever the calling block returns
195
+ def lock!(conn, primed_method, wait = nil)
196
+ return yield if locked?(conn)
197
+
198
+ enqueue(conn) do |queued_jid|
199
+ reflect(:debug, :queued, item, queued_jid)
200
+
201
+ primed_method.call(conn, wait) do |primed_jid|
202
+ reflect(:debug, :primed, item, primed_jid)
203
+ locked_jid = call_script(:lock, key.to_a, argv, conn)
204
+
205
+ if locked_jid
206
+ reflect(:debug, :locked, item, locked_jid)
207
+ return yield
208
+ end
114
209
  end
210
+ end
211
+ end
115
212
 
116
- return yield jid if token
213
+ #
214
+ # Prepares all the various lock data
215
+ #
216
+ # @param [Redis] conn a redis connection
217
+ #
218
+ # @return [nil] when redis was already prepared for this lock
219
+ # @return [yield<String>] when successfully enqueued
220
+ #
221
+ def enqueue(conn)
222
+ queued_jid, elapsed = timed do
223
+ call_script(:queue, key.to_a, argv, conn)
117
224
  end
225
+
226
+ return unless queued_jid
227
+ return unless [job_id, "1"].include?(queued_jid)
228
+
229
+ validity = config.pttl - elapsed - drift(config.pttl)
230
+ return unless validity >= 0 || config.pttl.zero?
231
+
232
+ write_lock_info(conn)
233
+ yield job_id
234
+ end
235
+
236
+ #
237
+ # Pops an enqueued token
238
+ # @note Used for runtime locks to avoid problems with blocking commands
239
+ # in current thread
240
+ #
241
+ # @param [Redis] conn a redis connection
242
+ #
243
+ # @return [nil] when lock was not possible
244
+ # @return [Object] whatever the block returns when lock was acquired
245
+ #
246
+ def primed_async(conn, wait = nil, &block)
247
+ timeout = (wait || config.timeout).to_i
248
+ timeout = 1 if timeout.zero?
249
+
250
+ brpoplpush_timeout = timeout
251
+ concurrent_timeout = add_drift(timeout)
252
+
253
+ reflect(:debug, :timeouts, item,
254
+ timeouts: {
255
+ brpoplpush_timeout: brpoplpush_timeout,
256
+ concurrent_timeout: concurrent_timeout,
257
+ })
258
+
259
+ # NOTE: When debugging, change .value to .value!
260
+ primed_jid = Concurrent::Promises
261
+ .future(conn) { |red_con| pop_queued(red_con, timeout) }
262
+ .value
263
+
264
+ handle_primed(primed_jid, &block)
265
+ end
266
+
267
+ #
268
+ # Pops an enqueued token
269
+ # @note Used for non-runtime locks
270
+ #
271
+ # @param [Redis] conn a redis connection
272
+ #
273
+ # @return [nil] when lock was not possible
274
+ # @return [Object] whatever the block returns when lock was acquired
275
+ #
276
+ def primed_sync(conn, wait = nil, &block)
277
+ primed_jid = pop_queued(conn, wait)
278
+ handle_primed(primed_jid, &block)
118
279
  end
119
280
 
120
- def touch_grabbed_token(token)
121
- redis(redis_pool) { |conn| conn.hset(grabbed_key, token, current_time.to_f) }
281
+ def handle_primed(primed_jid)
282
+ return yield job_id if [job_id, "1"].include?(primed_jid)
283
+
284
+ reflect(:timeout, item) unless config.wait_for_lock?
122
285
  end
123
286
 
124
- def return_token_or_block_value(token)
125
- return token unless block_given?
287
+ #
288
+ # Does the actual popping of the enqueued token
289
+ #
290
+ # @param [Redis] conn a redis connection
291
+ #
292
+ # @return [String] a previously enqueued token (now taken off the queue)
293
+ #
294
+ def pop_queued(conn, wait = 1)
295
+ wait ||= config.timeout if config.wait_for_lock?
126
296
 
127
- # The reason for begin is to only signal when we have a block
128
- begin
129
- yield token
130
- ensure
131
- unlock(token)
297
+ if wait.nil?
298
+ rpoplpush(conn)
299
+ else
300
+ brpoplpush(conn, wait)
132
301
  end
133
302
  end
134
303
 
135
- def available_key
136
- @available_key ||= namespaced_key('AVAILABLE')
304
+ #
305
+ # @api private
306
+ #
307
+ def brpoplpush(conn, wait)
308
+ # passing timeout 0 to brpoplpush causes it to block indefinitely
309
+ raise InvalidArgument, "wait must be an integer" unless wait.is_a?(Integer)
310
+
311
+ if defined?(::Redis::Namespace) && conn.instance_of?(::Redis::Namespace)
312
+ return conn.brpoplpush(key.queued, key.primed, wait)
313
+ end
314
+
315
+ if VersionCheck.satisfied?(redis_version, ">= 6.2.0") && conn.respond_to?(:blmove)
316
+ conn.blmove(key.queued, key.primed, "RIGHT", "LEFT", timeout: wait)
317
+ else
318
+ conn.brpoplpush(key.queued, key.primed, timeout: wait)
319
+ end
137
320
  end
138
321
 
139
- def exists_key
140
- @exists_key ||= namespaced_key('EXISTS')
322
+ #
323
+ # @api private
324
+ #
325
+ def rpoplpush(conn)
326
+ conn.rpoplpush(key.queued, key.primed)
141
327
  end
142
328
 
143
- def grabbed_key
144
- @grabbed_key ||= namespaced_key('GRABBED')
329
+ #
330
+ # Writes lock information to redis.
331
+ # The lock information contains information about worker, queue, limit etc.
332
+ #
333
+ #
334
+ # @return [void]
335
+ #
336
+ def write_lock_info(conn)
337
+ return unless config.lock_info?
338
+
339
+ conn.set(key.info, lock_info)
340
+ end
341
+
342
+ #
343
+ # Used to combat redis imprecision with ttl/pttl
344
+ #
345
+ # @param [Integer] val the value to compute drift for
346
+ #
347
+ # @return [Integer] a computed drift value
348
+ #
349
+ def drift(val)
350
+ # Add 2 milliseconds to the drift to account for Redis expires
351
+ # precision, which is 1 millisecond, plus 1 millisecond min drift
352
+ # for small TTLs.
353
+ (val + 2).to_f * CLOCK_DRIFT_FACTOR
145
354
  end
146
355
 
147
- def version_key
148
- @version_key ||= namespaced_key('VERSION')
356
+ def add_drift(val)
357
+ val = val.to_f
358
+ val + drift(val)
149
359
  end
150
360
 
151
- def namespaced_key(variable)
152
- "#{unique_digest}:#{variable}"
361
+ #
362
+ # Checks if the lock has been taken
363
+ #
364
+ # @param [Redis] conn a redis connection
365
+ #
366
+ # @return [true, false]
367
+ #
368
+ def taken?(conn)
369
+ conn.hexists(key.locked, job_id)
153
370
  end
154
371
 
155
- def current_time
156
- seconds, microseconds_with_frac = redis_time
157
- Time.at(seconds, microseconds_with_frac)
372
+ def argv
373
+ [job_id, config.pttl, config.type, config.limit]
374
+ end
375
+
376
+ def lock_info
377
+ @lock_info ||= dump_json(
378
+ WORKER => item[CLASS],
379
+ QUEUE => item[QUEUE],
380
+ LIMIT => item[LOCK_LIMIT],
381
+ TIMEOUT => item[LOCK_TIMEOUT],
382
+ TTL => item[LOCK_TTL],
383
+ TYPE => config.type,
384
+ LOCK_ARGS => item[LOCK_ARGS],
385
+ TIME => now_f,
386
+ )
158
387
  end
159
388
 
160
- def redis_time
161
- redis(&:time)
389
+ def redis_version
390
+ @redis_version ||= SidekiqUniqueJobs.config.redis_version
162
391
  end
163
392
  end
164
393
  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