redis_queued_locks 1.20.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +249 -36
  3. data/.claude/rules/acquirer.md +16 -14
  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 +34 -13
  8. data/.claude/rules/swarm.md +4 -5
  9. data/.claude/rules/tests.md +9 -6
  10. data/.claude/rules/type-checking.md +13 -14
  11. data/.claude/rules/visitors.md +13 -14
  12. data/.claude/skills/rql-code-review/SKILL.md +134 -43
  13. data/CHANGELOG.md +105 -125
  14. data/CLAUDE.md +11 -9
  15. data/README.md +501 -136
  16. data/Rakefile +11 -0
  17. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +68 -10
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +82 -15
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -14
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +441 -63
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
  23. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
  24. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +5 -5
  25. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +176 -39
  26. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +322 -49
  27. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +125 -13
  28. data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
  29. data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
  30. data/lib/redis_queued_locks/acquirer/keys.rb +16 -3
  31. data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
  32. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +11 -5
  33. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +20 -10
  34. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +105 -36
  35. data/lib/redis_queued_locks/acquirer/locks.rb +32 -11
  36. data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -8
  37. data/lib/redis_queued_locks/acquirer/queues.rb +26 -6
  38. data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
  39. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +37 -6
  40. data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
  41. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +45 -6
  42. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
  43. data/lib/redis_queued_locks/client.rb +140 -56
  44. data/lib/redis_queued_locks/config.rb +105 -14
  45. data/lib/redis_queued_locks/errors.rb +34 -4
  46. data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
  47. data/lib/redis_queued_locks/logging/sampler.rb +4 -8
  48. data/lib/redis_queued_locks/resource.rb +137 -12
  49. data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
  50. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
  51. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +8 -6
  52. data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
  53. data/lib/redis_queued_locks/swarm.rb +86 -5
  54. data/lib/redis_queued_locks/utilities.rb +1 -1
  55. data/lib/redis_queued_locks/version.rb +2 -2
  56. data/rbs_collection.lock.yaml +4 -0
  57. data/rbs_collection.yaml +2 -1
  58. data/sig/manifest.yml +1 -0
  59. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
  60. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +25 -5
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +32 -12
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +59 -11
  65. data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
  66. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +4 -4
  67. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +6 -4
  68. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +23 -1
  69. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +16 -3
  70. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  71. data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
  72. data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
  73. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
  74. data/sig/redis_queued_locks/client.rbs +24 -10
  75. data/sig/redis_queued_locks/config.rbs +54 -1
  76. data/sig/redis_queued_locks/errors.rbs +5 -2
  77. data/sig/redis_queued_locks/instrument/sampler.rbs +3 -3
  78. data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
  79. data/sig/redis_queued_locks/logging/sampler.rbs +3 -3
  80. data/sig/redis_queued_locks/resource.rbs +23 -13
  81. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
  82. data/sig/redis_queued_locks/utilities.rbs +2 -1
  83. data/sig/redis_queued_locks.rbs +7 -0
  84. data/sig/vendor/redis_client.rbs +1 -0
  85. metadata +4 -3
  86. /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
@@ -6,7 +6,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::WithAcqTimeout
6
6
  # @param redis [RedisClient]
7
7
  # Redis connection manager required for additional data extraction for error message.
8
8
  # @param timeout [NilClass,Integer]
9
- # Time period after which the logic will fail with timeout error.
9
+ # Time period after which the logic will fail with timeout error (`nil` and `0`: no timeout).
10
10
  # @param lock_key [String]
11
11
  # Lock name in RQL notation (rql:lock:some-lock-name).
12
12
  # @param lock_name [String]
@@ -26,7 +26,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::WithAcqTimeout
26
26
  #
27
27
  # @api private
28
28
  # @since 1.0.0
