sidekiq-unique-jobs 6.0.23 → 7.1.12

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +922 -41
  3. data/README.md +821 -284
  4. data/lib/sidekiq_unique_jobs/batch_delete.rb +123 -0
  5. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  6. data/lib/sidekiq_unique_jobs/cli.rb +34 -31
  7. data/lib/sidekiq_unique_jobs/config.rb +314 -0
  8. data/lib/sidekiq_unique_jobs/connection.rb +6 -5
  9. data/lib/sidekiq_unique_jobs/constants.rb +45 -24
  10. data/lib/sidekiq_unique_jobs/core_ext.rb +80 -0
  11. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  12. data/lib/sidekiq_unique_jobs/digests.rb +70 -102
  13. data/lib/sidekiq_unique_jobs/exceptions.rb +88 -12
  14. data/lib/sidekiq_unique_jobs/job.rb +41 -12
  15. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  16. data/lib/sidekiq_unique_jobs/key.rb +93 -0
  17. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +111 -82
  18. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  19. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  20. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +40 -15
  21. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +25 -7
  22. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +22 -2
  23. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +26 -16
  24. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  25. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +23 -12
  26. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +3 -3
  27. data/lib/sidekiq_unique_jobs/lock.rb +325 -0
  28. data/lib/sidekiq_unique_jobs/lock_args.rb +123 -0
  29. data/lib/sidekiq_unique_jobs/lock_config.rb +126 -0
  30. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  31. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  32. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  33. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  34. data/lib/sidekiq_unique_jobs/locksmith.rb +275 -102
  35. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  36. data/lib/sidekiq_unique_jobs/logging.rb +188 -33
  37. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  38. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +42 -0
  39. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  40. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  41. data/lib/sidekiq_unique_jobs/lua/lock.lua +93 -0
  42. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  43. data/lib/sidekiq_unique_jobs/lua/queue.lua +87 -0
  44. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +94 -0
  45. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  46. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  47. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  54. data/lib/sidekiq_unique_jobs/lua/unlock.lua +102 -0
  55. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  56. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  57. data/lib/sidekiq_unique_jobs/middleware/client.rb +40 -0
  58. data/lib/sidekiq_unique_jobs/middleware/server.rb +29 -0
  59. data/lib/sidekiq_unique_jobs/middleware.rb +29 -31
  60. data/lib/sidekiq_unique_jobs/normalizer.rb +4 -4
  61. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +9 -5
  62. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +1 -1
  63. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  64. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +61 -15
  65. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +54 -14
  66. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +16 -5
  67. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +25 -6
  68. data/lib/sidekiq_unique_jobs/on_conflict.rb +23 -10
  69. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +35 -32
  70. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  71. data/lib/sidekiq_unique_jobs/orphans/manager.rb +248 -0
  72. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  73. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  74. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +114 -0
  75. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  76. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +231 -0
  77. data/lib/sidekiq_unique_jobs/redis/entity.rb +112 -0
  78. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  79. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  80. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  81. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +86 -0
  82. data/lib/sidekiq_unique_jobs/redis/string.rb +49 -0
  83. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  84. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  85. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  86. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  87. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  88. data/lib/sidekiq_unique_jobs/script/caller.rb +127 -0
  89. data/lib/sidekiq_unique_jobs/script.rb +15 -0
  90. data/lib/sidekiq_unique_jobs/server.rb +61 -0
  91. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +114 -65
  92. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +241 -35
  93. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +18 -16
  94. data/lib/sidekiq_unique_jobs/testing.rb +62 -21
  95. data/lib/sidekiq_unique_jobs/timer_task.rb +78 -0
  96. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  97. data/lib/sidekiq_unique_jobs/unlockable.rb +20 -4
  98. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  99. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +155 -0
  100. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  101. data/lib/sidekiq_unique_jobs/version_check.rb +23 -4
  102. data/lib/sidekiq_unique_jobs/web/helpers.rb +128 -13
  103. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +4 -4
  104. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  105. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  106. data/lib/sidekiq_unique_jobs/web/views/locks.erb +54 -0
  107. data/lib/sidekiq_unique_jobs/web.rb +57 -27
  108. data/lib/sidekiq_unique_jobs.rb +52 -7
  109. data/lib/tasks/changelog.rake +15 -15
  110. metadata +124 -184
  111. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  112. data/lib/sidekiq_unique_jobs/scripts.rb +0 -118
  113. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  114. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  115. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  116. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -150
  117. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  118. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  119. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  120. data/redis/acquire_lock.lua +0 -21
  121. data/redis/convert_legacy_lock.lua +0 -13
  122. data/redis/delete.lua +0 -14
  123. data/redis/delete_by_digest.lua +0 -23
  124. data/redis/delete_job_by_digest.lua +0 -60
  125. data/redis/lock.lua +0 -62
  126. data/redis/release_stale_locks.lua +0 -90
  127. data/redis/unlock.lua +0 -35
