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
@@ -19,8 +19,9 @@
19
19
  # - write lock request should be the first in the write lock queue (the classic lock queue)
20
20
  # and should wait for all earlier read lock requests;
21
21
  # - read lock request should wait for all earlier write lock requests;
22
- # - `:random` access strategy ignores both queues (no ordering between modes, so
23
- # a continuous flow of readers can starve writers);
22
+ # - `:random` access strategy ignores the order of both queues (no ordering between modes, so
23
+ # a continuous flow of readers can starve writers); its own lock request is queued like any
24
+ # lock request (`:queued` lock requests wait for it);
24
25
  #
25
26
  # Lock index (`RedisQueuedLocks::Resource::LOCK_INDEX_KEY`): only with the enabled lock index
26
27
  # (`lock_index_enabled`, `config['lock_index_enabled']`); with the disabled lock index (default)
@@ -37,9 +38,21 @@
37
38
  # - each queueing drops the expired lock names from the index (`ZREMRANGEBYSCORE`): the index
38
39
  # is maintained by the lock acquirement itself (no background jobs);
39
40
  #
41
+ # Lock request priorities (`priority`, see `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`):
42
+ # - the position of the lock request (the score) is the enqueue time shifted by the priority
43
+ # band: requests of a higher priority are ahead of the requests of a lower priority in both
44
+ # queues (FIFO inside the same priority), so the queue heads and the order between modes
45
+ # (`find_opposite_request_ahead`) work with the positions as is;
46
+ # - dead lock requests are dropped per priority band (each band has its own time barrier):
47
+ # the band of the current request before the request is queued and the queue heads are read
48
+ # (both queues); a dead request of another band is dropped when it blocks the current request
49
+ # (the queue head or the earlier request of the opposite mode): its band and its enqueue time
50
+ # are decoded from its position, so the cost of an attempt does not depend on the number of
51
+ # priorities (see `drop_dead_requests_ahead`);
52
+ #
40
53
  # @api private
41
54
  # @since 1.0.0
42
- # @version 1.20.0
55
+ # @version 1.22.0
43
56
  # rubocop:disable Metrics/ModuleLength
44
57
  module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
45
58
  require_relative 'try_to_lock/log_visitor'
@@ -101,8 +114,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
101
114
  # @since 1.20.0
102
115
  LOCK_STATE_QUEUE_REPLIES_AT = 3
103
116
 
104
- # Position of the first request of the request queue (`ZRANGE`) in the replies of the lock
105
- # request queueing commands (`queue_lock_request`), relative to their first reply.
117
+ # Position of the first request of the request queue (`ZRANGE WITHSCORES`) in the replies of
118
+ # the lock request queueing commands (`queue_lock_request`), relative to their first reply.
106
119
  #
107
120
  # @return [Integer]
108
121
  #
@@ -120,6 +133,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
120
133
  # @since 1.20.0
121
134
  OPPOSITE_QUEUE_HEAD_REPLY = 4
122
135
 
136
+ # The maximum number of the round trips of one call of `drop_dead_requests_ahead` (the attempt or
137
+ # its pre-check; each round trip drops the dead blocking requests, the head of the write lock
138
+ # queue and the earlier request of the opposite mode, with the dead requests of their priority
139
+ # bands): the attempt fails when more dead lock requests block it (the next attempts drop them).
140
+ #
141
+ # @return [Integer]
142
+ #
143
+ # @api private
144
+ # @since 1.22.0
145
+ MAX_DEAD_REQUEST_REMOVALS = 5
146
+
123
147
  # @param redis [RedisClient]
124
148
  # @param logger [::Logger,#debug]
125
149
  # @param log_lock_try [Boolean]
@@ -135,6 +159,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
135
159
  # @param acquirer_id [String]
136
160
  # @param host_id [String]
137
161
  # @param acquirer_position [Numeric]
162
+ # The position of the lock request (the score: the enqueue time shifted by the priority band).
163
+ # @param priority [Integer]
164
+ # The lock request priority (the band of `acquirer_position`).
138
165
  # @param ttl [Integer]
139
166
  # @param queue_ttl [Integer]
140
167
  # @param lock_index_enabled [Boolean]
@@ -154,7 +181,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
154
181
  #
155
182
  # @api private
156
183
  # @since 1.0.0
157
- # @version 1.21.0
184
+ # @version 1.22.0
158
185
  # rubocop:disable Metrics/MethodLength
