redis_queued_locks 1.21.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +216 -25
  3. data/.claude/rules/acquirer.md +6 -5
  4. data/.claude/rules/arguments.md +2 -2
  5. data/.claude/rules/changelog.md +27 -0
  6. data/.claude/rules/git.md +1 -1
  7. data/.claude/rules/logic.md +10 -12
  8. data/.claude/rules/swarm.md +3 -4
  9. data/.claude/rules/tests.md +6 -6
  10. data/.claude/rules/type-checking.md +13 -14
  11. data/.claude/rules/visitors.md +5 -5
  12. data/.claude/skills/rql-code-review/SKILL.md +112 -35
  13. data/CHANGELOG.md +98 -244
  14. data/CLAUDE.md +9 -8
  15. data/README.md +275 -85
  16. data/Rakefile +11 -0
  17. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +28 -12
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +38 -16
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -15
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +430 -63
  21. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +72 -22
  22. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
  23. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +3 -1
  24. data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
  25. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +10 -5
  26. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -9
  27. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +52 -23
  28. data/lib/redis_queued_locks/acquirer/locks.rb +6 -2
  29. data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -7
  30. data/lib/redis_queued_locks/acquirer/queues.rb +17 -6
  31. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +6 -2
  32. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +6 -2
  33. data/lib/redis_queued_locks/client.rb +66 -24
  34. data/lib/redis_queued_locks/config.rb +93 -1
  35. data/lib/redis_queued_locks/resource.rb +124 -9
  36. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +6 -5
  37. data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
  38. data/lib/redis_queued_locks/version.rb +2 -2
  39. data/rbs_collection.lock.yaml +4 -0
  40. data/rbs_collection.yaml +2 -1
  41. data/sig/manifest.yml +1 -0
  42. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
  43. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
  44. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +12 -6
  45. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +21 -14
  46. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
  47. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +59 -11
  48. data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
  49. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
  50. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +5 -3
  51. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +14 -1
  52. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
  53. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  54. data/sig/redis_queued_locks/client.rbs +10 -7
  55. data/sig/redis_queued_locks/config.rbs +54 -1
  56. data/sig/redis_queued_locks/instrument/sampler.rbs +3 -1
  57. data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
  58. data/sig/redis_queued_locks/logging/sampler.rbs +3 -1
  59. data/sig/redis_queued_locks/resource.rbs +22 -12
  60. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
  61. data/sig/redis_queued_locks/utilities.rbs +2 -1
  62. data/sig/redis_queued_locks.rbs +7 -0
  63. metadata +4 -3
  64. /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
@@ -134,7 +134,8 @@ module RedisQueuedLocks::Acquirer::Keys
134
134
 
135
135
  # Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
136
136
  # lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
137
- # the index yet) are skipped: all their keys are expired.
137
+ # the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
138
+ # expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
138
139
  #
139
140
  # NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
140
141
  # operation modules are independent of each other, see `Locks.each_lock_name_batch`);
@@ -147,6 +148,7 @@ module RedisQueuedLocks::Acquirer::Keys
147
148
  #
148
149
  # @api private
149
150
  # @since 1.20.0
151
+ # @version 1.22.0
150
152
  def each_lock_name_batch(redis_client, scan_size)
151
153
  lock_index_time = RedisQueuedLocks::Resource.lock_index_time
152
154
  # @type var lock_names: Array[String]
@@ -155,7 +157,9 @@ module RedisQueuedLocks::Acquirer::Keys
155
157
  redis_client.zscan(
156
158
  RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
157
159
  ) do |lock_name, lock_index_score|
158
- next if lock_index_score.to_f < lock_index_time
160
+ next if RedisQueuedLocks::Resource.lock_index_score_expired?(
161
+ lock_index_score, lock_index_time
162
+ )
159
163
 
160
164
  lock_names << lock_name
161
165
  next if lock_names.size < scan_size
@@ -3,19 +3,21 @@
3
3
  # NOTE: Lock Series PoC
4
4
  # @api private
