redis_queued_locks 1.16.2 → 1.18.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +281 -0
  3. data/.claude/rules/acquirer.md +81 -0
  4. data/.claude/rules/arguments.md +60 -0
  5. data/.claude/rules/git.md +46 -0
  6. data/.claude/rules/logic.md +63 -0
  7. data/.claude/rules/swarm.md +120 -0
  8. data/.claude/rules/tests.md +48 -0
  9. data/.claude/rules/type-checking.md +50 -0
  10. data/.claude/rules/visitors.md +69 -0
  11. data/.rubocop.rbs.yml +31 -0
  12. data/.rubocop.yml +3 -3
  13. data/.ruby-version +1 -1
  14. data/CHANGELOG.md +80 -0
  15. data/CLAUDE.md +36 -0
  16. data/README.md +367 -82
  17. data/Rakefile +31 -2
  18. data/github_ci/.keep +0 -0
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/delay_execution.rb +2 -2
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +6 -3
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +9 -7
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +20 -10
  23. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +34 -16
  24. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +240 -28
  25. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +398 -132
  26. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +12 -6
  27. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +33 -9
  28. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +73 -24
  29. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +9 -1
  30. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +182 -3
  31. data/lib/redis_queued_locks/acquirer/is_locked.rb +19 -2
  32. data/lib/redis_queued_locks/acquirer/is_queued.rb +6 -2
  33. data/lib/redis_queued_locks/acquirer/lock_info.rb +96 -6
  34. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +8 -4
  35. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -7
  36. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +126 -29
  37. data/lib/redis_queued_locks/acquirer/locks.rb +103 -6
  38. data/lib/redis_queued_locks/acquirer/queue_info.rb +38 -9
  39. data/lib/redis_queued_locks/acquirer/queues.rb +28 -4
  40. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +35 -1
  41. data/lib/redis_queued_locks/acquirer/release_lock.rb +40 -9
  42. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +43 -3
  43. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +143 -0
  44. data/lib/redis_queued_locks/acquirer.rb +1 -0
  45. data/lib/redis_queued_locks/client.rb +274 -158
  46. data/lib/redis_queued_locks/config/dsl.rb +4 -10
  47. data/lib/redis_queued_locks/resource.rb +117 -9
  48. data/lib/redis_queued_locks/swarm/flush_zombies.rb +46 -6
  49. data/lib/redis_queued_locks/swarm/supervisor.rb +1 -1
  50. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +166 -44
  51. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +45 -47
  52. data/lib/redis_queued_locks/swarm/zombie_info.rb +42 -0
  53. data/lib/redis_queued_locks/version.rb +2 -2
  54. data/rbs_collection.lock.yaml +10 -2
  55. data/rbs_collection.yaml +0 -2
  56. data/redis_queued_locks.gemspec +1 -1
  57. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -1
  58. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +0 -1
  59. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +5 -0
  60. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +12 -6
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +74 -7
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +4 -2
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -0
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +4 -2
  65. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +19 -1
  66. data/sig/redis_queued_locks/acquirer/lock_info.rbs +9 -1
  67. data/sig/redis_queued_locks/acquirer/locks.rbs +10 -1
  68. data/sig/redis_queued_locks/acquirer/queue_info.rbs +2 -4
  69. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  70. data/sig/redis_queued_locks/acquirer/release_lock.rbs +8 -1
  71. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +44 -0
  72. data/sig/redis_queued_locks/client.rbs +22 -2
  73. data/sig/redis_queued_locks/resource.rbs +9 -2
  74. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +1 -1
  75. data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +13 -5
  76. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +6 -6
  77. data/sig/redis_queued_locks/swarm/zombie_info.rbs +5 -0
  78. metadata +17 -5
  79. data/github_ci/ruby3.3.gemfile +0 -17
  80. data/github_ci/ruby3.3.gemfile.lock +0 -216
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api public
4
4
  # @since 1.0.0
5
- # @version 1.16.0
5
+ # @version 1.18.0
6
6
  # rubocop:disable Metrics/ClassLength
7
7
  class RedisQueuedLocks::Client
8
8
  # @return [RedisClient]
@@ -79,7 +79,7 @@ class RedisQueuedLocks::Client
79
79
  #
80
80
  # @api public
81
81
  # @since 1.9.0
82
- def swarm_info(zombie_ttl: config['swarm.flush_zombies.zombie_ttl']) # steep:ignore
82
+ def swarm_info(zombie_ttl: config['swarm.flush_zombies.zombie_ttl'])
83
83
  swarm.swarm_info(zombie_ttl:)
84
84
  end
85
85
 
@@ -108,9 +108,9 @@ class RedisQueuedLocks::Client
108
108
  # @api public
109
109
  # @since 1.9.0