@@ -3,64 +3,108 @@
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
- # rubocop:disable Metrics/ClassLength
8
- 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
9
10
  include SidekiqUniqueJobs::Connection
10
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
+
36
+ #
37
+ # @!attribute [r] key
38
+ # @return [Key] the key used for locking
39
+ attr_reader :key
40
+ #
41
+ # @!attribute [r] job_id
42
+ # @return [String] a sidekiq JID
43
+ attr_reader :job_id
44
+ #
45
+ # @!attribute [r] config
46
+ # @return [LockConfig] the configuration for this lock
47
+ attr_reader :config
48
+ #
49
+ # @!attribute [r] item
50
+ # @return [Hash] a sidekiq job hash
51
+ attr_reader :item
52
+
53
+ #
54
+ # Initialize a new Locksmith instance
55
+ #
11
56
  # @param [Hash] item a Sidekiq job hash
12
- # @option item [Integer] :lock_expiration the configured expiration
57
+ # @option item [Integer] :lock_ttl the configured expiration
13
58
  # @option item [String] :jid the sidekiq job id
14
- # @option item [String] :unique_digest the unique digest (See: {UniqueArgs#unique_digest})
59
+ # @option item [String] :unique_digest the unique digest (See: {LockDigest#lock_digest})
15
60
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
61
+ #
16
62
  def initialize(item, redis_pool = nil)
17
- # @concurrency = 1 # removed in a0cff5bc42edbe7190d6ede7e7f845074d2d7af6
18
- @ttl = item[LOCK_EXPIRATION_KEY] || item[LOCK_TTL_KEY]
19
- @jid = item[JID_KEY]
20
- @unique_digest = item[UNIQUE_DIGEST_KEY] || item[LOCK_DIGEST_KEY]
21
- @lock_type = item[LOCK_KEY] || item[UNIQUE_KEY]
22
- @lock_type &&= @lock_type.to_sym
23
- @redis_pool = redis_pool
63
+ @item = item
64
+ @key = Key.new(item[LOCK_DIGEST] || item[UNIQUE_DIGEST]) # fallback until can be removed
65
+ @job_id = item[JID]
66
+ @config = LockConfig.new(item)
67
+ @redis_pool = redis_pool
24
68
  end
25
69
 
26
70
  #
27
- # Deletes the lock unless it has a ttl set
71
+ # Deletes the lock unless it has a pttl set
28
72
  #
29
73
  #
30
74
  def delete
31
- return if ttl
75
+ return if config.pttl.positive?
32
76
 
33
77
  delete!
34
78
  end
35
79
 
36
- # Deletes the lock regardless of if it has a ttl set
80
+ #
81
+ # Deletes the lock regardless of if it has a pttl set
82
+ #
37
83
  def delete!
38
- Scripts.call(
39
- :delete,
40
- redis_pool,
41
- keys: [exists_key, grabbed_key, available_key, version_key, UNIQUE_SET, unique_digest],
42
- )
84
+ call_script(:delete, key.to_a, [job_id, config.pttl, config.type, config.limit]).to_i.positive?
43
85
  end
44
86
 
45
87
  #
46
- # Create a lock for the item
88
+ # Create a lock for the Sidekiq job
47
89
  #
48
- # @param [Integer] timeout the number of seconds to wait for a lock.
90
+ # @return [String] the Sidekiq job_id that was locked/queued
49
91
  #