159
186
  def try_to_lock(
160
187
  redis,
@@ -170,6 +197,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
170
197
  acquirer_id,
171
198
  host_id,
172
199
  acquirer_position,
200
+ priority,
173
201
  ttl,
174
202
  queue_ttl,
175
203
  lock_index_enabled,
@@ -234,7 +262,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
234
262
  inter_result = precheck_lock(
235
263
  rconn, logger, log_sampled, log_lock_try, lock_key, read_write_mode, lock_readers_key,
236
264
  request_queue, opposite_request_queue, lock_name, acquirer_id, host_id,
237
- acquirer_position, acquirer_dead_score, queue_ttl, lock_index_enabled,
265
+ acquirer_position, priority, acquirer_dead_score, queue_ttl, lock_index_enabled,
238
266
  lock_index_grace_period, fail_fast, access_strategy
239
267
  )
240
268
  next if inter_result
@@ -255,14 +283,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
255
283
  # - read lock expiration of the current acquirer;
256
284
  # - the longest read lock (the last one in the readers registry): write lock requests
257
285
  # only (read lock requests do not wait for other readers);
258
- # - (eager queueing) the lock request queueing: Steps 1, 2.1, 3 (`queue_lock_request`);
286
+ # - (eager queueing) the lock request queueing: Steps 2.1, 1, 3 (`queue_lock_request`);
259
287
  # NOTE: replies are read by their positions: see "pipeline layout" in `pipeline_lock_state`
260
288
  # @type var lock_state: Array[untyped]
261
289
  lock_state = rconn.pipelined do |pipeline|
262
290
  pipeline_lock_state(
263
291
  pipeline, lock_key, lock_readers_key, acquirer_id, read_mode, eager_queueing,
264
- request_queue, opposite_request_queue, acquirer_position, acquirer_dead_score,
265
- lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
292
+ request_queue, opposite_request_queue, acquirer_position, priority,
293
+ acquirer_dead_score, lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
266
294
  )
267
295
  end
268
296
  # @type var current_lock_obtainer: String?
@@ -493,11 +521,12 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
493
521
  # Fast-Step X1: lock is already obtained. fail fast leads to "no try".
494
522
  inter_result = :fail_fast_no_try
495
523
  else
496
- # Step 1: add an acquirer to the lock acquirement queue
497
524
  # Step 2.1: drop expired acquirers from the lock queues
525
+ # Step 1: add an acquirer to the lock acquirement queue
498
526
  # Step 3: get the actual acquirer waiting in the queue
499
527
  # - one round trip for all of them (see `queue_lock_request`);
500
528
  # - eager queueing: already done in the lock state round trip (Step 0.1);
529
+ # - Step 2.2 (priorities) runs after them (see below);
501
530
  # @type var queue_replies: Array[untyped]
502
531
  queue_replies =
503
532
  if eager_queueing
@@ -506,30 +535,51 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
506
535
  rconn.pipelined do |pipeline|
507
536
  queue_lock_request(
508
537
  pipeline, request_queue, opposite_request_queue,
509
- acquirer_position, acquirer_id, acquirer_dead_score,
538
+ acquirer_position, priority, acquirer_id, acquirer_dead_score,
510
539
  lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
511
540
  )
512
541
  end
513
542
  end
514
543
  # NOTE: the position of the first queueing reply (see "pipeline layout" in Step 0.1)
515
544
  queue_replies_at = eager_queueing ? LOCK_STATE_QUEUE_REPLIES_AT : 0
516
- # @type var waiting_acquirer: String?
517
- waiting_acquirer = queue_replies[queue_replies_at + QUEUE_HEAD_REPLY].first
545
+ # @type var waiting_request: [String, Float]?
546
+ waiting_request = queue_replies[queue_replies_at + QUEUE_HEAD_REPLY].first
518
547
  # @type var opposite_waiting_request: [String, Float]?
519
548
  opposite_waiting_request =
520
549
  queue_replies[queue_replies_at + OPPOSITE_QUEUE_HEAD_REPLY].first
521
550
 
522
551
  LogVisitor.acq_added_to_queue(
523
552
  logger, log_sampled, log_lock_try, lock_key,
524
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
553
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, priority
525
554
  )
526
555
  LogVisitor.remove_expired_acqs(
527
556
  logger, log_sampled, log_lock_try, lock_key,
528
557
  queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
529
558
  )
559
+
560
+ # Step 2.2: (priorities) drop the dead lock requests of other priority bands that block
561
+ # the current one (the queue head or the earlier request of the opposite mode) and get
562
+ # the actual queue heads (no round trips while no dead request blocks the current one);
563
+ actual_queue_heads = drop_dead_requests_ahead(
564
+ rconn, read_write_mode, request_queue, opposite_request_queue, acquirer_id,
565
+ acquirer_position, acquirer_dead_score, access_strategy, waiting_request,
566
+ opposite_waiting_request
567
+ )
568
+ if actual_queue_heads
569
+ waiting_request, opposite_waiting_request, dropped_requests = actual_queue_heads
570
+ log_dropped_requests(
571
+ logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
572
+ access_strategy, read_write_mode, dropped_requests
573
+ )
574
+ end
575
+ waiting_acquirer = waiting_request ? waiting_request[0] : nil
576
+ # NOTE: the priority of the first request is decoded for logs only
577
+ waiting_priority = logged_request_priority(log_sampled, log_lock_try, waiting_request)
578
+
530
579
  LogVisitor.get_first_from_queue(
531
580
  logger, log_sampled, log_lock_try, lock_key,
532
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer
581
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer,
582
+ waiting_priority
533
583
  )
534
584
 
535
585
  # Step 3.1 (RW) + Steps PRE-4.x, ROLLBACK 1.1, 1.2, 2, 3: the obstacle to the lock
@@ -549,7 +599,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
549
599
  log_lock_obstacle(
550
600
  rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
551
601
  acquirer_id, host_id, access_strategy, read_write_mode,
552
- waiting_acquirer, opposite_request_ahead, current_lock_obtainer
602
+ waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
553
603
  )
554
604
  inter_result = obstacle
555
605
  elsif read_mode
@@ -829,24 +879,27 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
829
879
 
830
880
  private
831
881
 
832
- # Queues the lock request and reads the lock queue state (Steps 1, 2.1, 3 and 3.2-3.4 of
882
+ # Queues the lock request and reads the lock queue state (Steps 2.1, 1, 3 and 3.2-3.4 of
833
883
  # the attempt): the commands are added to the given pipeline (one round trip with other
834
884
  # pipelined commands).
835
- # Replies (positions relative to the first one): [0] ZADD NX, [1] ZREMRANGEBYSCORE (request
836
- # queue), [2] ZREMRANGEBYSCORE (opposite request queue), [QUEUE_HEAD_REPLY] ZRANGE (the first
837
- # request of the request queue), [OPPOSITE_QUEUE_HEAD_REPLY] ZRANGE WITHSCORES (the first
838
- # request of the opposite request queue); the enabled lock index only: [5] PEXPIRE NX, [6]
839
- # PEXPIRE GT (the request queue TTL), [7] ZADD GT (the lock index), [8] ZREMRANGEBYSCORE
840
- # (expired lock names of the lock index). Update these constants when the commands change
841
- # (new commands go to the end: the replies are not read after `OPPOSITE_QUEUE_HEAD_REPLY`, so
842
- # the conditional lock index commands do not shift the read replies).
885
+ # Replies (positions relative to the first one): [0] ZREMRANGEBYSCORE (request queue, the
886
+ # priority band of the request), [1] ZREMRANGEBYSCORE (opposite request queue, the priority band
887
+ # of the request), [2] ZADD NX, [QUEUE_HEAD_REPLY] ZRANGE WITHSCORES (the first request of
888
+ # the request queue), [OPPOSITE_QUEUE_HEAD_REPLY] ZRANGE WITHSCORES (the first request of
889
+ # the opposite request queue); the enabled lock index only: [5] PEXPIRE NX, [6] PEXPIRE GT (the
890
+ # request queue TTL), [7] ZADD GT (the lock index), [8] ZREMRANGEBYSCORE (expired lock names of
891
+ # the lock index). Update these constants when the commands change (new commands go to the end:
892
+ # the replies are not read after `OPPOSITE_QUEUE_HEAD_REPLY`, so the conditional lock index
893
+ # commands do not shift the read replies).
843
894
  #
844
895
  # @param pipeline [RedisClient::Pipeline]
845
896
  # @param request_queue [String] Queue of the requests of the requested lock mode.
846
897
  # @param opposite_request_queue [String] Queue of the requests of the opposite lock mode.
847
- # @param acquirer_position [Numeric]
898
+ # @param acquirer_position [Numeric] The enqueue time shifted by the priority band.
899
+ # @param priority [Integer] The priority band of `acquirer_position`.
848
900
  # @param acquirer_id [String]
849
- # @param acquirer_dead_score [Float] Requests queued before this score are expired.
901
+ # @param acquirer_dead_score [Float]
902
+ # Requests queued before this time are expired (the barrier of the default priority band).
850
903
  # @param lock_name [String] The lock name (the member of the lock index).
851
904
  # @param queue_ttl [Integer] Lifetime of the lock request (in seconds).
852
905
  # @param lock_index_enabled [Boolean] Send the lock index commands (Steps 3.2-3.4).
@@ -855,11 +908,13 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
855
908
  #
856
909
  # @api private
857
910
  # @since 1.20.0
911
+ # @version 1.22.0
858
912
  def queue_lock_request(
859
913
  pipeline,
860
914
  request_queue,
861
915
  opposite_request_queue,
862
916
  acquirer_position,
917
+ priority,
863
918
  acquirer_id,
864
919
  acquirer_dead_score,
865
920
  lock_name,
@@ -867,6 +922,22 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
867
922
  lock_index_enabled,
868
923
  lock_index_grace_period
869
924
  )
925
+ # Step 2.1: drop expired acquirers from the lock queues
926
+ # - (RW) both queues are cleared: an expired request of the opposite mode should not
927
+ # block the current request forever;
928
+ # - (priorities) the requests of the priority band of the current request (the band has its
929
+ # own time barrier, see `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`); a dead request of
930
+ # another band is dropped when it blocks the current request (`drop_dead_requests_ahead`),
931
+ # requests of lower priorities are never ahead of the current request;
932
+ # - before the queueing (Step 1): the old (dead) position of the requeued request (its
933
+ # position is reset after `queue_ttl`, see `dead_score_reached?`) is dropped first, so
934
+ # `ZADD NX` adds its new position in the same round trip (after `ZADD NX` the old position
935
+ # would be kept by `NX` and dropped by the removal: the request would be missing in the
936
+ # queue in this attempt);
937
+ pipeline_dead_requests_removal(
938
+ pipeline, request_queue, opposite_request_queue, acquirer_dead_score, priority
939
+ )
940
+
870
941
  # Step 1: add an acquirer to the lock acquirement queue
871
942
  # NOTE:
872
943
  # 'NX' means "Only add new elements. Don't update already existing elements."
@@ -876,15 +947,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
876
947
  # not in the queue now>>;
877
948
  pipeline.call('ZADD', request_queue, 'NX', acquirer_position, acquirer_id)
878
949
 
879
- # Step 2.1: drop expired acquirers from the lock queues
880
- # - (RW) both queues are cleared: an expired request of the opposite mode should not
881
- # block the current request forever;
882
- pipeline.call('ZREMRANGEBYSCORE', request_queue, '-inf', acquirer_dead_score)
883
- pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, '-inf', acquirer_dead_score)
884
-
885
- # Step 3: get the actual acquirer waiting in the queue
950
+ # Step 3: get the actual acquirer waiting in the queue (with its position: the band and
951
+ # the liveness of the queue head are decoded from it, see `drop_dead_requests_ahead`)
886
952
  # - (RW) and the first request of the opposite mode;