110
110
  def flush_zombies(
111
- zombie_ttl: config['swarm.flush_zombies.zombie_ttl'], # steep:ignore
112
- lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size'], # steep:ignore
113
- queue_scan_size: config['swarm.flush_zombies.zombie_queue_scan_size'] # steep:ignore
111
+ zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
112
+ lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size'],
113
+ queue_scan_size: config['swarm.flush_zombies.zombie_queue_scan_size']
114
114
  )
115
115
  swarm.flush_zombies(zombie_ttl:, lock_scan_size:, queue_scan_size:)
116
116
  end
@@ -122,8 +122,8 @@ class RedisQueuedLocks::Client
122
122
  # @api public
123
123
  # @since 1.9.0
124
124
  def zombie_locks(
125
- zombie_ttl: config['swarm.flush_zombies.zombie_ttl'], # steep:ignore
126
- lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size'] # steep:ignore
125
+ zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
126
+ lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size']
127
127
  )
128
128
  swarm.zombie_locks(zombie_ttl:, lock_scan_size:)
129
129
  end
@@ -135,8 +135,8 @@ class RedisQueuedLocks::Client
135
135
  # @api ppublic
136
136
  # @since 1.9.0
137
137
  def zombie_acquirers(
138
- zombie_ttl: config['swarm.flush_zombies.zombie_ttl'], # steep:ignore
139
- lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size'] # steep:ignore
138
+ zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
139
+ lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size']
140
140
  )
141
141
  swarm.zombie_acquirers(zombie_ttl:, lock_scan_size:)
142
142
  end
@@ -146,7 +146,7 @@ class RedisQueuedLocks::Client
146
146
  #
147
147
  # @api public
148
148
  # @since 1.9.0
149
- def zombie_hosts(zombie_ttl: config['swarm.flush_zombies.zombie_ttl']) # steep:ignore
149
+ def zombie_hosts(zombie_ttl: config['swarm.flush_zombies.zombie_ttl'])
150
150
  swarm.zombie_hosts(zombie_ttl:)
151
151
  end
152
152
 
@@ -159,9 +159,10 @@ class RedisQueuedLocks::Client
159
159
  #
160
160
  # @api public
161
161
  # @since 1.9.0
162
+ # @version 1.17.0
162
163
  def zombies_info(
163
- zombie_ttl: config['swarm.flush_zombies.zombie_ttl'], # steep:ignore
164
- lock_scan_size: config['swarm.flush_zombies.zombie_ttl'] # steep:ignore
164
+ zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
165
+ lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size']
165
166
  )
166
167
  swarm.zombies_info(zombie_ttl:, lock_scan_size:)
167
168
  end
@@ -206,7 +207,28 @@ class RedisQueuedLocks::Client
206
207
  # (with timeouts, retry delays, retry limits, etc - in classic way :));
207
208
  # - `:dead_locking` - fail with deadlock exception;
208
209
  # @option read_write_mode [Symbol]
209
- # - ?
210
+ # - The lock mode (read/write lock semantics);
211
+ # - `:write` mode is used by default;
212
+ # - Supports:
213
+ # - `:write` - exclusive lock: waits for the write lock and for all read locks;
214
+ # - `:read` - shared lock: waits for the write lock only (read locks of different
215
+ # acquirers do not block each other);
216
+ # - `:queued` access strategy orders read and write lock requests in FIFO between modes
217
+ # (read lock request waits for earlier write lock requests, write lock request waits for
218
+ # earlier read and write lock requests) so writers are not starved by readers;
219
+ # - `:random` access strategy ignores lock queues (a continuous flow of readers can starve
220
+ # writers);
221
+ # - read lock specifics:
222
+ # - read lock is released (with a block of code) or expired (by `ttl`) independently
223
+ # of other read locks; `#unlock` releases all read locks and the write lock;
224
+ # - each read lock has its own data (acquirer, host, timestamp, ttl, `:meta`, reentrant
225
+ # lock data) that is returned by `#lock_info` for each reader;
226
+ # - same-process conflicts (`conflict_strategy`) work for both modes:
227
+ # - read lock request under the obtained write lock (or read lock) works as a reentrant
228
+ # lock of the obtained lock;
229
+ # - write lock request under the obtained read lock (read-to-write lock upgrade) fails with
230
+ # `RedisQueuedLocks::ConflictLockObtainError` for any conflict strategy except
231
+ # `:wait_for_lock` (it waits for the read lock expiration);
210
232
  # @option access_strategy [Symbol]
211
233
  # - The way in which the lock will be obtained;
212
234
  # - By default it uses `:queued` strategy;
@@ -309,36 +331,36 @@ class RedisQueuedLocks::Client
309
331
  #
310
332
  # @api public
311
333
  # @since 1.0.0
312
- # @version 1.13.0
334
+ # @version 1.18.0
313
335
  # rubocop:disable Metrics/MethodLength