5
5
  # @since 1.16.1
6
- # @version 1.18.0
6
+ # @version 1.22.0
7
7
  module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
8
8
  class << self
9
9
  # NOTE: Lock Series PoC
10
10
  # @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
11
+ # @param priority [Integer] The lock request priority of the series.
11
12
  # @api private
12
13
  # @since 1.16.1
13
- # @version 1.18.0
14
+ # @version 1.22.0
14
15
  def lock_series_obtained( # steep:ignore
15
16
  instrumenter,
16
17
  instr_sampled,
17
18
  lock_keys,
18
19
  rw_mode,
20
+ priority,
19
21
  ttl,
20
22
  acq_id,
21
23
  hst_id,
@@ -25,20 +27,22 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
25
27
  )
26
28
  return unless instr_sampled
27
29
  instrumenter.notify('redis_queued_locks.lock_series_obtained', {
28
- lock_keys:, rw_mode:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
30
+ lock_keys:, rw_mode:, priority:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
29
31
  }) rescue nil
30
32
  end
31
33
 
32
34
  # NOTE: Lock Series PoC
33
35
  # @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
36
+ # @param priority [Integer] The lock request priority of the series.
34
37
  # @api private
35
38
  # @since 1.16.1
36
- # @version 1.21.0
39
+ # @version 1.22.0
37
40
  def lock_series_hold_and_release( # steep:ignore
38
41
  instrumenter,
39
42
  instr_sampled,
40
43
  lock_keys,
41
44
  rw_mode,
45
+ priority,
42
46
  ttl,
43
47
  acq_id,
44
48
  hst_id,
@@ -49,7 +53,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
49
53
  )
50
54
  return unless instr_sampled
51
55
  instrumenter.notify('redis_queued_locks.lock_series_hold_and_release', {
52
- lock_keys:, rw_mode:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
56
+ lock_keys:, rw_mode:, priority:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:,
57
+ instrument:
53
58
  }) rescue nil
54
59
  end
55
60
  end
@@ -3,14 +3,15 @@
3
3
  # NOTE: Lock Series PoC
4
4
  # @api private
5
5
  # @since 1.16.1
6
- # @version 1.18.0
6
+ # @version 1.22.0
7
7
  module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
8
8
  class << self
9
9
  # NOTE: Lock Series PoC
10
10
  # @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
11
+ # @param priority [Integer] The lock request priority of the series.
11
12
  # @api private
12
13
  # @since 1.16.0
13
- # @version 1.21.0
14
+ # @version 1.22.0
14
15
  def start_lock_series_obtaining( # steep:ignore
15
16
  logger,
16
17
  log_sampled,
@@ -19,7 +20,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
19
20
  acquirer_id,
20
21
  host_id,
21
22
  access_strategy,
22
- rw_mode
23
+ rw_mode,
24
+ priority
23
25
  )
24
26
  return unless log_sampled
25
27
 
@@ -30,15 +32,17 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
30
32
  "acq_id => '#{acquirer_id}' " \
31
33
  "hst_id => '#{host_id}' " \
32
34
  "acs_strat => '#{access_strategy}' " \
33
- "rw_mode => '#{rw_mode}'"
35
+ "rw_mode => '#{rw_mode}' " \
36
+ "priority => #{priority}"
34
37
  end rescue nil
35
38
  end
36
39
 
37
40
  # NOTE: Lock Series PoC
38
41
  # @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
42
+ # @param priority [Integer] The lock request priority of the series.
39
43
  # @api private
40
44
  # @since 1.16.0
41
- # @version 1.21.0
45
+ # @version 1.22.0
42
46
  def lock_series_obtained( # steep:ignore
43
47
  logger,
44
48
  log_sampled,
@@ -48,7 +52,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
48
52
  host_id,
49
53
  acq_time,
50
54
  access_strategy,
51
- rw_mode
55
+ rw_mode,
56
+ priority
52
57
  )
53
58
  return unless log_sampled
54
59
 
