redis_queued_locks 1.21.0 → 1.23.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +294 -51
  3. data/.claude/rules/acquirer.md +16 -11
  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 +14 -15
  8. data/.claude/rules/swarm.md +3 -4
  9. data/.claude/rules/tests.md +9 -9
  10. data/.claude/rules/type-checking.md +14 -16
  11. data/.claude/rules/visitors.md +11 -9
  12. data/.claude/skills/rql-code-review/SKILL.md +129 -47
  13. data/.ruby-version +1 -1
  14. data/CHANGELOG.md +122 -245
  15. data/CLAUDE.md +12 -10
  16. data/README.md +516 -175
  17. data/Rakefile +11 -0
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +28 -12
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +38 -16
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -15
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +945 -272
  23. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +82 -32
  24. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
  25. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +15 -13
  26. data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
  27. data/lib/redis_queued_locks/acquirer/lock_series/instr_visitor.rb +243 -0
  28. data/lib/redis_queued_locks/acquirer/lock_series/log_visitor.rb +472 -0
  29. data/lib/redis_queued_locks/acquirer/lock_series/release_lock_series.rb +181 -0
  30. data/lib/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rb +1051 -0
  31. data/lib/redis_queued_locks/acquirer/lock_series.rb +948 -0
  32. data/lib/redis_queued_locks/acquirer/locks.rb +6 -2
  33. data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -7
  34. data/lib/redis_queued_locks/acquirer/queues.rb +17 -6
  35. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +6 -2
  36. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +6 -2
  37. data/lib/redis_queued_locks/acquirer.rb +1 -1
  38. data/lib/redis_queued_locks/client.rb +116 -44
  39. data/lib/redis_queued_locks/config.rb +93 -1
  40. data/lib/redis_queued_locks/resource.rb +124 -9
  41. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +6 -5
  42. data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
  43. data/lib/redis_queued_locks/version.rb +2 -2
  44. data/rbs_collection.lock.yaml +4 -0
  45. data/rbs_collection.yaml +2 -1
  46. data/redis_queued_locks.gemspec +21 -14
  47. data/sig/manifest.yml +1 -0
  48. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
  49. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
  50. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +12 -6
  51. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +21 -14
  52. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
  53. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +160 -11
  54. data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
  55. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
  56. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +5 -3
  57. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +14 -1
  58. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
  59. data/sig/redis_queued_locks/acquirer/lock_series/instr_visitor.rbs +102 -0
  60. data/sig/redis_queued_locks/acquirer/lock_series/log_visitor.rbs +154 -0
  61. data/sig/redis_queued_locks/acquirer/lock_series/release_lock_series.rbs +37 -0
  62. data/sig/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rbs +198 -0
  63. data/sig/redis_queued_locks/acquirer/lock_series.rbs +106 -0
  64. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  65. data/sig/redis_queued_locks/client.rbs +77 -7
  66. data/sig/redis_queued_locks/config.rbs +54 -1
  67. data/sig/redis_queued_locks/instrument/sampler.rbs +3 -1
  68. data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
  69. data/sig/redis_queued_locks/logging/sampler.rbs +3 -1
  70. data/sig/redis_queued_locks/resource.rbs +22 -12
  71. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
  72. data/sig/redis_queued_locks/utilities.rbs +2 -1
  73. data/sig/redis_queued_locks.rbs +7 -0
  74. metadata +35 -13
  75. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -56
  76. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -95
  77. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -484
  78. /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- # @version 1.20.0
5
+ # @version 1.23.0
6
6
  # rubocop:disable Metrics/ModuleLength
7
7
  # rubocop:disable Metrics/MethodLength
8
8
  # rubocop:disable Metrics/ClassLength
@@ -106,7 +106,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
106
106
  # - `:extendable_work_through`;
107
107
  # - `:wait_for_lock`;
108
108
  # - `:dead_locking`;
109
- # @option read_write_mode [Symbol]
109
+ # @option rw_mode [Symbol]
110
110
  # - `:write` (default) - exclusive lock: waits for the write lock and for all read locks;
111
111
  # - `:read` - shared lock: waits for the write lock only (read locks do not block each other);
112
112
  # - `:queued` access strategy orders read and write requests in FIFO between modes
@@ -126,6 +126,22 @@ module RedisQueuedLocks::Acquirer::AcquireLock
126
126
  # (but with checking the limist, retries, timeouts and so on). if lock is
127
127
  # free to obtain - it will be obtained;
128
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;
129
145
  # @option log_sampling_enabled [Boolean]
