redis_queued_locks 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +249 -36
- data/.claude/rules/acquirer.md +16 -14
- data/.claude/rules/arguments.md +2 -2
- data/.claude/rules/changelog.md +27 -0
- data/.claude/rules/git.md +1 -1
- data/.claude/rules/logic.md +34 -13
- data/.claude/rules/swarm.md +4 -5
- data/.claude/rules/tests.md +9 -6
- data/.claude/rules/type-checking.md +13 -14
- data/.claude/rules/visitors.md +13 -14
- data/.claude/skills/rql-code-review/SKILL.md +134 -43
- data/CHANGELOG.md +105 -125
- data/CLAUDE.md +11 -9
- data/README.md +501 -136
- data/Rakefile +11 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +68 -10
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +82 -15
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -14
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +441 -63
- data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +5 -5
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +176 -39
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +322 -49
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +125 -13
- data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
- data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +16 -3
- data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +11 -5
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +20 -10
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +105 -36
- data/lib/redis_queued_locks/acquirer/locks.rb +32 -11
- data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -8
- data/lib/redis_queued_locks/acquirer/queues.rb +26 -6
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +37 -6
- data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +45 -6
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/client.rb +140 -56
- data/lib/redis_queued_locks/config.rb +105 -14
- data/lib/redis_queued_locks/errors.rb +34 -4
- data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
- data/lib/redis_queued_locks/logging/sampler.rb +4 -8
- data/lib/redis_queued_locks/resource.rb +137 -12
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +8 -6
- data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
- data/lib/redis_queued_locks/swarm.rb +86 -5
- data/lib/redis_queued_locks/utilities.rb +1 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- data/rbs_collection.lock.yaml +4 -0
- data/rbs_collection.yaml +2 -1
- data/sig/manifest.yml +1 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +25 -5
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +32 -12
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +59 -11
- data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +4 -4
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +6 -4
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +23 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +16 -3
- data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +24 -10
- data/sig/redis_queued_locks/config.rbs +54 -1
- data/sig/redis_queued_locks/errors.rbs +5 -2
- data/sig/redis_queued_locks/instrument/sampler.rbs +3 -3
- data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
- data/sig/redis_queued_locks/logging/sampler.rbs +3 -3
- data/sig/redis_queued_locks/resource.rbs +23 -13
- data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
- data/sig/redis_queued_locks/utilities.rbs +2 -1
- data/sig/redis_queued_locks.rbs +7 -0
- data/sig/vendor/redis_client.rbs +1 -0
- metadata +4 -3
- /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.
|
|
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
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
|
517
|
-
|
|
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,
|
|
602
|
+
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
553
603
|
)
|
|
554
604
|
inter_result = obstacle
|
|
555
605
|
elsif read_mode
|
|
@@ -648,6 +698,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
648
698
|
end
|
|
649
699
|
end
|
|
650
700
|
end
|
|
701
|
+
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
702
|
+
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
703
|
+
::Timeout::ExitException
|
|
704
|
+
# NOTE: (timeouts) the timeout can interrupt the attempt inside the WATCH block:
|
|
705
|
+
# redis-client sends `UNWATCH` only on `StandardError` (the internal timeouts and the
|
|
706
|
+
# `Timeout.timeout` of the user without an exception class are `::Exception`s), and the
|
|
707
|
+
# connection would return to the pool with the active WATCH (an EXEC of the next
|
|
708
|
+
# transaction on it is aborted when a watched key is changed);
|
|
709
|
+
# - the closed connection drops the WATCH state and is reconnected on the next use;
|
|
710
|
+
rconn.close
|
|
711
|
+
raise
|
|
651
712
|
end
|
|
652
713
|
|
|
653
714
|
# Step 7: Analyze the aquirement attempt:
|
|
@@ -818,24 +879,27 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
818
879
|
|
|
819
880
|
private
|
|
820
881
|
|
|
821
|
-
# Queues the lock request and reads the lock queue state (Steps 1,
|
|
882
|
+
# Queues the lock request and reads the lock queue state (Steps 2.1, 1, 3 and 3.2-3.4 of
|
|
822
883
|
# the attempt): the commands are added to the given pipeline (one round trip with other
|
|
823
884
|
# pipelined commands).
|
|
824
|
-
# Replies (positions relative to the first one): [0]
|
|
825
|
-
#
|
|
826
|
-
#
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
#
|
|
831
|
-
# the
|
|
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).
|
|
832
894
|
#
|
|
833
895
|
# @param pipeline [RedisClient::Pipeline]
|
|
834
896
|
# @param request_queue [String] Queue of the requests of the requested lock mode.
|
|
835
897
|
# @param opposite_request_queue [String] Queue of the requests of the opposite lock mode.
|
|
836
|
-
# @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`.
|
|
837
900
|
# @param acquirer_id [String]
|
|
838
|
-
# @param acquirer_dead_score [Float]
|
|
901
|
+
# @param acquirer_dead_score [Float]
|
|
902
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
839
903
|
# @param lock_name [String] The lock name (the member of the lock index).
|
|
840
904
|
# @param queue_ttl [Integer] Lifetime of the lock request (in seconds).
|
|
841
905
|
# @param lock_index_enabled [Boolean] Send the lock index commands (Steps 3.2-3.4).
|
|
@@ -844,11 +908,13 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
844
908
|
#
|
|
845
909
|
# @api private
|
|
846
910
|
# @since 1.20.0
|
|
911
|
+
# @version 1.22.0
|
|
847
912
|
def queue_lock_request(
|
|
848
913
|
pipeline,
|
|
849
914
|
request_queue,
|
|
850
915
|
opposite_request_queue,
|
|
851
916
|
acquirer_position,
|
|
917
|
+
priority,
|
|
852
918
|
acquirer_id,
|
|
853
919
|
acquirer_dead_score,
|
|
854
920
|
lock_name,
|
|
@@ -856,6 +922,22 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
856
922
|
lock_index_enabled,
|
|
857
923
|
lock_index_grace_period
|
|
858
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
|
+
|
|
859
941
|
# Step 1: add an acquirer to the lock acquirement queue
|
|
860
942
|
# NOTE:
|
|
861
943
|
# 'NX' means "Only add new elements. Don't update already existing elements."
|
|
@@ -865,15 +947,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
865
947
|
# not in the queue now>>;
|
|
866
948
|
pipeline.call('ZADD', request_queue, 'NX', acquirer_position, acquirer_id)
|
|
867
949
|
|
|
868
|
-
# Step
|
|
869
|
-
#
|
|
870
|
-
# block the current request forever;
|
|
871
|
-
pipeline.call('ZREMRANGEBYSCORE', request_queue, '-inf', acquirer_dead_score)
|
|
872
|
-
pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, '-inf', acquirer_dead_score)
|
|
873
|
-
|
|
874
|
-
# 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`)
|
|
875
952
|
# - (RW) and the first request of the opposite mode;
|
|
876
|
-
pipeline.call('ZRANGE', request_queue, '0', '0')
|
|
953
|
+
pipeline.call('ZRANGE', request_queue, '0', '0', 'WITHSCORES')
|
|
877
954
|
pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
|
|
878
955
|
|
|
879
956
|
# NOTE: the disabled lock index: no lock index commands, lock request queues have no TTL
|
|
@@ -910,6 +987,34 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
910
987
|
)
|
|
911
988
|
end
|
|
912
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
|
+
|
|
913
1018
|
# Adds the lock state commands to the given pipeline (the lock state of the attempt and of
|
|
914
1019
|
# the pre-check: one round trip).
|
|
915
1020
|
#
|
|
@@ -938,6 +1043,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
938
1043
|
# @param request_queue [String]
|
|
939
1044
|
# @param opposite_request_queue [String]
|
|
940
1045
|
# @param acquirer_position [Numeric]
|
|
1046
|
+
# @param priority [Integer]
|
|
941
1047
|
# @param acquirer_dead_score [Float]
|
|
942
1048
|
# @param lock_name [String] The lock name (the member of the lock index).
|
|
943
1049
|
# @param queue_ttl [Integer] In seconds.
|
|
@@ -947,6 +1053,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
947
1053
|
#
|
|
948
1054
|
# @api private
|
|
949
1055
|
# @since 1.20.0
|
|
1056
|
+
# @version 1.22.0
|
|
950
1057
|
def pipeline_lock_state(
|
|
951
1058
|
pipeline,
|
|
952
1059
|
lock_key,
|
|
@@ -957,6 +1064,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
957
1064
|
request_queue,
|
|
958
1065
|
opposite_request_queue,
|
|
959
1066
|
acquirer_position,
|
|
1067
|
+
priority,
|
|
960
1068
|
acquirer_dead_score,
|
|
961
1069
|
lock_name,
|
|
962
1070
|
queue_ttl,
|
|
@@ -970,7 +1078,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
970
1078
|
if queueing
|
|
971
1079
|
queue_lock_request(
|
|
972
1080
|
pipeline, request_queue, opposite_request_queue,
|
|
973
|
-
acquirer_position, acquirer_id, acquirer_dead_score,
|
|
1081
|
+
acquirer_position, priority, acquirer_id, acquirer_dead_score,
|
|
974
1082
|
lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
|
|
975
1083
|
)
|
|
976
1084
|
end
|
|
@@ -980,6 +1088,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
980
1088
|
|
|
981
1089
|
# (RW) The request of the opposite mode that should obtain the lock earlier
|
|
982
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;
|
|
983
1093
|
# - equal positions are ordered by acquirer ids;
|
|
984
1094
|
#
|
|
985
1095
|
# @param access_strategy [Symbol]
|
|
@@ -1071,12 +1181,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1071
1181
|
# @param access_strategy [Symbol]
|
|
1072
1182
|
# @param read_write_mode [Symbol]
|
|
1073
1183
|
# @param waiting_acquirer [String,NilClass]
|
|
1074
|
-
# @param
|
|
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`).
|
|
1075
1189
|
# @param current_lock_obtainer [String,NilClass]
|
|
1076
1190
|
# @return [void]
|
|
1077
1191
|
#
|
|
1078
1192
|
# @api private
|
|
1079
1193
|
# @since 1.20.0
|
|
1194
|
+
# @version 1.22.0
|
|
1080
1195
|
# rubocop:disable Metrics/MethodLength
|
|
1081
1196
|
def log_lock_obstacle(
|
|
1082
1197
|
rconn,
|
|
@@ -1091,7 +1206,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1091
1206
|
access_strategy,
|
|
1092
1207
|
read_write_mode,
|
|
1093
1208
|
waiting_acquirer,
|
|
1094
|
-
|
|
1209
|
+
waiting_priority,
|
|
1210
|
+
opposite_waiting_request,
|
|
1095
1211
|
current_lock_obtainer
|
|
1096
1212
|
)
|
|
1097
1213
|
case obstacle
|
|
@@ -1104,44 +1220,270 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1104
1220
|
LogVisitor.exit__no_first(
|
|
1105
1221
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1106
1222
|
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer,
|
|
1223
|
+
waiting_priority,
|
|
1107
1224
|
# NOTE: lock data is extracted for logs only
|
|
1108
1225
|
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
|
|
1109
1226
|
)
|
|
1110
|
-
when :write_request_is_ahead
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
|
1122
1244
|
when :lock_is_still_acquired
|
|
1123
1245
|
LogVisitor.exit__lock_still_obtained(
|
|
1124
1246
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1125
1247
|
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
1126
|
-
waiting_acquirer,
|
|
1248
|
+
waiting_acquirer, waiting_priority,
|
|
1249
|
+
current_lock_obtainer, #: String
|
|
1127
1250
|
# NOTE: lock data is extracted for logs only
|
|
1128
1251
|
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
|
|
1129
1252
|
)
|
|
1130
1253
|
when :read_lock_is_still_acquired
|
|
1131
1254
|
LogVisitor.exit__read_lock_still_obtained(
|
|
1132
1255
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1133
|
-
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
|
|
1134
1258
|
)
|
|
1135
1259
|
end
|
|
1136
1260
|
end
|
|
1137
1261
|
# rubocop:enable Metrics/MethodLength
|
|
1138
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
|
+
|
|
1139
1478
|
# Pre-check of the attempt (Step PRE): the lock state without WATCH (one round trip).
|
|
1140
1479
|
# - `fail_fast` attempts: the lock state only (the request is not queued: the failed "no try"
|
|
1141
1480
|
# attempt does not dequeue the request); fails with `:fail_fast_no_try` on the obtained lock;
|
|
1142
1481
|
# - retries: the lock state and the lock request queueing (the request is already queued by
|
|
1143
1482
|
# the previous attempts); fails with the obstacle of the classic logic (`lock_obstacle`)
|
|
1144
|
-
# 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`);
|
|
1145
1487
|
# - same-process conflicts (the lock is obtained by the current acquirer) and locks that can be
|
|
1146
1488
|
# obtained are not decided: the attempt continues with the WATCH/MULTI logic (`nil`);
|
|
1147
1489
|
# - the pre-check never obtains the lock (mutual exclusion relies on WATCH/MULTI only);
|
|
@@ -1159,6 +1501,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1159
1501
|
# @param acquirer_id [String]
|
|
1160
1502
|
# @param host_id [String]
|
|
1161
1503
|
# @param acquirer_position [Numeric]
|
|
1504
|
+
# @param priority [Integer]
|
|
1162
1505
|
# @param acquirer_dead_score [Float]
|
|
1163
1506
|
# @param queue_ttl [Integer]
|
|
1164
1507
|
# @param lock_index_enabled [Boolean]
|
|
@@ -1169,6 +1512,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1169
1512
|
#
|
|
1170
1513
|
# @api private
|
|
1171
1514
|
# @since 1.20.0
|
|
1515
|
+
# @version 1.22.0
|
|
1172
1516
|
# rubocop:disable Metrics/MethodLength
|
|
1173
1517
|
def precheck_lock(
|
|
1174
1518
|
rconn,
|
|
@@ -1184,6 +1528,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1184
1528
|
acquirer_id,
|
|
1185
1529
|
host_id,
|
|
1186
1530
|
acquirer_position,
|
|
1531
|
+
priority,
|
|
1187
1532
|
acquirer_dead_score,
|
|
1188
1533
|
queue_ttl,
|
|
1189
1534
|
lock_index_enabled,
|
|
@@ -1197,7 +1542,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1197
1542
|
lock_state = rconn.pipelined do |pipeline|
|
|
1198
1543
|
pipeline_lock_state(
|
|
1199
1544
|
pipeline, lock_key, lock_readers_key, acquirer_id, read_mode, !fail_fast,
|
|
1200
|
-
request_queue, opposite_request_queue, acquirer_position, acquirer_dead_score,
|
|
1545
|
+
request_queue, opposite_request_queue, acquirer_position, priority, acquirer_dead_score,
|
|
1201
1546
|
lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
|
|
1202
1547
|
)
|
|
1203
1548
|
end
|
|
@@ -1217,12 +1562,26 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1217
1562
|
# NOTE: the fail-fast check of the attempt (Fast-Step X0) without same-process conflicts
|
|
1218
1563
|
return ((current_lock_obtainer || read_locked) ? :fail_fast_no_try : nil) if fail_fast
|
|
1219
1564
|
|
|
1220
|
-
# @type var
|
|
1221
|
-
|
|
1222
|
-
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
|
|
1223
1567
|
# @type var opposite_waiting_request: [String, Float]?
|
|
1224
1568
|
opposite_waiting_request =
|
|
1225
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
|
|
1226
1585
|
# @type var opposite_request_ahead: String?
|
|
1227
1586
|
opposite_request_ahead = find_opposite_request_ahead(
|
|
1228
1587
|
access_strategy, opposite_waiting_request, acquirer_position, acquirer_id
|
|
@@ -1231,25 +1590,44 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1231
1590
|
read_mode, access_strategy, acquirer_id, waiting_acquirer, opposite_request_ahead,
|
|
1232
1591
|
current_lock_obtainer, read_locked
|
|
1233
1592
|
)
|
|
1234
|
-
|
|
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
|
|
1235
1602
|
|
|
1236
|
-
# NOTE: the
|
|
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)
|
|
1237
1608
|
LogVisitor.acq_added_to_queue(
|
|
1238
1609
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1239
|
-
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
1610
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, priority
|
|
1240
1611
|
)
|
|
1241
1612
|
LogVisitor.remove_expired_acqs(
|
|
1242
1613
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1243
1614
|
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
1244
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
|
|
1245
1622
|
LogVisitor.get_first_from_queue(
|
|
1246
1623
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1247
|
-
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
|
|
1248
1626
|
)
|
|
1249
1627
|
log_lock_obstacle(
|
|
1250
1628
|
rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
|
|
1251
1629
|
acquirer_id, host_id, access_strategy, read_write_mode,
|
|
1252
|
-
waiting_acquirer,
|
|
1630
|
+
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
1253
1631
|
)
|
|
1254
1632
|
obstacle
|
|
1255
1633
|
end
|