@@ -60,15 +65,17 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
60
65
  "hst_id => '#{host_id}' " \
61
66
  "acs_strat => '#{access_strategy}' " \
62
67
  "rw_mode => '#{rw_mode}' " \
68
+ "priority => #{priority} " \
63
69
  "acq_time => #{acq_time} (ms)"
64
70
  end rescue nil
65
71
  end
66
72
 
67
73
  # NOTE: Lock Series PoC
68
74
  # @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
75
+ # @param priority [Integer] The lock request priority of the series.
69
76
  # @api private
70
77
  # @since 1.16.0
71
- # @version 1.18.0
78
+ # @version 1.22.0
72
79
  def expire_lock_series( # steep:ignore
73
80
  logger,
74
81
  log_sampled,
@@ -77,7 +84,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
77
84
  acquirer_id,
78
85
  host_id,
79
86
  access_strategy,
80
- rw_mode
87
+ rw_mode,
88
+ priority
81
89
  )
82
90
  return unless log_sampled
83
91
 
@@ -88,7 +96,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
88
96
  "acq_id => '#{acquirer_id}' " \
89
97
  "hst_id => '#{host_id}' " \
90
98
  "acs_strat => '#{access_strategy}' " \
91
- "rw_mode => '#{rw_mode}'"
99
+ "rw_mode => '#{rw_mode}' " \
100
+ "priority => #{priority}"
92
101
  end rescue nil
93
102
  end
94
103
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # NOTE: Lock Series PoC
4
- # rubocop:disable all
4
+ # rubocop:disable Metrics/ModuleLength, Metrics/ClassLength, Metrics/MethodLength
5
5
  # @api private
6
6
  # @since 1.16.0
7
- # @version 1.20.0
7
+ # @version 1.22.0
8
8
  module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
9
9
  require_relative 'lock_series_poc/log_visitor'
10
10
  require_relative 'lock_series_poc/instr_visitor'
@@ -15,7 +15,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
15
15
  class << self
16
16
  # @api private
17
17
  # @since 1.16.0
18
- # @version 1.21.0
18
+ # @version 1.22.0
19
19
  def lock_series_poc( # steep:ignore
20
20
  redis,
21
21
  lock_names,
@@ -45,6 +45,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
45
45
  conflict_strategy:,
46
46
  read_write_mode:,
47
47
  access_strategy:,
48
+ priority:,
49
+ lock_priorities:,
48
50
  log_sampling_enabled:,
49
51
  log_sampling_percent:,
50
52
  log_sampler:,
@@ -57,6 +59,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
57
59
  )
58
60
  # NOTE: (RW) the lock mode is the same for all locks of the series
59
61
  # (see RedisQueuedLocks::Acquirer::AcquireLock for read_write_mode validations);
62
+ # NOTE: the lock request priority is the same for all locks of the series too (validated
63
+ # below: the series logs and instrumentation report it);
60
64
 
61
65
  case meta
62
66
  when Hash, NilClass then nil
@@ -120,6 +124,23 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
120
124
  )
121
125
  end
122
126
 
127
+ # NOTE: the priority of the series is validated before the series logs and instrumentation:
128
+ # a priority name is resolved to its Integer priority (the series events report the Integer
129
+ # priority; the same check as in RedisQueuedLocks::Acquirer::AcquireLock);
130
+ priority = lock_priorities.fetch(priority, priority) if priority.is_a?(::Symbol)
131
+ if !priority.is_a?(::Integer) ||
132
+ priority < RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY ||
133
+ priority > RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY
134
+ raise(
135
+ RedisQueuedLocks::ArgumentError,
136
+ "`:priority` argument should be an Integer from " \
137
+ "#{RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY} to " \
138
+ "#{RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY} or a priority name of " \
139
+ "`config['lock_priorities']` (#{lock_priorities.keys.map(&:inspect).join(', ')}), " \
140
+ "got #{priority.inspect}."
141
+ )
142
+ end
143
+
123
144
  locks_count = lock_names.size
124
145
 
125
146
  lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