50
- # @return [String] the Sidekiq job_id (jid)
51
- #
52
- #
53
- def lock(timeout = nil, &block)
54
- Scripts.call(:lock, redis_pool,
55
- keys: [exists_key, grabbed_key, available_key, UNIQUE_SET, unique_digest],
56
- argv: [jid, ttl, lock_type])
92
+ def lock(wait: nil)
93
+ method_name = wait ? :primed_async : :primed_sync
94
+ redis(redis_pool) do |conn|
95
+ lock!(conn, method(method_name), wait) do
96
+ return job_id
97
+ end
98
+ end
99
+ end
57
100
 
58
- grab_token(timeout) do |token|
59
- touch_grabbed_token(token)
60
- return_token_or_block_value(token, &block)
101
+ def execute(&block)
102
+ raise SidekiqUniqueJobs::InvalidArgument, "#execute needs a block" unless block
103
+
104
+ redis(redis_pool) do |conn|
105
+ lock!(conn, method(:primed_async), &block)
61
106
  end
62
107
  end
63
- alias wait lock
64
108
 
65
109
  #
66
110
  # Removes the lock keys from Redis if locked by the provided jid/token
@@ -68,122 +112,251 @@ module SidekiqUniqueJobs
68
112
  # @return [false] unless locked?
69
113
  # @return [String] Sidekiq job_id (jid) if successful
70
114
  #
71
- def unlock(token = nil)
72
- token ||= jid
73
- return false unless locked?(token)
115
+ def unlock(conn = nil)
116
+ return false unless locked?(conn)
74
117
 
75
- unlock!(token)
118
+ unlock!(conn)
76
119
  end
77
120
 
78
121
  #
79
122
  # Removes the lock keys from Redis
80
123
  #
81
- # @param [String] token the token to unlock (defaults to jid)
82
- #
83
124
  # @return [false] unless locked?
84
125
  # @return [String] Sidekiq job_id (jid) if successful
85
126
  #
86
- def unlock!(token = nil)
87
- token ||= jid
127
+ def unlock!(conn = nil)
128
+ call_script(:unlock, key.to_a, argv, conn) do |unlocked_jid|
129
+ reflect(:debug, :unlocked, item, unlocked_jid) if unlocked_jid == job_id
88
130
 
89
- Scripts.call(
90
- :unlock,
91
- redis_pool,
92
- keys: [exists_key, grabbed_key, available_key, version_key, UNIQUE_SET, unique_digest],
93
- argv: [token, ttl, lock_type],
94
- )
131
+ unlocked_jid
132
+ end
95
133
  end
96
134
 
135
+ # Checks if this instance is considered locked
97
136
  #
98
- # @param [String] token the unique token to check for a lock.
99
- # nil will default to the jid provided in the initializer
100
- # @return [true, false]
137
+ # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
101
138
  #
102
- # Checks if this instance is considered locked
139
+ # @return [true, false] true when the :LOCKED hash contains the job_id
140
+ #
141
+ def locked?(conn = nil)
142
+ return taken?(conn) if conn
143
+
144
+ redis { |rcon| taken?(rcon) }
145
+ end
146
+
103
147
  #
104
- # @param [<type>] token <description>
148
+ # Nicely formatted string with information about self
105
149
  #
106
- # @return [<type>] <description>
107
150
  #
108
- def locked?(token = nil)
109
- token ||= jid
151
+ # @return [String]
152
+ #
153
+ def to_s
154
+ "Locksmith##{object_id}(digest=#{key} job_id=#{job_id} locked=#{locked?})"
155
+ end
110
156
 
111
- convert_legacy_lock(token)
112
- redis(redis_pool) { |conn| conn.hexists(grabbed_key, token) }
157
+ #
158
+ # @see to_s
159
+ #
160
+ def inspect
161
+ to_s
162
+ end
163
+
164
+ #
165
+ # Compare this locksmith with another
166
+ #
167
+ # @param [Locksmith] other the locksmith to compare with
168
+ #
169
+ # @return [true, false]
170
+ #
171
+ def ==(other)
172
+ key == other.key && job_id == other.job_id
113
173
  end