314
336
  def lock(
315
337
  lock_name,
316
- ttl: config['default_lock_ttl'], # steep:ignore
317
- queue_ttl: config['default_queue_ttl'], # steep:ignore
318
- timeout: config['try_to_lock_timeout'], # steep:ignore
319
- timed: config['is_timed_by_default'], # steep:ignore
320
- retry_count: config['retry_count'], # steep:ignore
321
- retry_delay: config['retry_delay'], # steep:ignore
322
- retry_jitter: config['retry_jitter'], # steep:ignore
338
+ ttl: config['default_lock_ttl'],
339
+ queue_ttl: config['default_queue_ttl'],
340
+ timeout: config['try_to_lock_timeout'],
341
+ timed: config['is_timed_by_default'],
342
+ retry_count: config['retry_count'],
343
+ retry_delay: config['retry_delay'],
344
+ retry_jitter: config['retry_jitter'],
323
345
  raise_errors: false,
324
346
  fail_fast: false,
325
- conflict_strategy: config['default_conflict_strategy'], # steep:ignore
347
+ conflict_strategy: config['default_conflict_strategy'],
326
348
  read_write_mode: :write,
327
- access_strategy: config['default_access_strategy'], # steep:ignore
349
+ access_strategy: config['default_access_strategy'],
328
350
  identity: uniq_identity,
329
351
  meta: nil,
330
- detailed_acq_timeout_error: config['detailed_acq_timeout_error'], # steep:ignore
331
- logger: config['logger'], # steep:ignore
332
- log_lock_try: config['log_lock_try'], # steep:ignore
333
- instrumenter: config['instrumenter'], # steep:ignore
352
+ detailed_acq_timeout_error: config['detailed_acq_timeout_error'],
353
+ logger: config['logger'],
354
+ log_lock_try: config['log_lock_try'],
355
+ instrumenter: config['instrumenter'],
334
356
  instrument: nil,
335
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
336
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
337
- log_sampler: config['log_sampler'], # steep:ignore
357
+ log_sampling_enabled: config['log_sampling_enabled'],
358
+ log_sampling_percent: config['log_sampling_percent'],
359
+ log_sampler: config['log_sampler'],
338
360
  log_sample_this: false,
339
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
340
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
341
- instr_sampler: config['instr_sampler'], # steep:ignore
361
+ instr_sampling_enabled: config['instr_sampling_enabled'],
362
+ instr_sampling_percent: config['instr_sampling_percent'],
363
+ instr_sampler: config['instr_sampler'],
342
364
  instr_sample_this: false,
343
365
  &block
344
366
  )
@@ -388,32 +410,32 @@ class RedisQueuedLocks::Client
388
410
  def lock_series( # steep:ignore
389
411
  *lock_names,
390
412
  detailed_result: false,
391
- ttl: config['default_lock_ttl'], # steep:ignore
392
- queue_ttl: config['default_queue_ttl'], # steep:ignore
393
- timeout: config['try_to_lock_timeout'], # steep:ignore
394
- timed: config['is_timed_by_default'], # steep:ignore
395
- retry_count: config['retry_count'], # steep:ignore
396
- retry_delay: config['retry_delay'], # steep:ignore
397
- retry_jitter: config['retry_jitter'], # steep:ignore
413
+ ttl: config['default_lock_ttl'],
414
+ queue_ttl: config['default_queue_ttl'],
415
+ timeout: config['try_to_lock_timeout'],
416
+ timed: config['is_timed_by_default'],
417
+ retry_count: config['retry_count'],
418
+ retry_delay: config['retry_delay'],
419
+ retry_jitter: config['retry_jitter'],
398
420
  raise_errors: false,
399
421
  fail_fast: false,
400
- conflict_strategy: config['default_conflict_strategy'], # steep:ignore
422
+ conflict_strategy: config['default_conflict_strategy'],
401
423
  read_write_mode: :write,
402
- access_strategy: config['default_access_strategy'], # steep:ignore
424
+ access_strategy: config['default_access_strategy'],
403
425
  identity: uniq_identity,
404
426
  meta: nil,
405
- detailed_acq_timeout_error: config['detailed_acq_timeout_error'], # steep:ignore
406
- logger: config['logger'], # steep:ignore
407
- log_lock_try: config['log_lock_try'], # steep:ignore
408
- instrumenter: config['instrumenter'], # steep:ignore
427
+ detailed_acq_timeout_error: config['detailed_acq_timeout_error'],
428
+ logger: config['logger'],
429
+ log_lock_try: config['log_lock_try'],
430
+ instrumenter: config['instrumenter'],
409
431
  instrument: nil,
410
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
411
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
412
- log_sampler: config['log_sampler'], # steep:ignore
432
+ log_sampling_enabled: config['log_sampling_enabled'],
433
+ log_sampling_percent: config['log_sampling_percent'],
434
+ log_sampler: config['log_sampler'],
413
435
  log_sample_this: false,
414
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
415
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
416
- instr_sampler: config['instr_sampler'], # steep:ignore
436
+ instr_sampling_enabled: config['instr_sampling_enabled'],
437
+ instr_sampling_percent: config['instr_sampling_percent'],
438
+ instr_sampler: config['instr_sampler'],
417
439
  instr_sample_this: false,
418
440
  &block
419
441
  )