887
- pipeline.call('ZRANGE', request_queue, '0', '0')
953
+ pipeline.call('ZRANGE', request_queue, '0', '0', 'WITHSCORES')
888
954
  pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
889
955
 
890
956
  # NOTE: the disabled lock index: no lock index commands, lock request queues have no TTL
@@ -921,6 +987,34 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
921
987
  )
922
988
  end
923
989
 
990
+ # Adds the removal of the dead lock requests of the given priority band (both lock queues) to
991
+ # the given pipeline (see `queue_lock_request` and `drop_dead_requests_ahead`).
992
+ #
993
+ # @param pipeline [RedisClient::Pipeline]
994
+ # @param request_queue [String]
995
+ # @param opposite_request_queue [String]
996
+ # @param acquirer_dead_score [Float]
997
+ # Requests queued before this time are expired (the barrier of the default priority band).
998
+ # @param priority [Integer]
999
+ # The priority band (see `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`).
1000
+ # @return [void]
1001
+ #
1002
+ # @api private
1003
+ # @since 1.22.0
1004
+ def pipeline_dead_requests_removal(
1005
+ pipeline,
1006
+ request_queue,
1007
+ opposite_request_queue,
1008
+ acquirer_dead_score,
1009
+ priority
1010
+ )
1011
+ band_floor = RedisQueuedLocks::Resource.lock_priority_band_floor(priority)
1012
+ band_dead_score =
1013
+ RedisQueuedLocks::Resource.lock_priority_band_dead_score(acquirer_dead_score, priority)
1014
+ pipeline.call('ZREMRANGEBYSCORE', request_queue, band_floor, band_dead_score)
1015
+ pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, band_floor, band_dead_score)
1016
+ end
1017
+
924
1018
  # Adds the lock state commands to the given pipeline (the lock state of the attempt and of