114
174
 
115
175
  private
116
176
 
117
- attr_reader :unique_digest, :ttl, :jid, :redis_pool, :lock_type
177
+ attr_reader :redis_pool
118
178
 
119
- def convert_legacy_lock(token)
120
- Scripts.call(
121
- :convert_legacy_lock,
122
- redis_pool,
123
- keys: [grabbed_key, unique_digest],
124
- argv: [token, current_time.to_f],
125
- )
126
- end
179
+ #
180
+ # Used to reduce some duplication from the two methods
181
+ #
182
+ # @see lock
183
+ # @see execute
184
+ #
185
+ # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
186
+ # @param [Method] primed_method reference to the method to use for getting a primed token
187
+ #
188
+ # @yieldparam [string] job_id the sidekiq JID
189
+ # @yieldreturn [void] whatever the calling block returns
190
+ def lock!(conn, primed_method, wait = nil)
191
+ return yield job_id if locked?(conn)
127
192
 
128
- def grab_token(timeout = nil)
129
- redis(redis_pool) do |conn|
130
- if timeout.nil? || timeout.positive?
131
- # passing timeout 0 to blpop causes it to block
132
- _key, token = conn.blpop(available_key, timeout || 0)
133
- else
134
- token = conn.lpop(available_key)
135
- end
193
+ enqueue(conn) do |queued_jid|
194
+ reflect(:debug, item, queued_jid)
195
+
196
+ primed_method.call(conn, wait) do |primed_jid|
197
+ reflect(:debug, :primed, item, primed_jid)
136
198
 
137
- return yield jid if token
199
+ locked_jid = call_script(:lock, key.to_a, argv, conn)
200
+ if locked_jid
201
+ reflect(:debug, :locked, item, locked_jid)
202
+ return yield job_id
203
+ end
204
+ end
138
205
  end
139
206
  end
140
207
 
141
- def touch_grabbed_token(token)
142
- redis(redis_pool) do |conn|
143
- conn.hset(grabbed_key, token, current_time.to_f)
144
- conn.expire(grabbed_key, ttl) if ttl && lock_type == :until_expired
208
+ #
209
+ # Prepares all the various lock data
210
+ #
211
+ # @param [Redis] conn a redis connection
212
+ #
213
+ # @return [nil] when redis was already prepared for this lock
214
+ # @return [yield<String>] when successfully enqueued
215
+ #
216
+ def enqueue(conn)
217
+ queued_jid, elapsed = timed do
218
+ call_script(:queue, key.to_a, argv, conn)
145
219
  end
220
+
221
+ return unless queued_jid
222
+ return unless [job_id, "1"].include?(queued_jid)
223
+
224
+ validity = config.pttl - elapsed - drift(config.pttl)
225
+ return unless validity >= 0 || config.pttl.zero?
226
+
227
+ write_lock_info(conn)
228
+ yield job_id
146
229
  end
147
230
 
148
- def return_token_or_block_value(token)
149
- return token unless block_given?
231
+ #
232
+ # Pops an enqueued token
233
+ # @note Used for runtime locks to avoid problems with blocking commands
234
+ # in current thread
235
+ #
236
+ # @param [Redis] conn a redis connection
237
+ #
238
+ # @return [nil] when lock was not possible
239
+ # @return [Object] whatever the block returns when lock was acquired
240
+ #
241
+ def primed_async(conn, wait = nil, &block)
242
+ primed_jid = Concurrent::Promises
243
+ .future(conn) { |red_con| pop_queued(red_con, wait) }
244
+ .value(add_drift(wait || config.timeout))
245
+
246
+ handle_primed(primed_jid, &block)
247
+ end
150
248
 