@@ -464,31 +486,31 @@ class RedisQueuedLocks::Client
464
486
  def lock_series!( # steep:ignore
465
487
  *lock_names,
466
488
  detailed_result: false,
467
- ttl: config['default_lock_ttl'], # steep:ignore
468
- queue_ttl: config['default_queue_ttl'], # steep:ignore
469
- timeout: config['try_to_lock_timeout'], # steep:ignore
470
- timed: config['is_timed_by_default'], # steep:ignore
471
- retry_count: config['retry_count'], # steep:ignore
472
- retry_delay: config['retry_delay'], # steep:ignore
473
- retry_jitter: config['retry_jitter'], # steep:ignore
489
+ ttl: config['default_lock_ttl'],
490
+ queue_ttl: config['default_queue_ttl'],
491
+ timeout: config['try_to_lock_timeout'],
492
+ timed: config['is_timed_by_default'],
493
+ retry_count: config['retry_count'],
494
+ retry_delay: config['retry_delay'],
495
+ retry_jitter: config['retry_jitter'],
474
496
  fail_fast: false,
475
- conflict_strategy: config['default_conflict_strategy'], # steep:ignore
497
+ conflict_strategy: config['default_conflict_strategy'],
476
498
  read_write_mode: :write,
477
- access_strategy: config['default_access_strategy'], # steep:ignore
499
+ access_strategy: config['default_access_strategy'],
478
500
  identity: uniq_identity,
479
501
  meta: nil,
480
- detailed_acq_timeout_error: config['detailed_acq_timeout_error'], # steep:ignore
481
- logger: config['logger'], # steep:ignore
482
- log_lock_try: config['log_lock_try'], # steep:ignore
483
- instrumenter: config['instrumenter'], # steep:ignore
502
+ detailed_acq_timeout_error: config['detailed_acq_timeout_error'],
503
+ logger: config['logger'],
504
+ log_lock_try: config['log_lock_try'],
505
+ instrumenter: config['instrumenter'],
484
506
  instrument: nil,
485
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
486
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
487
- log_sampler: config['log_sampler'], # steep:ignore
507
+ log_sampling_enabled: config['log_sampling_enabled'],
508
+ log_sampling_percent: config['log_sampling_percent'],
509
+ log_sampler: config['log_sampler'],
488
510
  log_sample_this: false,
489
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
490
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
491
- instr_sampler: config['instr_sampler'], # steep:ignore
511
+ instr_sampling_enabled: config['instr_sampling_enabled'],
512
+ instr_sampling_percent: config['instr_sampling_percent'],
513
+ instr_sampler: config['instr_sampler'],
492
514
  instr_sample_this: false,
493
515
  &block
494
516
  )
@@ -530,34 +552,34 @@ class RedisQueuedLocks::Client
530
552
  #
531
553
  # @api public
532
554
  # @since 1.0.0
533
- # @version 1.13.0
555
+ # @version 1.18.0
534
556
  def lock!(
535
557
  lock_name,
536
- ttl: config['default_lock_ttl'], # steep:ignore
537
- queue_ttl: config['default_queue_ttl'], # steep:ignore
538
- timeout: config['try_to_lock_timeout'], # steep:ignore
539
- timed: config['is_timed_by_default'], # steep:ignore
540
- retry_count: config['retry_count'], # steep:ignore
541
- retry_delay: config['retry_delay'], # steep:ignore
542
- retry_jitter: config['retry_jitter'], # steep:ignore
558
+ ttl: config['default_lock_ttl'],
559
+ queue_ttl: config['default_queue_ttl'],
560
+ timeout: config['try_to_lock_timeout'],
561
+ timed: config['is_timed_by_default'],
562
+ retry_count: config['retry_count'],
563
+ retry_delay: config['retry_delay'],
564
+ retry_jitter: config['retry_jitter'],
543
565
  fail_fast: false,
544
- conflict_strategy: config['default_conflict_strategy'], # steep:ignore
566
+ conflict_strategy: config['default_conflict_strategy'],
545
567
  read_write_mode: :write,
546
- access_strategy: config['default_access_strategy'], # steep:ignore
568
+ access_strategy: config['default_access_strategy'],
547
569
  identity: uniq_identity,
548
- instrumenter: config['instrumenter'], # steep:ignore
570
+ instrumenter: config['instrumenter'],
549
571
  meta: nil,
550
- detailed_acq_timeout_error: config['detailed_acq_timeout_error'], # steep:ignore
551
- logger: config['logger'], # steep:ignore
552
- log_lock_try: config['log_lock_try'], # steep:ignore
572
+ detailed_acq_timeout_error: config['detailed_acq_timeout_error'],
573
+ logger: config['logger'],
574
+ log_lock_try: config['log_lock_try'],
553
575
  instrument: nil,
554
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
555
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
556
- log_sampler: config['log_sampler'], # steep:ignore
576
+ log_sampling_enabled: config['log_sampling_enabled'],
577
+ log_sampling_percent: config['log_sampling_percent'],
578
+ log_sampler: config['log_sampler'],
557
579
  log_sample_this: false,
558
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
559
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
560
- instr_sampler: config['instr_sampler'], # steep:ignore
580
+ instr_sampling_enabled: config['instr_sampling_enabled'],
581
+ instr_sampling_percent: config['instr_sampling_percent'],
582
+ instr_sampler: config['instr_sampler'],
561
583
  instr_sample_this: false,
562
584
  &block
563
585
  )