@@ -164,10 +185,10 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
164
185
  identity
165
186
  )
166
187
 
167
- RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.start_lock_series_obtaining( # steep:ignore
188
+ LogVisitor.start_lock_series_obtaining( # steep:ignore
168
189
  logger, log_sampled, lock_keys_for_instrumentation,
169
- queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
170
- read_write_mode
190
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
191
+ access_strategy, read_write_mode, priority
171
192
  )
172
193
 
173
194
  acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
@@ -208,6 +229,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
208
229
  conflict_strategy:,
209
230
  read_write_mode:,
210
231
  access_strategy:,
232
+ priority:,
233
+ lock_priorities:,
211
234
  log_sampling_enabled:,
212
235
  log_sampling_percent:,
213
236
  log_sampler:,
@@ -240,7 +263,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
240
263
  else
241
264
  failed_on_lock = lock_operation_options[:lock_name]
242
265
  failed_locks_and_errors[lock_operation_options[:lock_name]] = result
243
- # NOTE: release all previously acquired locks (non-exceptional failure: raise_errors: false)
266
+ # NOTE: release all previously acquired locks
267
+ # (non-exceptional failure: raise_errors: false)
244
268
  unless is_failed_series_released
245
269
  release_lock_series( # steep:ignore
246
270
  redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
@@ -251,20 +275,22 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
251
275
  end
252
276
  end
253
277
 
254
- if (successfully_acquired_locks.size == lock_names.size && (successfully_acquired_locks.all? { |res| res[:ok] }))
278
+ if successfully_acquired_locks.size == lock_names.size &&
279
+ successfully_acquired_locks.all? { |res| res[:ok] }
255
280
  acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
256
281
  acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
257
282
  ts = Time.now.to_f
258
283
 
259
- RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.lock_series_obtained( # steep:ignore
284
+ LogVisitor.lock_series_obtained( # steep:ignore
260
285
  logger, log_sampled, lock_keys_for_instrumentation,
261
286
  queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
262
- acq_time, access_strategy, read_write_mode
287
+ acq_time, access_strategy, read_write_mode, priority
263
288
  )
264
289
 
265
- RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_obtained( # steep:ignore
266
- instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode,
267
- ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time, instrument
290
+ InstrVisitor.lock_series_obtained( # steep:ignore
291
+ instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode, priority,
292
+ ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time,
293
+ instrument
268
294
  )
269
295
 
270
296
  yield_time = RedisQueuedLocks::Utilities.clock_gettime
@@ -274,8 +300,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
274
300
  ).ceil(2)
275
301
 
276
302
  yield_result = nil
277
- is_lock_manually_released = nil
278
- hold_time = nil
303
+ # @type var is_lock_manually_released: bool
304
+ # @type var hold_time: Float | Integer
279
305
 
280
306
  begin
281
307
  yield_result = yield_expire( # steep:ignore
@@ -316,17 +342,18 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
316
342
  hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
317
343
  ts = Time.now.to_f
318
344
 
319
- RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.expire_lock_series( # steep:ignore
345
+ LogVisitor.expire_lock_series( # steep:ignore
320
346
  logger, log_sampled, lock_keys_for_instrumentation,
321
- queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
322
- read_write_mode
347
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
348
+ access_strategy, read_write_mode, priority
323
349
  ) if is_lock_manually_released
324
350
 
325
- RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_hold_and_release( # steep:ignore
351
+ InstrVisitor.lock_series_hold_and_release( # steep:ignore
326
352
  instrumenter,
327
353
  instr_sampled,
328
354
  lock_keys_for_instrumentation,
329
355
  read_write_mode,
356
+ priority,
330
357
  ttl,
331
358
  acquirer_id_for_instrumentation,
332
359
  host_id_for_instrumentation,
@@ -362,7 +389,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
362
389
  lock_series: lock_names,
363
390
  acquired_locks:,
364
391
  missing_locks:,
365
- failed_on_lock:,
392
+ failed_on_lock:
366
393
  }
367
394
  }
368
395
  end
@@ -382,7 +409,9 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
382
409
  #
383
410
  # @api private
384
411
  # @since 1.18.0
385
- def release_lock_series(redis, read_write_mode, successfully_acquired_locks, acquirer_id) # steep:ignore
412
+ def release_lock_series( # steep:ignore
413
+ redis, read_write_mode, successfully_acquired_locks, acquirer_id
414
+ )
386
415
  lock_names = obtained_lock_names(successfully_acquired_locks) # steep:ignore
387
416
 
388
417
  if read_write_mode == :read
@@ -427,7 +456,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
427
456
  own_lock_keys.each { |lock_key| transaction.call('DEL', lock_key) }
428
457
  end
429
458
  # NOTE: `nil` => the transaction is aborted (a watched lock is changed) => retry
430
- break unless released.nil?
459
+ break unless released == nil
431
460
  end
432
461
  rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
433
462
  RedisQueuedLocks::TimedLockIntermediateTimeoutError,
@@ -481,4 +510,4 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
481
510
  end
482
511
  end
483
512
  end
484
- # rubocop:enable all
513
+ # rubocop:enable Metrics/ModuleLength, Metrics/ClassLength, Metrics/MethodLength
@@ -115,7 +115,8 @@ module RedisQueuedLocks::Acquirer::Locks
115
115
 
116
116
  # Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
117
117
  # lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
118
- # the index yet) are skipped: all their keys are expired.
118
+ # the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
119
+ # expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
119
120
  #
120
121
  # NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
121
122
  # operation modules are independent of each other, see `extract_locks_info`);
@@ -128,6 +129,7 @@ module RedisQueuedLocks::Acquirer::Locks
128
129
  #
129
130
  # @api private
130
131
  # @since 1.20.0
132
+ # @version 1.22.0
131
133
  def each_lock_name_batch(redis_client, scan_size)
132
134
  lock_index_time = RedisQueuedLocks::Resource.lock_index_time
133
135
  # @type var lock_names: Array[String]
@@ -136,7 +138,9 @@ module RedisQueuedLocks::Acquirer::Locks
136
138
  redis_client.zscan(
137
139
  RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
138
140
  ) do |lock_name, lock_index_score|
139
- next if lock_index_score.to_f < lock_index_time
141
+ next if RedisQueuedLocks::Resource.lock_index_score_expired?(
142
+ lock_index_score, lock_index_time
143
+ )
140
144
 
141
145
  lock_names << lock_name
142
146
  next if lock_names.size < scan_size
@@ -16,22 +16,34 @@ module RedisQueuedLocks::Acquirer::QueueInfo
16
16
  # - result format: {
17
17
  # "lock_queue" => "rql:lock_queue:your_lock_name", # lock queue key in redis,
18
18
  # "queue" => [
19
- # { "acq_id" => "rql:acq:123/456/789/987/id", "score" => 123.456, "rw_mode" => "write" },
20
- # { "acq_id" => "rql:acq:123/686/789/987/id", "score" => 456.789, "rw_mode" => "write" },
19
+ # {
20
+ # "acq_id" => "rql:acq:123/456/789/987/id",
21
+ # "score" => 123.456,
22
+ # "rw_mode" => "write",
23
+ # "priority" => 0
24
+ # },
21
25
  # ...
22
26
  # ] # ordered set (by score) with information about an acquirer, their position in queue
23
- # # and the requested lock mode
27
+ # # (the enqueue time shifted by the priority band: see
28
+ # # `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`), the requested lock mode and
29
+ # # the lock request priority (decoded from the position)
24
30
  # <additional keys when the queue of read lock requests exists>:
25
31
  # "read_lock_queue" => "rql:lock_read_queue:your_lock_name", # read lock queue key in redis
26
32
  # "read_queue" => [
27
- # { "acq_id" => "rql:acq:123/456/789/987/id", "score" => 123.456, "rw_mode" => "read" },
33
+ # {
34
+ # "acq_id" => "rql:acq:123/456/789/987/id",
35
+ # "score" => 123.456,
36
+ # "rw_mode" => "read",
37
+ # "priority" => 0
38
+ # },
28
39
  # ...
29
40
  # ] # read lock requests (positions are comparable with the write lock request positions)
30
41
  # }
31
42
  #
32
43
  # @api private
33
44
  # @since 1.0.0
34
- # @version 1.20.0
45
+ # @version 1.22.0
46
+ # rubocop:disable Metrics/MethodLength
35
47
  def queue_info(redis_client, lock_name)
36
48
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
37
49
  read_lock_key_queue = RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
@@ -54,14 +66,24 @@ module RedisQueuedLocks::Acquirer::QueueInfo
54
66
  queue_info = {
55
67
  'lock_queue' => lock_key_queue,
56
68
  'queue' => zrange_cmd_res.map do |val|
57
- { 'acq_id' => val[0], 'score' => val[1], 'rw_mode' => 'write' }
69
+ {
70
+ 'acq_id' => val[0],
71
+ 'score' => val[1],
72
+ 'rw_mode' => 'write',
73
+ 'priority' => RedisQueuedLocks::Resource.lock_request_priority(val[1])
74
+ }
58
75
  end
59
76
  }
60
77
  # NOTE: (RW) the queue of read lock requests
61
78
  unless read_zrange_cmd_res.empty?
62
79
  queue_info['read_lock_queue'] = read_lock_key_queue
63
80
  queue_info['read_queue'] = read_zrange_cmd_res.map do |val|
64
- { 'acq_id' => val[0], 'score' => val[1], 'rw_mode' => 'read' }
81
+ {
82
+ 'acq_id' => val[0],
83
+ 'score' => val[1],
84
+ 'rw_mode' => 'read',
85
+ 'priority' => RedisQueuedLocks::Resource.lock_request_priority(val[1])
86
+ }
65
87
  end
66
88
  end
67
89
  queue_info
@@ -70,5 +92,6 @@ module RedisQueuedLocks::Acquirer::QueueInfo
70
92
  nil
71
93
  end
72
94
  end
95
+ # rubocop:enable Metrics/MethodLength
73
96
  end
74
97
  end
@@ -15,7 +15,7 @@ module RedisQueuedLocks::Acquirer::Queues
15
15
  #
16
16
  # @api private
17
17
  # @since 1.0.0
18
- # @version 1.21.0
18
+ # @version 1.22.0
19
19
  def queues(redis_client, scan_size:, with_info:, lock_index_enabled:)
20
20
  # NOTE: (arguments) the same range as the config key: zero/negative sizes fail SCAN/ZSCAN
21
21
  # (`COUNT 0`) and the batching (`each_slice(0)`);
@@ -102,7 +102,8 @@ module RedisQueuedLocks::Acquirer::Queues
102
102
 
103
103
  # Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
104
104
  # lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
105
- # the index yet) are skipped: all their keys are expired.
105
+ # the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
106
+ # expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
106
107
  #
107
108
  # NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
108
109
  # operation modules are independent of each other, see `Locks.each_lock_name_batch`);
@@ -115,6 +116,7 @@ module RedisQueuedLocks::Acquirer::Queues
115
116
  #
116
117
  # @api private
117
118
  # @since 1.20.0
119
+ # @version 1.22.0
118
120
  def each_lock_name_batch(redis_client, scan_size)
119
121
  lock_index_time = RedisQueuedLocks::Resource.lock_index_time
120
122
  # @type var lock_names: Array[String]
@@ -123,7 +125,9 @@ module RedisQueuedLocks::Acquirer::Queues
123
125
  redis_client.zscan(
124
126
  RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
125
127
  ) do |lock_name, lock_index_score|
126
- next if lock_index_score.to_f < lock_index_time
128
+ next if RedisQueuedLocks::Resource.lock_index_score_expired?(
129
+ lock_index_score, lock_index_time
130
+ )
127
131
 
128
132
  lock_names << lock_name
129
133
  next if lock_names.size < scan_size
@@ -139,11 +143,13 @@ module RedisQueuedLocks::Acquirer::Queues
139
143
  # @param lock_queues [Set<String>]
140
144
  # @param scan_size [Integer] The number of queues whose requests are read in one round trip.
141
145
  # @return [Set<Hash<Symbol,Any>>]
142
- # - each request: `{ 'acq_id' => String, 'score' => Float, 'rw_mode' => 'read'/'write' }`;
146
+ # - each request: `{ 'acq_id' => String, 'score' => Float, 'rw_mode' => 'read'/'write',
147
+ # 'priority' => Integer }` (the priority is decoded from the position: see
148
+ # `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`);
143
149
  #
144
150
  # @api private
145
151
  # @since 1.0.0
146
- # @version 1.20.0
152
+ # @version 1.22.0
147
153
  def extract_queues_info(redis_client, lock_queues, scan_size)
148
154
  # NOTE: the same approach as in `QueueInfo` (duplicated on purpose: public operation modules
149
155
  # do not reuse each other, they share logic via `Resource`, `Utilities`);
@@ -171,7 +177,12 @@ module RedisQueuedLocks::Acquirer::Queues
171
177
  seeded_queues << {
172
178
  queue: lock_queue,
173
179
  requests: queues_requests[index].map do |val|
174
- { 'acq_id' => val[0], 'score' => val[1], 'rw_mode' => rw_mode }
180
+ {
181
+ 'acq_id' => val[0],
182
+ 'score' => val[1],
183
+ 'rw_mode' => rw_mode,
184
+ 'priority' => RedisQueuedLocks::Resource.lock_request_priority(val[1])
185
+ }
175
186
  end
176
187
  }
177
188
  end
@@ -272,7 +272,8 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
272
272
 
273
273
  # Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
274
274
  # lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
275
- # the index yet) are skipped: all their keys are expired.
275
+ # the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
276
+ # expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
276
277
  #
277
278
  # NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
278
279
  # operation modules are independent of each other, see `Locks.each_lock_name_batch`);
@@ -285,6 +286,7 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
285
286
  #
286
287
  # @api private
287
288
  # @since 1.20.0
289
+ # @version 1.22.0
288
290
  def each_lock_name_batch(redis_client, scan_size)
289
291
  lock_index_time = RedisQueuedLocks::Resource.lock_index_time
290
292
  # @type var lock_names: Array[String]
@@ -293,7 +295,9 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
293
295
  redis_client.zscan(
294
296
  RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
295
297
  ) do |lock_name, lock_index_score|
296
- next if lock_index_score.to_f < lock_index_time
298
+ next if RedisQueuedLocks::Resource.lock_index_score_expired?(
299
+ lock_index_score, lock_index_time
300
+ )
297
301
 
298
302
  lock_names << lock_name
299
303
  next if lock_names.size < scan_size
@@ -427,7 +427,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
427
427
 
428
428
  # Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
429
429
  # lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
430
- # the index yet) are skipped: all their keys are expired.
430
+ # the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
431
+ # expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
431
432
  #
432
433
  # NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
433
434
  # operation modules are independent of each other, see `Locks.each_lock_name_batch`);
@@ -440,6 +441,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
440
441
  #
441
442
  # @api private
442
443
  # @since 1.20.0
444
+ # @version 1.22.0
443
445
  def each_lock_name_batch(redis_client, scan_size)
444
446
  lock_index_time = RedisQueuedLocks::Resource.lock_index_time
445
447
  # @type var lock_names: Array[String]
@@ -448,7 +450,9 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
448
450
  redis_client.zscan(
449
451
  RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
450
452
  ) do |lock_name, lock_index_score|
451
- next if lock_index_score.to_f < lock_index_time
453
+ next if RedisQueuedLocks::Resource.lock_index_score_expired?(
454
+ lock_index_score, lock_index_time
455
+ )
452
456
 
453
457
  lock_names << lock_name
454
458
  next if lock_names.size < scan_size