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.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +216 -25
- data/.claude/rules/acquirer.md +6 -5
- 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 +10 -12
- data/.claude/rules/swarm.md +3 -4
- data/.claude/rules/tests.md +6 -6
- data/.claude/rules/type-checking.md +13 -14
- data/.claude/rules/visitors.md +5 -5
- data/.claude/skills/rql-code-review/SKILL.md +112 -35
- data/CHANGELOG.md +98 -244
- data/CLAUDE.md +9 -8
- data/README.md +275 -85
- data/Rakefile +11 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +28 -12
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +38 -16
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -15
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +430 -63
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +72 -22
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +3 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +10 -5
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -9
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +52 -23
- data/lib/redis_queued_locks/acquirer/locks.rb +6 -2
- data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -7
- data/lib/redis_queued_locks/acquirer/queues.rb +17 -6
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +6 -2
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +6 -2
- data/lib/redis_queued_locks/client.rb +66 -24
- data/lib/redis_queued_locks/config.rb +93 -1
- data/lib/redis_queued_locks/resource.rb +124 -9
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +6 -5
- data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
- 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 +12 -6
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +21 -14
- 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 +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +5 -3
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +14 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +10 -7
- data/sig/redis_queued_locks/config.rbs +54 -1
- data/sig/redis_queued_locks/instrument/sampler.rbs +3 -1
- data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
- data/sig/redis_queued_locks/logging/sampler.rbs +3 -1
- data/sig/redis_queued_locks/resource.rbs +22 -12
- 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
- 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
|
|
@@ -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,
|
|
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]
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
#
|
|
839
|
-
#
|
|
840
|
-
#
|
|
841
|
-
#
|
|
842
|
-
# 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).
|
|
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]
|
|
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
|
|
880
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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,
|
|
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
|
|
1232
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
1630
|
+
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
1264
1631
|
)
|
|
1265
1632
|
obstacle
|
|
1266
1633
|
end
|