@@ -595,6 +617,8 @@ class RedisQueuedLocks::Client
595
617
  end
596
618
  # rubocop:enable Metrics/MethodLength
597
619
 
620
+ # Releases the lock: the write lock, all read locks and all lock requests (write and read).
621
+ #
598
622
  # @param lock_name [String] The lock name that should be released.
599
623
  # @option logger [::Logger,#debug]
600
624
  # @option instrumenter [#notify]
@@ -614,26 +638,26 @@ class RedisQueuedLocks::Client
614
638
  # rel_time: Integer, # <millisecnds>
615
639
  # rel_key: String, # lock key
616
640
  # rel_queue: String, # lock queue
617
- # queue_res: Symbol, # :released or :nothing_to_release
618
- # lock_res: Symbol # :released or :nothing_to_release
641
+ # queue_res: Symbol, # :released or :nothing_to_release (write or read lock requests)
642
+ # lock_res: Symbol # :released or :nothing_to_release (write lock or read locks)
619
643
  # }
620
644
  # }
621
645
  #
622
646
  # @api public
623
647
  # @since 1.0.0
624
- # @version 1.6.0
648
+ # @version 1.18.0
625
649
  def unlock(
626
650
  lock_name,
627
- logger: config['logger'], # steep:ignore
628
- instrumenter: config['instrumenter'], # steep:ignore
651
+ logger: config['logger'],
652
+ instrumenter: config['instrumenter'],
629
653
  instrument: nil,
630
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
631
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
632
- log_sampler: config['log_sampler'], # steep:ignore
654
+ log_sampling_enabled: config['log_sampling_enabled'],
655
+ log_sampling_percent: config['log_sampling_percent'],
656
+ log_sampler: config['log_sampler'],
633
657
  log_sample_this: false,
634
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
635
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
636
- instr_sampler: config['instr_sampler'], # steep:ignore
658
+ instr_sampling_enabled: config['instr_sampling_enabled'],
659
+ instr_sampling_percent: config['instr_sampling_percent'],
660
+ instr_sampler: config['instr_sampler'],
637
661
  instr_sample_this: false
638
662
  )