130
146
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
131
147
  # - disabled by default;
@@ -177,7 +193,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
177
193
  #
178
194
  # @api private
179
195
  # @since 1.0.0
180
- # @version 1.21.0
196
+ # @version 1.23.0
181
197
  def acquire_lock(
182
198
  redis,
183
199
  lock_name,
@@ -204,8 +220,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock
204
220
  logger:,
205
221
  log_lock_try:,
206
222
  conflict_strategy:,
207
- read_write_mode:,
223
+ rw_mode:,
208
224
  access_strategy:,
225
+ priority:,
226
+ lock_priorities:,
209
227
  log_sampling_enabled:,
210
228
  log_sampling_percent:,
211
229
  log_sampler:,
@@ -227,12 +245,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock
227
245
  )
228
246
  end
229
247
 
230
- # Step 0.1 (RW): prevent :read_write_mode incompatabilities
231
- if read_write_mode != :read && read_write_mode != :write
248
+ # Step 0.1 (RW): prevent :rw_mode incompatabilities
249
+ case rw_mode # NOTE: (steep) case/when narrows `Symbol` to `RQL::readWriteMode` below
250
+ when :read, :write then nil
251
+ else
232
252
  raise(
233
253
  RedisQueuedLocks::ArgumentError,
234
- "`:read_write_mode` argument should be `:read` or `:write`, " \
235
- "got #{read_write_mode.inspect}."
254
+ "`:rw_mode` argument should be `:read` or `:write`, " \
255
+ "got #{rw_mode.inspect}."
236
256
  )
237
257
  end
238
258
 
@@ -292,6 +312,27 @@ module RedisQueuedLocks::Acquirer::AcquireLock
292
312
  )
293
313
  end
294
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}."
333
+ )
334
+ end
335
+
295
336
  # Step 0.2: prevent :meta incompatabiltiies (structure)
296
337
  # - Symbol keys are written to the lock data as Strings (`Symbol#name`, as redis-client
297
338
  # does it) and would overwrite the reserved lock data fields too;
@@ -342,11 +383,13 @@ module RedisQueuedLocks::Acquirer::AcquireLock
342
383
  # NOTE: (RW) read lock data key is required by read lock requests only (write lock requests
343
384
  # never obtain read locks: read-to-write lock upgrade is not supported);
344
385
  read_lock_key =
345
- if read_write_mode == :read
386
+ if rw_mode == :read
346
387
  RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
347
388
  end
348
389
 
349
- 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)
350
393
 
351
394
  log_sampled = RedisQueuedLocks::Logging.should_log?(
352
395
  log_sampling_enabled,
@@ -379,7 +422,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
379
422
  redis,
380
423
  logger,
381
424
  lock_key,
382
- read_write_mode,
425
+ rw_mode,
383
426
  lock_key_queue,
384
427
  read_lock_key_queue,
385
428
  queue_ttl,
@@ -393,7 +436,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
393
436
 
394
437
  LogVisitor.start_lock_obtaining(
395
438
  logger, log_sampled, lock_key,
396
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
439
+ queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
397
440
  )
398
441
 
399
442
  # Step 2: try to lock with timeout
@@ -414,17 +457,18 @@ module RedisQueuedLocks::Acquirer::AcquireLock
414
457
 
415
458
  LogVisitor.start_try_to_lock_cycle(
416
459
  logger, log_sampled, lock_key,
417
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
460
+ queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
418
461
  )
419
462
 
420
463
  # Step 2.X: check the actual score: is it in queue ttl limit or not?
421
- if RedisQueuedLocks::Resource.dead_score_reached?(acquirer_position, queue_ttl)
422
- # Step 2.X.X: dead score reached => re-queue the lock request with the new score;
423
- 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)
424
468
 
425
469
  LogVisitor.dead_score_reached__reset_acquirer_position(
426
470
  logger, log_sampled, lock_key,
427
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
471
+ queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
428
472
  )
429
473
  end
430
474
 
@@ -435,7 +479,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
435
479
  logger,
436
480
  log_lock_try,
437
481
  lock_key,
438
- read_write_mode,
482
+ rw_mode,
439
483
  lock_key_queue,
440
484
  read_lock_key_queue,
441
485
  lock_readers_key,
@@ -444,6 +488,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
444
488
  acquirer_id,
445
489
  host_id,
446
490
  acquirer_position,
491
+ priority,
447
492
  lock_ttl,
448
493
  queue_ttl,
449
494
  lock_index_enabled,