151
- # The reason for begin is to only signal when we have a block
152
- begin
153
- yield token
154
- ensure
155
- unlock(token)
249
+ #
250
+ # Pops an enqueued token
251
+ # @note Used for non-runtime locks
252
+ #
253
+ # @param [Redis] conn a redis connection
254
+ #
255
+ # @return [nil] when lock was not possible
256
+ # @return [Object] whatever the block returns when lock was acquired
257
+ #
258
+ def primed_sync(conn, wait = nil, &block)
259
+ primed_jid = pop_queued(conn, wait)
260
+ handle_primed(primed_jid, &block)
261
+ end
262
+
263
+ def handle_primed(primed_jid)
264
+ return yield job_id if [job_id, "1"].include?(primed_jid)
265
+
266
+ reflect(:timeout, item) unless config.wait_for_lock?
267
+ end
268
+
269
+ #
270
+ # Does the actual popping of the enqueued token
271
+ #
272
+ # @param [Redis] conn a redis connection
273
+ #
274
+ # @return [String] a previously enqueued token (now taken off the queue)
275
+ #
276
+ def pop_queued(conn, wait = nil)
277
+ wait ||= config.timeout if config.wait_for_lock?
278
+
279
+ if wait.nil?
280
+ rpoplpush(conn)
281
+ else
282
+ brpoplpush(conn, wait)
156
283
  end
157
284
  end
158
285
 
159
- def available_key
160
- @available_key ||= namespaced_key("AVAILABLE")
286
+ #
287
+ # @api private
288
+ #
289
+ def brpoplpush(conn, wait)
290
+ raise InvalidArgument, "wait must be an integer" unless wait.is_a?(Integer)
291
+
292
+ # passing timeout 0 to brpoplpush causes it to block indefinitely
293
+ conn.brpoplpush(key.queued, key.primed, timeout: wait)
161
294
  end
162
295
 
163
- def exists_key
164
- @exists_key ||= namespaced_key("EXISTS")
296
+ #
297
+ # @api private
298
+ #
299
+ def rpoplpush(conn)
300
+ conn.rpoplpush(key.queued, key.primed)
301
+ end
302
+
303
+ #
304
+ # Writes lock information to redis.
305
+ # The lock information contains information about worker, queue, limit etc.
306
+ #
307
+ #
308
+ # @return [void]
309
+ #
310
+ def write_lock_info(conn)
311
+ return unless config.lock_info?
312
+
313
+ conn.set(key.info, lock_info)
165
314
  end
166
315
 
167
- def grabbed_key
168
- @grabbed_key ||= namespaced_key("GRABBED")
316
+ #
317
+ # Used to combat redis imprecision with ttl/pttl
318
+ #
319
+ # @param [Integer] val the value to compute drift for
320
+ #
321
+ # @return [Integer] a computed drift value
322
+ #
323
+ def drift(val)
324
+ # Add 2 milliseconds to the drift to account for Redis expires
325
+ # precision, which is 1 millisecond, plus 1 millisecond min drift
326
+ # for small TTLs.
327
+ (val + 2).to_f * CLOCK_DRIFT_FACTOR
169
328
  end
170
329
 
171
- def version_key
172
- @version_key ||= namespaced_key("VERSION")
330
+ def add_drift(val)
331
+ val + drift(val)
173
332
  end
174
333
 
175
- def namespaced_key(variable)
176
- "#{unique_digest}:#{variable}"
334
+ #
335
+ # Checks if the lock has been taken
336
+ #
337
+ # @param [Redis] conn a redis connection
338
+ #
339
+ # @return [true, false]
340
+ #
341
+ def taken?(conn)
342
+ conn.hexists(key.locked, job_id)
177
343
  end
178
344
 
179
- def current_time
180
- seconds, microseconds_with_frac = redis_time
181
- Time.at(seconds, microseconds_with_frac)
345
+ def argv
346
+ [job_id, config.pttl, config.type, config.limit]
182
347
  end
183
348
 
184
- def redis_time
185
- redis(&:time)
349
+ def lock_info
350
+ @lock_info ||= dump_json(
351
+ WORKER => item[CLASS],
352
+ QUEUE => item[QUEUE],
353
+ LIMIT => item[LOCK_LIMIT],
354
+ TIMEOUT => item[LOCK_TIMEOUT],
355
+ TTL => item[LOCK_TTL],
356
+ TYPE => config.type,
357
+ LOCK_ARGS => item[LOCK_ARGS],
358
+ TIME => now_f,
359
+ )
186
360
  end
187
361
  end
188
- # rubocop:enable Metrics/ClassLength
189
362
  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