29
- # @version 1.11.0
29
+ # @version 1.21.0
30
30
  def with_acq_timeout(
31
31
  redis,
32
32
  timeout,
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.16.0
5
+ # @version 1.18.0
5
6
  module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
6
7
  class << self
7
8
  # @param logger [::Logger,#debug]
@@ -30,7 +30,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
30
30
  # @param access_strategy [Symbol] Lock obtaining strategy.
31
31
  # @param timed [Boolean] Should the lock be wrapped by Timeout with with lock's ttl
32
32
  # @param ttl_shift [Numeric] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
33
- # @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
33
+ # @param ttl [Integer] Lock's time to live (in ms). A positive Integer.
34
34
  # @param queue_ttl [Integer] Lock request lifetime.
35
35
  # @param meta [NilClass,Hash<String|Symbol,Any>] Custom metadata wich is passed to the lock data;
36
36
  # @param log_sampled [Boolean] Should the logic be logged or not.
@@ -45,7 +45,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
45
45
  #
46
46
  # @api private
47
47
  # @since 1.3.0
48
- # @version 1.18.0
48
+ # @version 1.21.0
49
49
  # rubocop:disable Metrics/MethodLength
50
50
  def yield_expire(
51
51
  redis,
@@ -71,7 +71,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
71
71
  initial_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
72
72
 
73
73
  if block_given?
74
- if timed && ttl != nil
74
+ if timed
75
75
  # NOTE:
76
76
  # - steep is ignored cuz steep can not recognize `::Integer - ::Integer|::Float`
77
77
  # operation here for some mystical reason (it tryes to find overloadd `-` method for
@@ -150,7 +150,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
150
150
 
151
151
  # @param timeout [Float]
152
152
  # @param lock_key [String]
153
- # @param lock_ttl [Integer,NilClass]
153
+ # @param lock_ttl [Integer]
154
154
  # @param acquirer_id [String]
155
155
  # @param host_id [String]
156
156
  # @param meta [NilClass,Hash<Symbol|String,Any>]
@@ -161,7 +161,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
161
161
  #
162
162
  # @api private
163
163
  # @since 1.3.0
164
- # @version 1.12.0
164
+ # @version 1.21.0
165
165
  def yield_with_timeout(timeout, lock_key, lock_ttl, acquirer_id, host_id, meta, &block)
166
166
  ::Timeout.timeout(timeout, RedisQueuedLocks::TimedLockIntermediateTimeoutError, &block)
167
167
  rescue RedisQueuedLocks::TimedLockIntermediateTimeoutError
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- # @version 1.20.0
5
+ # @version 1.22.0
6
6
  # rubocop:disable Metrics/ModuleLength
7
7
  # rubocop:disable Metrics/MethodLength
8
8
  # rubocop:disable Metrics/ClassLength
@@ -42,8 +42,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
42
42
  # A current fiber that want to acquire a lock.
43
43
  # @option ractor_id [Integer,String]
44
44
  # The current ractor that want to acquire a lock.
45
- # @option ttl [Integer,NilClass]
46
- # Lock's time to live (in milliseconds). Nil means "without timeout".
45
+ # @option ttl [Integer]
46
+ # Lock's time to live (in milliseconds). A positive Integer (both modes).
47
47
  # @option queue_ttl [Integer]
48
48
  # Lifetime of the acuier's lock request. In seconds.
49
49
  # @option lock_index_enabled [Boolean]
@@ -57,7 +57,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
57
57
  # - pre-configured in `config['lock_index_grace_period']`;
58
58
  # - used only with the enabled lock index (`lock_index_enabled`);
59
59
  # @option timeout [Integer,NilClass]
60
- # Time period whe should try to acquire the lock (in seconds).
60
+ # Time period whe should try to acquire the lock (in seconds). Nil means "without timeout"
61
+ # (`0` is the same as `nil`: `Timeout.timeout(0)` sets no timeout).
61
62
  # @option timed [Boolean]
62
63
  # Limit the invocation time period of the passed block of code by the lock's TTL.
63
64
  # @option retry_count [Integer,NilClass]
@@ -125,13 +126,29 @@ module RedisQueuedLocks::Acquirer::AcquireLock
125
126
  # (but with checking the limist, retries, timeouts and so on). if lock is
126
127
  # free to obtain - it will be obtained;
127
128
  # - pre-configured in `config['default_access_strategy']`;
129
+ # @option priority [Integer,Symbol]
130
+ # - The lock request priority: from `RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY` (`-100`)
131
+ # to `RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY` (`100`), `0` by default, or a priority
132
+ # name of `lock_priorities` (resolved to its Integer priority: the lock request queues,
133
+ # the logs and the instrumentation use the Integer priority);
134
+ # - `:queued` access strategy: the lock request is placed in the lock request queue after all
135
+ # requests of the same or higher priority and before all requests of the lower priority
136
+ # (strict priority, FIFO inside the same priority; between modes too: the read and write
137
+ # lock request queues use the same positions);
138
+ # - `:random` access strategy: the lock request does not wait for its turn (it ignores the
139
+ # order of the lock request queues and the priorities of other requests), but it is queued
140
+ # at the position of its priority like a `:queued` one: `:queued` lock requests wait for it;
141
+ # - the obtained lock is never taken away: the priority orders the waiting lock requests only;
142
+ # - pre-configured in `config['default_lock_priority']`;
143
+ # @option lock_priorities [Hash<Symbol,Integer>]
144
+ # - The priority names (`config['lock_priorities']`): a priority name => its Integer priority;
128
145
  # @option log_sampling_enabled [Boolean]
129
146
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
130
147
  # - disabled by default;
131
148
  # - works in tandem with <config['log_sampling_percent'] and <config['log_sampler']>;
132
149
  # @option log_sampling_percent [Integer]
133
150
  # - the percent of cases that should be logged;
134
- # - take an effect when <config['log_sampling_enalbed']> is true;
151
+ # - take an effect when <config['log_sampling_enabled']> is true;
135
152
  # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
136
153
  # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
137
154
  # - percent-based log sampler that decides should be RQL case logged or not;
@@ -152,7 +169,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
152
169
  # - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
153
170
  # @option instr_sampling_percent [Integer]
154
171
  # - the percent of cases that should be instrumented;
155
- # - take an effect when <config['instr_sampling_enalbed']> is true;
172
+ # - take an effect when <config['instr_sampling_enabled']> is true;
156
173
  # - works in tandem with <config['instr_sampling_enabled']>
157
174
  # and <config['instr_sampler']> configs;
158
175
  # @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
@@ -176,7 +193,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
176
193
  #
177
194
  # @api private
178
195
  # @since 1.0.0
179
- # @version 1.20.0
196
+ # @version 1.22.0
180
197
  def acquire_lock(
181
198
  redis,
182
199
  lock_name,
@@ -205,6 +222,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
205
222
  conflict_strategy:,
206
223
  read_write_mode:,
207
224
  access_strategy:,
225
+ priority:,
226
+ lock_priorities:,
208
227
  log_sampling_enabled:,
209
228
  log_sampling_percent:,
210
229
  log_sampler:,
@@ -227,7 +246,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock
227
246
  end
228
247
 
229
248
  # Step 0.1 (RW): prevent :read_write_mode incompatabilities
230
- if read_write_mode != :read && read_write_mode != :write
249
+ case read_write_mode # NOTE: (steep) case/when narrows `Symbol` to `RQL::readWriteMode` below
250
+ when :read, :write then nil
251
+ else
231
252
  raise(
232
253
  RedisQueuedLocks::ArgumentError,
233
254
  "`:read_write_mode` argument should be `:read` or `:write`, " \
@@ -235,29 +256,100 @@ module RedisQueuedLocks::Acquirer::AcquireLock
235
256
  )
236
257
  end
237
258
 
238
- # Step 0.1 (RW): prevent read lock incompatabilities
239
- # - read lock expiration time is calculated from the lock ttl;
240
- if read_write_mode == :read && (!ttl.is_a?(::Integer) || ttl <= 0)
259
+ # Step 0.1: prevent :ttl incompatibilities (both modes)
260
+ # - write lock: `PEXPIRE` with a non-positive ttl deletes the just obtained lock key in the
261
+ # obtaining transaction (the lock is "obtained" without the mutual exclusion);
262
+ # - read lock: the read lock expiration time is calculated from the lock ttl;
263
+ if !ttl.is_a?(::Integer) || ttl <= 0
241
264
  raise(
242
265
  RedisQueuedLocks::ArgumentError,
243
- "`:ttl` argument should be a positive Integer for read locks, got #{ttl.inspect}."
266
+ "`:ttl` argument should be a positive Integer, got #{ttl.inspect}."
267
+ )
268
+ end
269
+
270
+ # Step 0.1: prevent :queue_ttl, :timeout, :retry_* and :lock_index_grace_period
271
+ # incompatibilities (the same ranges as their config keys):
272
+ # - a non-positive queue ttl drops the own lock request in the queueing pipeline (the lock
273
+ # request is always dead);
274
+ # - a negative timeout/retry delay/retry jitter fails `Timeout.timeout`/`sleep` in the
275
+ # middle of the acquirement (`0` timeout is the same as `nil`: no timeout);
276
+ if !queue_ttl.is_a?(::Integer) || queue_ttl <= 0
277
+ raise(
278
+ RedisQueuedLocks::ArgumentError,
279
+ "`:queue_ttl` argument should be a positive Integer, got #{queue_ttl.inspect}."
280
+ )
281
+ end
282
+ if timeout != nil && (!timeout.is_a?(::Integer) || timeout < 0)
283
+ raise(
284
+ RedisQueuedLocks::ArgumentError,
285
+ "`:timeout` argument should be nil or a non-negative Integer, got #{timeout.inspect}."
286
+ )
287
+ end
288
+ if retry_count != nil && (!retry_count.is_a?(::Integer) || retry_count < 0)
289
+ raise(
290
+ RedisQueuedLocks::ArgumentError,
291
+ "`:retry_count` argument should be nil or a non-negative Integer, " \
292
+ "got #{retry_count.inspect}."
293
+ )
294
+ end
295
+ if !retry_delay.is_a?(::Integer) || retry_delay < 0
296
+ raise(
297
+ RedisQueuedLocks::ArgumentError,
298
+ "`:retry_delay` argument should be a non-negative Integer, got #{retry_delay.inspect}."
299
+ )
300
+ end
301
+ if !retry_jitter.is_a?(::Integer) || retry_jitter < 0
302
+ raise(
303
+ RedisQueuedLocks::ArgumentError,
304
+ "`:retry_jitter` argument should be a non-negative Integer, got #{retry_jitter.inspect}."
305
+ )
306
+ end
307
+ if !lock_index_grace_period.is_a?(::Integer) || lock_index_grace_period < 0
308
+ raise(
309
+ RedisQueuedLocks::ArgumentError,
310
+ "`:lock_index_grace_period` argument should be a non-negative Integer, " \
311
+ "got #{lock_index_grace_period.inspect}."
312
+ )
313
+ end
314
+
315
+ # Step 0.1: prevent :priority incompatibilities (the same range as its config key)
316
+ # - a Symbol is a priority name of `config['lock_priorities']`: it is resolved to its
317
+ # Integer priority (the lock request queues, the logs and the instrumentation use the
318
+ # Integer priority; an unknown name stays a Symbol and fails the check);
319
+ # - each priority has its own band of positions in the lock request queues: the range
320
+ # bounds the shift of the positions (float scores lose precision with the shift: the FIFO
321
+ # order inside a priority keeps ~61 µs for -100..100, see `LOCK_PRIORITY_BAND`);
322
+ priority = lock_priorities.fetch(priority, priority) if priority.is_a?(::Symbol)
323
+ if !priority.is_a?(::Integer) ||
324
+ priority < RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY ||
325
+ priority > RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY
326
+ raise(
327
+ RedisQueuedLocks::ArgumentError,
328
+ "`:priority` argument should be an Integer from " \
329
+ "#{RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY} to " \
330
+ "#{RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY} or a priority name of " \
331
+ "`config['lock_priorities']` (#{lock_priorities.keys.map(&:inspect).join(', ')}), " \
332
+ "got #{priority.inspect}."
244
333
  )
245
334
  end
246
335
 
247
336
  # Step 0.2: prevent :meta incompatabiltiies (structure)
337
+ # - Symbol keys are written to the lock data as Strings (`Symbol#name`, as redis-client
338
+ # does it) and would overwrite the reserved lock data fields too;
248
339
  if meta.is_a?(::Hash) && (meta.any? do |key, _value|
249
- key == 'acq_id' ||
250
- key == 'hst_id' ||
251
- key == 'ts' ||
252
- key == 'ini_ttl' ||
253
- key == 'lock_key' ||
254
- key == 'rem_ttl' ||
255
- key == 'spc_ext_ttl' ||
256
- key == 'spc_cnt' ||
257
- key == 'l_spc_ext_ini_ttl' ||
258
- key == 'l_spc_ext_ts' ||
259
- key == 'l_spc_ts' ||
260
- key == 'rw_mode'
340
+ meta_key = key.is_a?(::Symbol) ? key.name : key
341
+ meta_key == 'acq_id' ||
342
+ meta_key == 'hst_id' ||
343
+ meta_key == 'ts' ||
344
+ meta_key == 'ini_ttl' ||
345
+ meta_key == 'lock_key' ||
346
+ meta_key == 'rem_ttl' ||
347
+ meta_key == 'spc_ext_ttl' ||
348
+ meta_key == 'spc_cnt' ||
349
+ meta_key == 'l_spc_ext_ini_ttl' ||
350
+ meta_key == 'l_spc_ext_ts' ||
351
+ meta_key == 'l_spc_ts' ||
352
+ meta_key == 'rw_mode'
261
353
  end)
262
354
  raise(
263
355
  RedisQueuedLocks::ArgumentError,
@@ -295,7 +387,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock
295
387
  RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
296
388
  end
297
389
 
298
- acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
390
+ # NOTE: the position in the lock request queue: the enqueue time shifted by the priority band
391
+ # (see `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`);
392
+ acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position(priority)
299
393
 
300
394
  log_sampled = RedisQueuedLocks::Logging.should_log?(
301
395
  log_sampling_enabled,
@@ -342,10 +436,13 @@ module RedisQueuedLocks::Acquirer::AcquireLock
342
436
 
343
437
  LogVisitor.start_lock_obtaining(
344
438
  logger, log_sampled, lock_key,
345
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
439
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, priority
346
440
  )
347
441
 
348
442
  # Step 2: try to lock with timeout
443
+ # NOTE: the acquirement start time is defined out of the timeout block: the failed
444
+ # acquirement reports the acquirement time too (Steps 3.b and 3.c);
445
+ acq_start_time = clock_gettime
349
446
  with_acq_timeout(
350
447
  redis,
351
448
  timeout,
@@ -355,24 +452,23 @@ module RedisQueuedLocks::Acquirer::AcquireLock
355
452
  detailed_acq_timeout_error,
356
453
  on_timeout: acq_dequeue
357
454
  ) do
358
- acq_start_time = clock_gettime
359
-
360
455
  # Step 2.1: cyclically try to obtain the lock
361
456
  while acq_process[:should_try]
362
457
 
363
458
  LogVisitor.start_try_to_lock_cycle(
364
459
  logger, log_sampled, lock_key,
365
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
460
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, priority
366
461
  )
367
462
 
368
463
  # Step 2.X: check the actual score: is it in queue ttl limit or not?
369
- if RedisQueuedLocks::Resource.dead_score_reached?(acquirer_position, queue_ttl)
370
- # Step 2.X.X: dead score reached => re-queue the lock request with the new score;
371
- acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
464
+ if RedisQueuedLocks::Resource.dead_score_reached?(acquirer_position, priority, queue_ttl)
465
+ # Step 2.X.X: dead score reached => re-queue the lock request with the new score
466
+ # (the end of the requests of the same priority);
467
+ acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position(priority)
372
468
 
373
469
  LogVisitor.dead_score_reached__reset_acquirer_position(
374
470
  logger, log_sampled, lock_key,
375
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
471
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, priority
376
472
  )
377
473
  end
378
474
 
@@ -392,6 +488,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
392
488
  acquirer_id,
393
489
  host_id,
394
490
  acquirer_position,
491
+ priority,
395
492
  lock_ttl,
396
493
  queue_ttl,
397
494
  lock_index_enabled,
@@ -425,10 +522,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
425
522
  # instrumetnation: (reentrant lock with ttl extension)
426
523
  LogVisitor.extendable_reentrant_lock_obtained(
427
524
  logger, log_sampled, result[:lock_key],
428
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
525
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode,
526
+ priority
429
527
  )
430
528
  InstrVisitor.extendable_reentrant_lock_obtained(
431
- instrumenter, instr_sampled, result[:lock_key], read_write_mode,
529
+ instrumenter, instr_sampled, result[:lock_key], read_write_mode, priority,
432
530
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
433
531
  instrument
434
532
  )
@@ -436,10 +534,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
436
534
  # instrumetnation: (reentrant lock without ttl extension)
437
535
  LogVisitor.reentrant_lock_obtained(
438
536
  logger, log_sampled, result[:lock_key],
439
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
537
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode,
538
+ priority
440
539
  )
441
540
  InstrVisitor.reentrant_lock_obtained(
442
- instrumenter, instr_sampled, result[:lock_key], read_write_mode,
541
+ instrumenter, instr_sampled, result[:lock_key], read_write_mode, priority,
443
542
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
444
543
  instrument
445
544
  )
@@ -448,10 +547,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
448
547
  # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
449
548
  LogVisitor.lock_obtained(
450
549
  logger, log_sampled, result[:lock_key],
451
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
550
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode,
551
+ priority
452
552
  )
453
553
  InstrVisitor.lock_obtained(
454
- instrumenter, instr_sampled, result[:lock_key], read_write_mode,
554
+ instrumenter, instr_sampled, result[:lock_key], read_write_mode, priority,
455
555
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
456
556
  instrument
457
557
  )
@@ -609,6 +709,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
609
709
  instr_sampled,
610
710
  acq_process[:lock_info][:lock_key],
611
711
  read_write_mode,
712
+ priority,
612
713
  acq_process[:lock_info][:ttl],
613
714
  acq_process[:lock_info][:acq_id],
614
715
  acq_process[:lock_info][:hst_id],
@@ -624,6 +725,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
624
725
  instr_sampled,
625
726
  acq_process[:lock_info][:lock_key],
626
727
  read_write_mode,
728
+ priority,
627
729
  acq_process[:lock_info][:ttl],
628
730
  acq_process[:lock_info][:acq_id],
629
731
  acq_process[:lock_info][:hst_id],
@@ -650,8 +752,43 @@ module RedisQueuedLocks::Acquirer::AcquireLock
650
752
  acq_process[:result] = :timeout_reached
651
753
  end
652
754
  # Step 3.b: lock is not acquired (acquirer is dequeued by timeout callback)
755
+ acq_time = ((clock_gettime - acq_start_time) / 1_000.0).ceil(2)
756
+ LogVisitor.lock_obtaining_failed(
757
+ logger, log_sampled, lock_key, queue_ttl, acquirer_id, host_id,
758
+ acq_time, access_strategy, read_write_mode, priority, acq_process[:result]
759
+ )
760
+ InstrVisitor.lock_obtaining_failed(
761
+ instrumenter, instr_sampled, lock_key, read_write_mode, priority, lock_ttl, acquirer_id,
762
+ host_id, Time.now.to_f, acq_time, acq_process[:result], instrument
763
+ )
653
764
  { ok: false, result: acq_process[:result] } #: { ok: bool, result: Symbol }
654
765
  end
766
+ rescue RedisQueuedLocks::LockAlreadyObtainedError,
767
+ RedisQueuedLocks::ConflictLockObtainError,
768
+ RedisQueuedLocks::LockAcquirementRetryLimitError,
769
+ RedisQueuedLocks::LockAcquirementTimeoutError => error
770
+ # Step 3.c: (raise_errors) the failed acquirement is reported before the error is raised
771
+ # (like Step 3.b); errors of the block of code under the obtained lock are not reported;
772
+ # NOTE: (steep) the locals of the method body are nilable in the method-level `rescue` for
773
+ # Steep, but these errors are raised by the acquirement only (after Step 1: the locals
774
+ # are assigned);
775
+ # steep:ignore:start
776
+ unless acq_process[:acquired]
777
+ if error.is_a?(RedisQueuedLocks::LockAcquirementTimeoutError)
778
+ acq_process[:result] = :timeout_reached
779
+ end
780
+ acq_time = ((clock_gettime - acq_start_time) / 1_000.0).ceil(2)
781
+ LogVisitor.lock_obtaining_failed(
782
+ logger, log_sampled, lock_key, queue_ttl, acquirer_id, host_id,
783
+ acq_time, access_strategy, read_write_mode, priority, acq_process[:result]
784
+ )
785
+ InstrVisitor.lock_obtaining_failed(
786
+ instrumenter, instr_sampled, lock_key, read_write_mode, priority, lock_ttl, acquirer_id,
787
+ host_id, Time.now.to_f, acq_time, acq_process[:result], instrument
788
+ )
789
+ end
790
+ # steep:ignore:end
791
+ raise
655
792
  end
656
793
  end
657
794
  end