@@ -477,10 +522,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
477
522
  # instrumetnation: (reentrant lock with ttl extension)
478
523
  LogVisitor.extendable_reentrant_lock_obtained(
479
524
  logger, log_sampled, result[:lock_key],
480
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
525
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, rw_mode,
526
+ priority
481
527
  )
482
528
  InstrVisitor.extendable_reentrant_lock_obtained(
483
- instrumenter, instr_sampled, result[:lock_key], read_write_mode,
529
+ instrumenter, instr_sampled, result[:lock_key], rw_mode, priority,
484
530
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
485
531
  instrument
486
532
  )
@@ -488,10 +534,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
488
534
  # instrumetnation: (reentrant lock without ttl extension)
489
535
  LogVisitor.reentrant_lock_obtained(
490
536
  logger, log_sampled, result[:lock_key],
491
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
537
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, rw_mode,
538
+ priority
492
539
  )
493
540
  InstrVisitor.reentrant_lock_obtained(
494
- instrumenter, instr_sampled, result[:lock_key], read_write_mode,
541
+ instrumenter, instr_sampled, result[:lock_key], rw_mode, priority,
495
542
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
496
543
  instrument
497
544
  )
@@ -500,10 +547,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
500
547
  # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
501
548
  LogVisitor.lock_obtained(
502
549
  logger, log_sampled, result[:lock_key],
503
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
550
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, rw_mode,
551
+ priority
504
552
  )
505
553
  InstrVisitor.lock_obtained(
506
- instrumenter, instr_sampled, result[:lock_key], read_write_mode,
554
+ instrumenter, instr_sampled, result[:lock_key], rw_mode, priority,
507
555
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
508
556
  instrument
509
557
  )
@@ -660,7 +708,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
660
708
  instrumenter,
661
709
  instr_sampled,
662
710
  acq_process[:lock_info][:lock_key],
663
- read_write_mode,
711
+ rw_mode,
712
+ priority,
664
713
  acq_process[:lock_info][:ttl],
665
714
  acq_process[:lock_info][:acq_id],
666
715
  acq_process[:lock_info][:hst_id],
@@ -675,7 +724,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
675
724
  instrumenter,
676
725
  instr_sampled,
677
726
  acq_process[:lock_info][:lock_key],
678
- read_write_mode,
727
+ rw_mode,
728
+ priority,
679
729
  acq_process[:lock_info][:ttl],
680
730
  acq_process[:lock_info][:acq_id],
681
731
  acq_process[:lock_info][:hst_id],
@@ -705,11 +755,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
705
755
  acq_time = ((clock_gettime - acq_start_time) / 1_000.0).ceil(2)
706
756
  LogVisitor.lock_obtaining_failed(
707
757
  logger, log_sampled, lock_key, queue_ttl, acquirer_id, host_id,
708
- acq_time, access_strategy, read_write_mode, acq_process[:result]
758
+ acq_time, access_strategy, rw_mode, priority, acq_process[:result]
709
759
  )
710
760
  InstrVisitor.lock_obtaining_failed(
711
- instrumenter, instr_sampled, lock_key, read_write_mode, lock_ttl, acquirer_id, host_id,
712
- Time.now.to_f, acq_time, acq_process[:result], instrument
761
+ instrumenter, instr_sampled, lock_key, rw_mode, priority, lock_ttl, acquirer_id,
762
+ host_id, Time.now.to_f, acq_time, acq_process[:result], instrument
713
763
  )
714
764
  { ok: false, result: acq_process[:result] } #: { ok: bool, result: Symbol }
715
765
  end
@@ -730,11 +780,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
730
780
  acq_time = ((clock_gettime - acq_start_time) / 1_000.0).ceil(2)
731
781
  LogVisitor.lock_obtaining_failed(
732
782
  logger, log_sampled, lock_key, queue_ttl, acquirer_id, host_id,
733
- acq_time, access_strategy, read_write_mode, acq_process[:result]
783
+ acq_time, access_strategy, rw_mode, priority, acq_process[:result]
734
784
  )
735
785
  InstrVisitor.lock_obtaining_failed(
736
- instrumenter, instr_sampled, lock_key, read_write_mode, lock_ttl, acquirer_id, host_id,
737
- Time.now.to_f, acq_time, acq_process[:result], instrument
786
+ instrumenter, instr_sampled, lock_key, rw_mode, priority, lock_ttl, acquirer_id,
787
+ host_id, Time.now.to_f, acq_time, acq_process[:result], instrument
738
788
  )
739
789
  end
740
790
  # steep:ignore:end