639
663
  RedisQueuedLocks::Acquirer::ReleaseLock.release_lock(
@@ -653,38 +677,112 @@ class RedisQueuedLocks::Client
653
677
  end
654
678
  alias_method :release_lock, :unlock
655
679
 
680
+ # Releases the read lock of the current acquirer only: other read locks, the write lock and
681
+ # lock requests are not affected (`#unlock` releases the lock entirely). It is suitable for
682
+ # read locks obtained without a block of code.
683
+ #
684
+ # @param lock_name [String] The lock name whose read lock should be released.
685
+ # @option identity [String]
686
+ # Unique acquirer identity (the same as for `#lock`) that is used to calculate the current
687
+ # acquirer id (see `#current_acquirer_id`).
688
+ # @option logger [::Logger,#debug]
689
+ # @option instrumenter [#notify]
690
+ # @option instrument [NilClass,Any]
691
+ # @option log_sampling_enabled [Boolean]
692
+ # @option log_sampling_percent [Integer]
693
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
694
+ # @option log_sample_this [Boolean]
695
+ # @option instr_sampling_enabled [Boolean]
696
+ # @option instr_sampling_percent [Integer]
697
+ # @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
698
+ # @option instr_sample_this [Boolean]
699
+ # @return [Hash<Symbol,Any>]
700
+ # Format: {
701
+ # ok: true,
702
+ # result: {
703
+ # rel_time: Numeric, # <milliseconds>
704
+ # rel_key: String, # lock key
705
+ # rel_acq_id: String, # read lock acquirer
706
+ # lock_res: Symbol # :released or :nothing_to_release
707
+ # }
708
+ # }
709
+ #
710
+ # @api public
711
+ # @since 1.18.0
712
+ def unlock_read(
713
+ lock_name,
714
+ identity: uniq_identity,
715
+ logger: config['logger'],
716
+ instrumenter: config['instrumenter'],
717
+ instrument: nil,
718
+ log_sampling_enabled: config['log_sampling_enabled'],
719
+ log_sampling_percent: config['log_sampling_percent'],
720
+ log_sampler: config['log_sampler'],
721
+ log_sample_this: false,
722
+ instr_sampling_enabled: config['instr_sampling_enabled'],
723
+ instr_sampling_percent: config['instr_sampling_percent'],
724
+ instr_sampler: config['instr_sampler'],
725
+ instr_sample_this: false
726
+ )
727
+ RedisQueuedLocks::Acquirer::ReleaseReadLock.release_read_lock(
728
+ redis_client,
729
+ lock_name,
730
+ current_acquirer_id(identity:),
731
+ instrumenter,
732
+ logger,
733
+ instrument,
734
+ log_sampling_enabled,
735
+ log_sampling_percent,
736
+ log_sampler,
737
+ log_sample_this,
738
+ instr_sampling_enabled,
739
+ instr_sampling_percent,
740
+ instr_sampler,
741
+ instr_sample_this
742
+ )
743
+ end
744
+ alias_method :release_read_lock, :unlock_read
745
+
656
746
  # @param lock_name [String]
657
- # @return [Boolean]
747
+ # @return [Boolean] Is the lock obtained by a writer or by any reader.
658
748
  #
659
749
  # @api public
660
750
  # @since 1.0.0
751
+ # @version 1.18.0
661
752
  def locked?(lock_name)
662
753
  RedisQueuedLocks::Acquirer::IsLocked.locked?(redis_client, lock_name)
663
754
  end
664
755
 
665
756
  # @param lock_name [String]
666
- # @return [Boolean]
757
+ # @return [Boolean] Are there any write or read lock requests.
667
758
  #
668
759
  # @api public
669
760
  # @since 1.0.0
761
+ # @version 1.18.0
670
762
  def queued?(lock_name)
671
763
  RedisQueuedLocks::Acquirer::IsQueued.queued?(redis_client, lock_name)
672
764
  end
673
765
 
674
766
  # @param lock_name [String]
675
- # @return [Hash<String,String|Numeric>,NilClass]
767
+ # @return [Hash<String,String|Numeric|Array<Hash<String,String|Numeric>>>,NilClass]
768
+ # - write lock info or read locks info (when there is no write lock);
769
+ # - see `RedisQueuedLocks::Acquirer::LockInfo.lock_info` for details;
676
770
  #
677
771
  # @api public
678
772
  # @since 1.0.0
773
+ # @version 1.18.0
679
774
  def lock_info(lock_name)
680
775
  RedisQueuedLocks::Acquirer::LockInfo.lock_info(redis_client, lock_name)
681
776
  end
682
777
 
683
778
  # @param lock_name [String]
684
779
  # @return [Hash<String|Array<Hash<String,String|Numeric>>,NilClass]
780
+ # - write lock requests and read lock requests (when they exist);
781
+ # - see `RedisQueuedLocks::Acquirer::QueueInfo.queue_info` for details;
685
782
  #
686
783
  # @api public
687
784
  # @since 1.0.0
785
+ # @version 1.18.0
688
786
  def queue_info(lock_name)
689
787
  RedisQueuedLocks::Acquirer::QueueInfo.queue_info(redis_client, lock_name)
690
788
  end
@@ -782,6 +880,16 @@ class RedisQueuedLocks::Client
782
880
  #
783
881
  # @param lock_name [String]
784
882
  # @param milliseconds [Integer] How many milliseconds should be added.
883
+ # @option read_write_mode [Symbol]
884
+ # - `:write` (default) - extends the write lock (the method description above);
885
+ # - `:read` - extends the read lock of the current acquirer only (an expired read lock or
886
+ # a read lock replaced by a writer is not extended);
887
+ # @option all_read_locks [Boolean]
888
+ # `:read` mode only (ignored for `:write` mode): extends all live read locks of the lock
889
+ # instead of the read lock of the current acquirer (`false` by default).
890
+ # @option identity [String]
891
+ # Unique acquirer identity (the same as for `#lock`) that is used to calculate the current
892
+ # acquirer id for `:read` mode (see `#current_acquirer_id`).
785
893
  # @option logger [::Logger,#debug]
786
894
  # @option instrumenter [#notify] See `config['instrumenter']` docs for details.
787
895
  # @option instrument [NilClass,Any]
@@ -793,32 +901,40 @@ class RedisQueuedLocks::Client
793
901
  # @option instr_sampling_percent [Integer]
794
902
  # @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
795
903
  # @option instr_sample_this [Boolean]
796
- # @return [Hash<Symbol,Boolean|Symbol>]
797
- # - { ok: true, result: :ttl_extended }
904
+ # @return [Hash<Symbol,Boolean|Symbol|Hash<Symbol,Integer>>]
905
+ # - { ok: true, result: { extended_locks_count: Integer } } - the number of extended locks:
906
+ # `1` for the write lock and for the read lock of the current acquirer,
907
+ # the number of extended read locks for `all_read_locks: true`;
798
908
  # - { ok: false, result: :async_expire_or_no_lock }
799
909
  #
800
910
  # @api public
801
911
  # @since 1.0.0
802
- # @version 1.6.0
912
+ # @version 1.18.0
803
913
  def extend_lock_ttl(
804
914
  lock_name,
805
915
  milliseconds,
806
- logger: config['logger'], # steep:ignore
807
- instrumenter: config['instrumenter'], # steep:ignore
916
+ read_write_mode: :write,
917
+ all_read_locks: false,
918
+ identity: uniq_identity,
919
+ logger: config['logger'],
920
+ instrumenter: config['instrumenter'],
808
921
  instrument: nil,
809
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
810
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
811
- log_sampler: config['log_sampler'], # steep:ignore
922
+ log_sampling_enabled: config['log_sampling_enabled'],
923
+ log_sampling_percent: config['log_sampling_percent'],
924
+ log_sampler: config['log_sampler'],
812
925
  log_sample_this: false,
813
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
814
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
815
- instr_sampler: config['instr_sampler'], # steep:ignore
926
+ instr_sampling_enabled: config['instr_sampling_enabled'],
927
+ instr_sampling_percent: config['instr_sampling_percent'],
928
+ instr_sampler: config['instr_sampler'],
816
929
  instr_sample_this: false
817
930
  )
818
931
  RedisQueuedLocks::Acquirer::ExtendLockTTL.extend_lock_ttl(
819
932
  redis_client,
820
933
  lock_name,
821
934
  milliseconds,
935
+ read_write_mode,
936
+ all_read_locks,
937
+ current_acquirer_id(identity:),
822
938
  logger,
823
939
  instrumenter,
824
940
  instrument,
@@ -857,17 +973,17 @@ class RedisQueuedLocks::Client
857
973
  # @since 1.0.0
858
974
  # @version 1.6.0
859
975
  def clear_locks(
860
- batch_size: config['lock_release_batch_size'], # steep:ignore
861
- logger: config['logger'], # steep:ignore
862
- instrumenter: config['instrumenter'], # steep:ignore
976
+ batch_size: config['lock_release_batch_size'],
977
+ logger: config['logger'],
978
+ instrumenter: config['instrumenter'],
863
979
  instrument: nil,
864
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
865
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
866
- log_sampler: config['log_sampler'], # steep:ignore
980
+ log_sampling_enabled: config['log_sampling_enabled'],
981
+ log_sampling_percent: config['log_sampling_percent'],
982
+ log_sampler: config['log_sampler'],
867
983
  log_sample_this: false,
868
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
869
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
870
- instr_sampler: config['instr_sampler'], # steep:ignore
984
+ instr_sampling_enabled: config['instr_sampling_enabled'],
985
+ instr_sampling_percent: config['instr_sampling_percent'],
986
+ instr_sampler: config['instr_sampler'],
871
987
  instr_sample_this: false
872
988
  )
873
989
  RedisQueuedLocks::Acquirer::ReleaseAllLocks.release_all_locks(
@@ -927,7 +1043,7 @@ class RedisQueuedLocks::Client
927
1043
  # @example Release locks of a different host/acquirer:
928
1044
  # client.clear_locks_of(
929
1045
  # host_id: "rql:hst:62681/2016/2032/b30ec5e4bea10512",
930
- # acquirer_id: "ral:acq:62681/2016/2024/2032/b30ec5e4bea10512"
1046
+ # acquirer_id: "rql:acq:62681/2016/2024/2032/b30ec5e4bea10512"
931
1047
  # )
932
1048
  #
933
1049
  # @see #clear_current_locks
@@ -944,18 +1060,18 @@ class RedisQueuedLocks::Client
944
1060
  def clear_locks_of(
945
1061
  host_id:,
946
1062
  acquirer_id:,
947
- lock_scan_size: config['clear_locks_of__lock_scan_size'], # steep:ignore
1063
+ lock_scan_size: config['clear_locks_of__lock_scan_size'],
948
1064
  queue_scan_size: config['clear_locks_of__queue_scan_size'], # steep:ingore
949
- logger: config['logger'], # steep:ignore
950
- instrumenter: config['instrumenter'], # steep:ignore
1065
+ logger: config['logger'],
1066
+ instrumenter: config['instrumenter'],
951
1067
  instrument: nil,
952
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
953
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
954
- log_sampler: config['log_sampler'], # steep:ignore
1068
+ log_sampling_enabled: config['log_sampling_enabled'],
1069
+ log_sampling_percent: config['log_sampling_percent'],
1070
+ log_sampler: config['log_sampler'],
955
1071
  log_sample_this: false,
956
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
957
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
958
- instr_sampler: config['instr_sampler'], # steep:ignore
1072
+ instr_sampling_enabled: config['instr_sampling_enabled'],
1073
+ instr_sampling_percent: config['instr_sampling_percent'],
1074
+ instr_sampler: config['instr_sampler'],
959
1075
  instr_sample_this: false
960
1076
  )
961
1077
  RedisQueuedLocks::Acquirer::ReleaseLocksOf.release_locks_of(
@@ -1002,18 +1118,18 @@ class RedisQueuedLocks::Client
1002
1118
  # @api public
1003
1119
  # @since 1.14.0
1004
1120
  def clear_current_locks(
1005
- lock_scan_size: config['clear_locks_of__lock_scan_size'], # steep:ignore
1121
+ lock_scan_size: config['clear_locks_of__lock_scan_size'],
1006
1122
  queue_scan_size: config['clear_locks_of__queue_scan_size'], # steep:ingore
1007
- logger: config['logger'], # steep:ignore
1008
- instrumenter: config['instrumenter'], # steep:ignore
1123
+ logger: config['logger'],
1124
+ instrumenter: config['instrumenter'],
1009
1125
  instrument: nil,
1010
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
1011
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
1012
- log_sampler: config['log_sampler'], # steep:ignore
1126
+ log_sampling_enabled: config['log_sampling_enabled'],
1127
+ log_sampling_percent: config['log_sampling_percent'],
1128
+ log_sampler: config['log_sampler'],
1013
1129
  log_sample_this: false,
1014
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
1015
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
1016
- instr_sampler: config['instr_sampler'], # steep:ignore
1130
+ instr_sampling_enabled: config['instr_sampling_enabled'],
1131
+ instr_sampling_percent: config['instr_sampling_percent'],
1132
+ instr_sampler: config['instr_sampler'],
1017
1133
  instr_sample_this: false
1018
1134
  )
1019
1135
  clear_locks_of(
@@ -1055,7 +1171,7 @@ class RedisQueuedLocks::Client
1055
1171
  #
1056
1172
  # @api public
1057
1173
  # @since 1.0.0
1058
- def locks(scan_size: config['key_extraction_batch_size'], with_info: false) # steep:ignore
1174
+ def locks(scan_size: config['key_extraction_batch_size'], with_info: false)
1059
1175
  RedisQueuedLocks::Acquirer::Locks.locks(redis_client, scan_size:, with_info:)
1060
1176
  end
1061
1177
 
@@ -1086,7 +1202,7 @@ class RedisQueuedLocks::Client
1086
1202
  #
1087
1203
  # @api public
1088
1204
  # @since 1.0.0
1089
- def queues(scan_size: config['key_extraction_batch_size'], with_info: false) # steep:ignore
1205
+ def queues(scan_size: config['key_extraction_batch_size'], with_info: false)
1090
1206
  RedisQueuedLocks::Acquirer::Queues.queues(redis_client, scan_size:, with_info:)
1091
1207
  end
1092
1208
 
@@ -1097,7 +1213,7 @@ class RedisQueuedLocks::Client
1097
1213
  #
1098
1214
  # @api public
1099
1215
  # @since 1.0.0
1100
- def queues_info(scan_size: config['key_extraction_batch_size']) # steep:ignore
1216
+ def queues_info(scan_size: config['key_extraction_batch_size'])
1101
1217
  queues(scan_size:, with_info: true)
1102
1218
  end
1103
1219
 
@@ -1106,7 +1222,7 @@ class RedisQueuedLocks::Client
1106
1222
  #
1107
1223
  # @api public
1108
1224
  # @since 1.0.0
1109
- def keys(scan_size: config['key_extraction_batch_size']) # steep:ignore
1225
+ def keys(scan_size: config['key_extraction_batch_size'])
1110
1226
  RedisQueuedLocks::Acquirer::Keys.keys(redis_client, scan_size:)
1111
1227
  end
1112
1228
 
@@ -1135,18 +1251,18 @@ class RedisQueuedLocks::Client
1135
1251
  # @since 1.0.0
1136
1252
  # @version 1.6.0
1137
1253
  def clear_dead_requests(
1138
- dead_ttl: config['dead_request_ttl'], # steep:ignore
1139
- scan_size: config['lock_release_batch_size'], # steep:ignore
1140
- logger: config['logger'], # steep:ignore
1141
- instrumenter: config['instrumenter'], # steep:ignore
1254
+ dead_ttl: config['dead_request_ttl'],
1255
+ scan_size: config['lock_release_batch_size'],
1256
+ logger: config['logger'],
1257
+ instrumenter: config['instrumenter'],
1142
1258
  instrument: nil,
1143
- log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
1144
- log_sampling_percent: config['log_sampling_percent'], # steep:ignore
1145
- log_sampler: config['log_sampler'], # steep:ignore
1259
+ log_sampling_enabled: config['log_sampling_enabled'],
1260
+ log_sampling_percent: config['log_sampling_percent'],
1261
+ log_sampler: config['log_sampler'],
1146
1262
  log_sample_this: false,
1147
- instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
1148
- instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
1149
- instr_sampler: config['instr_sampler'], # steep:ignore
1263
+ instr_sampling_enabled: config['instr_sampling_enabled'],
1264
+ instr_sampling_percent: config['instr_sampling_percent'],
1265
+ instr_sampler: config['instr_sampler'],
1150
1266
  instr_sample_this: false
1151
1267
  )
1152
1268
  RedisQueuedLocks::Acquirer::ClearDeadRequests.clear_dead_requests(