925
1019
  # the pre-check: one round trip).
926
1020
  #
@@ -949,6 +1043,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
949
1043
  # @param request_queue [String]
950
1044
  # @param opposite_request_queue [String]
951
1045
  # @param acquirer_position [Numeric]
1046
+ # @param priority [Integer]
952
1047
  # @param acquirer_dead_score [Float]
953
1048
  # @param lock_name [String] The lock name (the member of the lock index).
954
1049
  # @param queue_ttl [Integer] In seconds.
@@ -958,6 +1053,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
958
1053
  #
959
1054
  # @api private
960
1055
  # @since 1.20.0
1056
+ # @version 1.22.0
961
1057
  def pipeline_lock_state(
962
1058
  pipeline,
963
1059
  lock_key,
@@ -968,6 +1064,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
968
1064
  request_queue,
969
1065
  opposite_request_queue,
970
1066
  acquirer_position,
1067
+ priority,
971
1068
  acquirer_dead_score,
972
1069
  lock_name,
973
1070
  queue_ttl,
@@ -981,7 +1078,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
981
1078
  if queueing
982
1079
  queue_lock_request(
983
1080
  pipeline, request_queue, opposite_request_queue,
984
- acquirer_position, acquirer_id, acquirer_dead_score,
1081
+ acquirer_position, priority, acquirer_id, acquirer_dead_score,
985
1082
  lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
986
1083
  )
987
1084
  end
@@ -991,6 +1088,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
991
1088
 
992
1089
  # (RW) The request of the opposite mode that should obtain the lock earlier
993
1090
  # - FIFO between modes (`:queued` access strategy only);
1091
+ # - (priorities) positions are shifted by the priority bands: a request of a higher priority
1092
+ # is ahead of any request of a lower priority, requests of the same priority are FIFO;
994
1093
  # - equal positions are ordered by acquirer ids;
995
1094
  #
996
1095
  # @param access_strategy [Symbol]
@@ -1082,12 +1181,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1082
1181
  # @param access_strategy [Symbol]
1083
1182
  # @param read_write_mode [Symbol]
1084
1183
  # @param waiting_acquirer [String,NilClass]
1085
- # @param opposite_request_ahead [String,NilClass]
1184
+ # @param waiting_priority [Integer,NilClass]
1185
+ # The priority of the first request (decoded for logs only: `nil` without logs).
1186
+ # @param opposite_waiting_request [Array<String,Float>,NilClass]
1187
+ # The first request of the opposite queue (the request ahead of the `*_request_is_ahead`
1188
+ # obstacles, see `find_opposite_request_ahead`).
1086
1189
  # @param current_lock_obtainer [String,NilClass]
1087
1190
  # @return [void]
1088
1191
  #
1089
1192
  # @api private
1090
1193
  # @since 1.20.0
1194
+ # @version 1.22.0
1091
1195
  # rubocop:disable Metrics/MethodLength
1092
1196
  def log_lock_obstacle(
1093
1197
  rconn,
@@ -1102,7 +1206,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1102
1206
  access_strategy,
1103
1207
  read_write_mode,
1104
1208
  waiting_acquirer,
1105
- opposite_request_ahead,
1209
+ waiting_priority,
1210
+ opposite_waiting_request,
1106
1211
  current_lock_obtainer
1107
1212
  )
1108
1213
  case obstacle
@@ -1115,44 +1220,270 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1115
1220
  LogVisitor.exit__no_first(
1116
1221
  logger, log_sampled, log_lock_try, lock_key,
1117
1222
  queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer,
1223
+ waiting_priority,
1118
1224
  # NOTE: lock data is extracted for logs only
1119
1225
  ((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
1120
1226
  )
1121
- when :write_request_is_ahead
1122
- LogVisitor.exit__write_request_ahead(
1123
- logger, log_sampled, log_lock_try, lock_key,
1124
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
1125
- opposite_request_ahead #: String
1126
- )
1127
- when :read_request_is_ahead
1128
- LogVisitor.exit__read_request_ahead(
1129
- logger, log_sampled, log_lock_try, lock_key,
1130
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
1131
- opposite_request_ahead #: String
1132
- )
1227
+ when :write_request_is_ahead, :read_request_is_ahead
1228
+ ahead_request = opposite_waiting_request #: [String, Float]
1229
+ # NOTE: the priority of the request ahead is decoded for logs only
1230
+ ahead_priority = logged_request_priority(log_sampled, log_lock_try, ahead_request)
1231
+ if obstacle == :write_request_is_ahead
1232
+ LogVisitor.exit__write_request_ahead(
1233
+ logger, log_sampled, log_lock_try, lock_key,
1234
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
1235
+ ahead_request[0], ahead_priority
1236
+ )
1237
+ else
1238
+ LogVisitor.exit__read_request_ahead(
1239
+ logger, log_sampled, log_lock_try, lock_key,
1240
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
1241
+ ahead_request[0], ahead_priority
1242
+ )
1243
+ end
1133
1244
  when :lock_is_still_acquired
1134
1245
  LogVisitor.exit__lock_still_obtained(
1135
1246
  logger, log_sampled, log_lock_try, lock_key,
1136
1247
  queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
1137
- waiting_acquirer, current_lock_obtainer, #: String
1248
+ waiting_acquirer, waiting_priority,
1249
+ current_lock_obtainer, #: String
1138
1250
  # NOTE: lock data is extracted for logs only
1139
1251
  ((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
1140
1252
  )
1141
1253
  when :read_lock_is_still_acquired
1142
1254
  LogVisitor.exit__read_lock_still_obtained(
1143
1255
  logger, log_sampled, log_lock_try, lock_key,
1144
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer
1256
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer,
1257
+ waiting_priority
1145
1258
  )
1146
1259
  end
1147
1260
  end
1148
1261
  # rubocop:enable Metrics/MethodLength
1149
1262
 
1263
+ # (priorities) The priority of the lock request for the try-lock logs (the first request of
1264
+ # the queue, the request ahead): decoded only when the try-lock logs are written (log-only values
1265
+ # are never computed without the logs: `log_sampled && log_lock_try`).
1266
+ #
1267
+ # @param log_sampled [Boolean]
1268
+ # @param log_lock_try [Boolean]
1269
+ # @param lock_request [Array<String,Float>,NilClass]
1270
+ # The acquirer and the position of the lock request.
1271
+ # @return [Integer,NilClass] `nil` without the try-lock logs or without the lock request.
1272
+ #
1273
+ # @api private
1274
+ # @since 1.22.0
1275
+ def logged_request_priority(log_sampled, log_lock_try, lock_request)
1276
+ return unless log_sampled && log_lock_try && lock_request
1277
+
1278
+ RedisQueuedLocks::Resource.lock_request_priority(lock_request[1])
1279
+ end
1280
+
1281
+ # (priorities) Drops the dead lock requests of other priority bands that block the current lock
1282
+ # request (`:queued` access strategy: the head of the write lock queue for write lock requests,
1283
+ # the earlier lock request of the opposite mode for both modes) and reads the actual queue heads
1284
+ # (Step 2.2 of the attempt and of its pre-check):
1285
+ # - the dead lock requests of the band of the current request are dropped by the queueing
1286
+ # (Step 2.1, before the queue heads are read) and requests of lower priorities are never
1287
+ # ahead of it: only a request of a higher priority can be a dead blocking request;
1288
+ # - the band and the enqueue time of the blocking request are decoded from its position (see
1289
+ # `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`): no Redis commands to check the liveness,
1290
+ # any band is recognized (the priority range of the current RQL version is not used), the
1291
+ # time barrier is the barrier of the current request (`acquirer_dead_score`: its `queue_ttl`);
1292
+ # - the dead blocking requests (the head of the write lock queue and the earlier request of the
1293
+ # opposite mode) are dropped with all dead requests of their bands (both queues) in one round
1294
+ # trip with the reading of the actual queue heads; no round trips while no dead request
1295
+ # blocks the current one (the lock state does not matter: the attempt drops the dead blocking
1296
+ # requests even when the lock is obtained by another acquirer);
1297
+ # - up to `MAX_DEAD_REQUEST_REMOVALS` round trips (one or two bands each) per call;
1298
+ # - (infinite positions) RQL never writes them (manual writes of the queues, see
1299
+ # `RedisQueuedLocks::Resource.lock_request_priority`): a blocking request at `-inf` is always
1300
+ # dead (the highest band, no enqueue time) and is dropped with all requests at `-inf` of both
1301
+ # queues in the same round trip; a request at `+inf` is never ahead of the current request;
1302
+ # - the dropped blocking requests are returned, not logged: the attempt and its pre-check log
1303
+ # them at the same step (after the queueing log events, see `log_dropped_requests`);
1304
+ #
1305
+ # @param rconn [RedisClient]
1306
+ # @param read_write_mode [Symbol]
1307
+ # @param request_queue [String]
1308
+ # @param opposite_request_queue [String]
1309
+ # @param acquirer_id [String]
1310
+ # @param acquirer_position [Numeric]
1311
+ # @param acquirer_dead_score [Float]
1312
+ # Requests queued before this time are expired (the barrier of the default priority band).
1313
+ # @param access_strategy [Symbol]
1314
+ # @param waiting_request [Array<String,Float>,NilClass] The head of the request queue.
1315
+ # @param opposite_waiting_request [Array<String,Float>,NilClass]
1316
+ # The head of the opposite request queue.
1317
+ # @return [Array<Array<String,Float>,NilClass,Array<Array<String,Integer>>>,NilClass]
1318
+ # - the actual queue heads and the dropped blocking requests (the acquirer and the priority of
1319
+ # each one, in the order of the removal): `[waiting_request, opposite_waiting_request,
1320
+ # dropped_requests]` when at least one dead lock request is dropped;
1321
+ # - `nil` otherwise;
1322
+ #
1323
+ # @api private
1324
+ # @since 1.22.0
1325
+ # rubocop:disable Metrics/MethodLength
1326
+ def drop_dead_requests_ahead(
1327
+ rconn,
1328
+ read_write_mode,
1329
+ request_queue,
1330
+ opposite_request_queue,
1331
+ acquirer_id,
1332
+ acquirer_position,
1333
+ acquirer_dead_score,
1334
+ access_strategy,
1335
+ waiting_request,
1336
+ opposite_waiting_request
1337
+ )
1338
+ return unless access_strategy == :queued
1339
+
1340
+ read_mode = (read_write_mode == :read)
1341
+ round_trips = 0
1342
+ # NOTE: allocated by the first removal only (no allocations while nothing dead blocks)
1343
+ # @type var dropped_requests: Array[[String, Integer]]?
1344
+ dropped_requests = nil
1345
+
1346
+ while round_trips < MAX_DEAD_REQUEST_REMOVALS
1347
+ # NOTE: the lock requests that block the current one (the same order as in `lock_obstacle`):
1348
+ # the head of the write lock queue (write lock requests only) and the earlier lock request
1349
+ # of the opposite mode; a live head of the write lock queue stops the removal (the attempt
1350
+ # waits for it), the dead blocking requests are dropped in one round trip (both bands);
1351
+ # @type var dead_head: [String, Float]?
1352
+ dead_head = nil
1353
+ # @type var dead_head_priority: Integer?
1354
+ dead_head_priority = nil
1355
+ if !read_mode && waiting_request && waiting_request[0] != acquirer_id
1356
+ dead_head_priority = dead_request_priority(waiting_request, acquirer_dead_score)
1357
+ break unless dead_head_priority
1358
+
1359
+ dead_head = waiting_request
1360
+ end
1361
+ # @type var dead_ahead: [String, Float]?
1362
+ dead_ahead = nil
1363
+ # @type var dead_ahead_priority: Integer?
1364
+ dead_ahead_priority = nil
1365
+ if find_opposite_request_ahead(
1366
+ access_strategy, opposite_waiting_request, acquirer_position, acquirer_id
1367
+ )
1368
+ ahead_request = opposite_waiting_request #: [String, Float]
1369
+ dead_ahead_priority = dead_request_priority(ahead_request, acquirer_dead_score)
1370
+ dead_ahead = ahead_request if dead_ahead_priority
1371
+ end
1372
+ break unless dead_head_priority || dead_ahead_priority
1373
+
1374
+ # NOTE: (pipeline layout) ZREMRANGEBYSCORE of both queues for each dropped band (one or two
1375
+ # bands) and for the requests at `-inf` (a dead blocking request at `-inf` only), then [-2]
1376
+ # ZRANGE WITHSCORES (the head of the request queue) and [-1] ZRANGE WITHSCORES (the head of
1377
+ # the opposite request queue);
1378
+ # @type var queue_state: Array[untyped]
1379
+ queue_state = rconn.pipelined do |pipeline|
1380
+ if dead_head_priority
1381
+ pipeline_dead_requests_removal(
1382
+ pipeline, request_queue, opposite_request_queue, acquirer_dead_score, dead_head_priority
1383
+ )
1384
+ end
1385
+ if dead_ahead_priority && dead_ahead_priority != dead_head_priority
1386
+ pipeline_dead_requests_removal(
1387
+ pipeline, request_queue, opposite_request_queue, acquirer_dead_score,
1388
+ dead_ahead_priority
1389
+ )
1390
+ end
1391
+ # NOTE: (infinite positions) a dead blocking request at `-inf` (a manual write: out of all
1392
+ # bands, see `RedisQueuedLocks::Resource.lock_request_priority`) is not covered by
1393
+ # the band removal of its decoded priority (it drops the dead requests of that band
1394
+ # only): the requests at `-inf` are dropped from both queues by their position;
1395
+ if (dead_head && dead_head[1] == -Float::INFINITY) ||
1396
+ (dead_ahead && dead_ahead[1] == -Float::INFINITY)
1397
+ pipeline.call('ZREMRANGEBYSCORE', request_queue, '-inf', '-inf')
1398
+ pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, '-inf', '-inf')
1399
+ end
1400
+ pipeline.call('ZRANGE', request_queue, '0', '0', 'WITHSCORES')
1401
+ pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
1402
+ end
1403
+ # @type var removed_requests: Array[[String, Integer]]
1404
+ removed_requests = dropped_requests || []
1405
+ removed_requests << [dead_head[0], dead_head_priority] if dead_head && dead_head_priority
1406
+ removed_requests << [dead_ahead[0], dead_ahead_priority] if dead_ahead && dead_ahead_priority
1407
+ dropped_requests = removed_requests
1408
+ waiting_request = queue_state[-2].first
1409
+ opposite_waiting_request = queue_state[-1].first
1410
+ round_trips += 1
1411
+ end
1412
+
1413
+ [waiting_request, opposite_waiting_request, dropped_requests] if dropped_requests
1414
+ end
1415
+ # rubocop:enable Metrics/MethodLength
1416
+
1417
+ # (priorities) Logs the dead lock requests dropped by `drop_dead_requests_ahead` (the attempt
1418
+ # and its pre-check log them at the same step: after the queueing log events).
1419
+ #
1420
+ # @param logger [::Logger,#debug]
1421
+ # @param log_sampled [Boolean]
1422
+ # @param log_lock_try [Boolean]
1423
+ # @param lock_key [String]
1424
+ # @param queue_ttl [Integer]
1425
+ # @param acquirer_id [String]
1426
+ # @param host_id [String]
1427
+ # @param access_strategy [Symbol]
1428
+ # @param read_write_mode [Symbol]
1429
+ # @param dropped_requests [Array<Array<String,Integer>>]
1430
+ # The acquirer and the priority of each dropped lock request.
1431
+ # @return [void]
1432
+ #
1433
+ # @api private
1434
+ # @since 1.22.0
1435
+ def log_dropped_requests(
1436
+ logger,
1437
+ log_sampled,
1438
+ log_lock_try,
1439
+ lock_key,
1440
+ queue_ttl,
1441
+ acquirer_id,
1442
+ host_id,
1443
+ access_strategy,
1444
+ read_write_mode,
1445
+ dropped_requests
1446
+ )
1447
+ return unless log_sampled && log_lock_try
1448
+
1449
+ dropped_requests.each do |dead_acquirer_id, dead_priority|
1450
+ LogVisitor.dead_request_removed(
1451
+ logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
1452
+ access_strategy, read_write_mode, dead_acquirer_id, dead_priority
1453
+ )
1454
+ end
1455
+ end
1456
+
1457
+ # (priorities) The priority band of the dead lock request (see `drop_dead_requests_ahead`): the
1458
+ # band and the enqueue time are decoded from the position of the request (infinite positions:
1459
+ # `-inf` is decoded to the highest band and is always dead, `+inf` to the lowest band and is never
1460
+ # dead, see `RedisQueuedLocks::Resource.lock_request_priority`).
1461
+ #
1462
+ # @param lock_request [Array<String,Float>] The acquirer and the position of the lock request.
1463
+ # @param acquirer_dead_score [Float]
1464
+ # Requests queued before this time are expired (the barrier of the default priority band).
1465
+ # @return [Integer,NilClass]
1466
+ # The priority of the dead lock request, `nil` for a live one (its position is after the time
1467
+ # barrier of its band: the same barrier as the dropping of the dead requests of the band).
1468
+ #
1469
+ # @api private
1470
+ # @since 1.22.0
1471
+ def dead_request_priority(lock_request, acquirer_dead_score)
1472
+ priority = RedisQueuedLocks::Resource.lock_request_priority(lock_request[1])
1473
+ band_dead_score =
1474
+ RedisQueuedLocks::Resource.lock_priority_band_dead_score(acquirer_dead_score, priority)
1475
+ priority if lock_request[1] <= band_dead_score
1476
+ end
1477
+
1150
1478
  # Pre-check of the attempt (Step PRE): the lock state without WATCH (one round trip).
1151
1479
  # - `fail_fast` attempts: the lock state only (the request is not queued: the failed "no try"
1152
1480
  # attempt does not dequeue the request); fails with `:fail_fast_no_try` on the obtained lock;
1153
1481
  # - retries: the lock state and the lock request queueing (the request is already queued by
1154
1482
  # the previous attempts); fails with the obstacle of the classic logic (`lock_obstacle`)
1155
- # and logs the same events as the failed attempt;
1483
+ # and logs the same events as the failed attempt in the same order;
1484
+ # - (priorities) retries drop the dead lock requests of other priority bands that block the
1485
+ # request before the decision (Step 2.2 of the attempt: one more round trip per dropped
1486
+ # band, see `drop_dead_requests_ahead`);
1156
1487
  # - same-process conflicts (the lock is obtained by the current acquirer) and locks that can be
1157
1488
  # obtained are not decided: the attempt continues with the WATCH/MULTI logic (`nil`);
1158
1489
  # - the pre-check never obtains the lock (mutual exclusion relies on WATCH/MULTI only);
@@ -1170,6 +1501,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1170
1501
  # @param acquirer_id [String]
1171
1502
  # @param host_id [String]
1172
1503
  # @param acquirer_position [Numeric]
1504
+ # @param priority [Integer]
1173
1505
  # @param acquirer_dead_score [Float]
1174
1506
  # @param queue_ttl [Integer]
1175
1507
  # @param lock_index_enabled [Boolean]
@@ -1180,6 +1512,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1180
1512
  #
1181
1513
  # @api private
1182
1514
  # @since 1.20.0
1515
+ # @version 1.22.0
1183
1516
  # rubocop:disable Metrics/MethodLength
1184
1517
  def precheck_lock(
1185
1518
  rconn,
@@ -1195,6 +1528,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1195
1528
  acquirer_id,
1196
1529
  host_id,
1197
1530
  acquirer_position,
1531
+ priority,
1198
1532
  acquirer_dead_score,
1199
1533
  queue_ttl,
1200
1534
  lock_index_enabled,
@@ -1208,7 +1542,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1208
1542
  lock_state = rconn.pipelined do |pipeline|
1209
1543
  pipeline_lock_state(
1210
1544
  pipeline, lock_key, lock_readers_key, acquirer_id, read_mode, !fail_fast,
1211
- request_queue, opposite_request_queue, acquirer_position, acquirer_dead_score,
1545
+ request_queue, opposite_request_queue, acquirer_position, priority, acquirer_dead_score,
1212
1546
  lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
1213
1547
  )
1214
1548
  end
@@ -1228,12 +1562,26 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1228
1562
  # NOTE: the fail-fast check of the attempt (Fast-Step X0) without same-process conflicts
1229
1563
  return ((current_lock_obtainer || read_locked) ? :fail_fast_no_try : nil) if fail_fast
1230
1564
 
1231
- # @type var waiting_acquirer: String?
1232
- waiting_acquirer =
1233
- lock_state[LOCK_STATE_QUEUE_REPLIES_AT + QUEUE_HEAD_REPLY].first
1565
+ # @type var waiting_request: [String, Float]?
1566
+ waiting_request = lock_state[LOCK_STATE_QUEUE_REPLIES_AT + QUEUE_HEAD_REPLY].first
1234
1567
  # @type var opposite_waiting_request: [String, Float]?
1235
1568
  opposite_waiting_request =
1236
1569
  lock_state[LOCK_STATE_QUEUE_REPLIES_AT + OPPOSITE_QUEUE_HEAD_REPLY].first
1570
+ # NOTE: (priorities) Step 2.2 of the attempt: the dead lock requests of other priority bands
1571
+ # that block the current one are dropped before the obstacle decision; they are logged at
1572
+ # the step of the attempt (after the queueing log events of the failed pre-check, they
1573
+ # follow the decision) or before the attempt that continues (it logs its own queueing);
1574
+ actual_queue_heads = drop_dead_requests_ahead(
1575
+ rconn, read_write_mode, request_queue, opposite_request_queue, acquirer_id,
1576
+ acquirer_position, acquirer_dead_score, access_strategy, waiting_request,
1577
+ opposite_waiting_request
1578
+ )
1579
+ # @type var dropped_requests: Array[[String, Integer]]?
1580
+ dropped_requests = nil
1581
+ if actual_queue_heads
1582
+ waiting_request, opposite_waiting_request, dropped_requests = actual_queue_heads
1583
+ end
1584
+ waiting_acquirer = waiting_request ? waiting_request[0] : nil
1237
1585
  # @type var opposite_request_ahead: String?
1238
1586
  opposite_request_ahead = find_opposite_request_ahead(
1239
1587
  access_strategy, opposite_waiting_request, acquirer_position, acquirer_id
@@ -1242,25 +1590,44 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
1242
1590
  read_mode, access_strategy, acquirer_id, waiting_acquirer, opposite_request_ahead,
1243
1591
  current_lock_obtainer, read_locked
1244
1592
  )
1245
- return unless obstacle
1593
+ unless obstacle
1594
+ if dropped_requests
1595
+ log_dropped_requests(
1596
+ logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
1597
+ access_strategy, read_write_mode, dropped_requests
1598
+ )
1599
+ end
1600
+ return
1601
+ end
1246
1602
 
1247
- # NOTE: the same log events as the failed attempt (queueing steps + the exit event)
1603
+ # NOTE: the priority of the first request is decoded for logs only
1604
+ waiting_priority = logged_request_priority(log_sampled, log_lock_try, waiting_request)
1605
+
1606
+ # NOTE: the same log events as the failed attempt in the same order (queueing steps, the dropped
1607
+ # dead requests, the first request, the exit event)
1248
1608
  LogVisitor.acq_added_to_queue(
1249
1609
  logger, log_sampled, log_lock_try, lock_key,
1250
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
1610
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, priority
1251
1611
  )
1252
1612
  LogVisitor.remove_expired_acqs(
1253
1613
  logger, log_sampled, log_lock_try, lock_key,
1254
1614
  queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
1255
1615
  )
1616
+ if dropped_requests
1617
+ log_dropped_requests(
1618
+ logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
1619
+ access_strategy, read_write_mode, dropped_requests
1620
+ )
1621
+ end
1256
1622
  LogVisitor.get_first_from_queue(
1257
1623
  logger, log_sampled, log_lock_try, lock_key,
1258
- queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer
1624
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer,
1625
+ waiting_priority
1259
1626
  )
1260
1627
  log_lock_obstacle(
1261
1628
  rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
1262
1629
  acquirer_id, host_id, access_strategy, read_write_mode,
1263
- waiting_acquirer, opposite_request_ahead, current_lock_obtainer
1630
+ waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
1264
1631
  )
1265
1632
  obstacle
1266
1633
  end