redis_queued_locks 1.22.0 → 1.23.1
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 +90 -38
- data/.claude/rules/acquirer.md +12 -7
- data/.claude/rules/arguments.md +2 -2
- data/.claude/rules/logic.md +5 -4
- data/.claude/rules/tests.md +4 -4
- data/.claude/rules/type-checking.md +2 -3
- data/.claude/rules/visitors.md +8 -6
- data/.claude/skills/rql-code-review/SKILL.md +25 -20
- data/.ruby-version +1 -1
- data/CHANGELOG.md +30 -1
- data/CLAUDE.md +5 -4
- data/README.md +259 -103
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +582 -276
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +29 -12
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +36 -27
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +13 -13
- data/lib/redis_queued_locks/acquirer/lock_series/instr_visitor.rb +243 -0
- data/lib/redis_queued_locks/acquirer/lock_series/log_visitor.rb +472 -0
- data/lib/redis_queued_locks/acquirer/lock_series/release_lock_series.rb +181 -0
- data/lib/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rb +1051 -0
- data/lib/redis_queued_locks/acquirer/lock_series.rb +957 -0
- data/lib/redis_queued_locks/acquirer.rb +1 -1
- data/lib/redis_queued_locks/client.rb +55 -25
- data/lib/redis_queued_locks/errors.rb +8 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- data/redis_queued_locks.gemspec +21 -14
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +106 -5
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/lock_series/instr_visitor.rbs +102 -0
- data/sig/redis_queued_locks/acquirer/lock_series/log_visitor.rbs +154 -0
- data/sig/redis_queued_locks/acquirer/lock_series/release_lock_series.rbs +37 -0
- data/sig/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rbs +198 -0
- data/sig/redis_queued_locks/acquirer/lock_series.rbs +106 -0
- data/sig/redis_queued_locks/client.rbs +70 -3
- data/sig/redis_queued_locks/errors.rbs +6 -0
- metadata +32 -11
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -61
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -104
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -513
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Read/Write locks (`
|
|
3
|
+
# Read/Write locks (`rw_mode`):
|
|
4
4
|
# - `:write` lock is exclusive: it is obtained when there is no write lock and no live read lock;
|
|
5
5
|
# - `:read` lock is shared: it is obtained when there is no write lock;
|
|
6
6
|
# - read locks are stored in the readers registry: a sorted set where the member is an acquirer
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
#
|
|
53
53
|
# @api private
|
|
54
54
|
# @since 1.0.0
|
|
55
|
-
# @version 1.
|
|
55
|
+
# @version 1.23.0
|
|
56
56
|
# rubocop:disable Metrics/ModuleLength
|
|
57
57
|
module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
58
58
|
require_relative 'try_to_lock/log_visitor'
|
|
@@ -133,6 +133,33 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
133
133
|
# @since 1.20.0
|
|
134
134
|
OPPOSITE_QUEUE_HEAD_REPLY = 4
|
|
135
135
|
|
|
136
|
+
# The number of the replies of the lock request queueing commands (`queue_lock_request`) with
|
|
137
|
+
# the disabled lock index.
|
|
138
|
+
#
|
|
139
|
+
# @return [Integer]
|
|
140
|
+
#
|
|
141
|
+
# @api private
|
|
142
|
+
# @since 1.23.0
|
|
143
|
+
QUEUE_REPLIES = 5
|
|
144
|
+
|
|
145
|
+
# The number of the replies of the lock index commands of the lock request queueing
|
|
146
|
+
# (`queue_lock_request`, the enabled lock index only).
|
|
147
|
+
#
|
|
148
|
+
# @return [Integer]
|
|
149
|
+
#
|
|
150
|
+
# @api private
|
|
151
|
+
# @since 1.23.0
|
|
152
|
+
QUEUE_LOCK_INDEX_REPLIES = 4
|
|
153
|
+
|
|
154
|
+
# The number of the commands of the read lock extension (`transact_read_lock_extension`) without
|
|
155
|
+
# the lock index command (the enabled lock index only: the last command).
|
|
156
|
+
#
|
|
157
|
+
# @return [Integer]
|
|
158
|
+
#
|
|
159
|
+
# @api private
|
|
160
|
+
# @since 1.23.0
|
|
161
|
+
READ_LOCK_EXTENSION_COMMANDS = 8
|
|
162
|
+
|
|
136
163
|
# The maximum number of the round trips of one call of `drop_dead_requests_ahead` (the attempt or
|
|
137
164
|
# its pre-check; each round trip drops the dead blocking requests, the head of the write lock
|
|
138
165
|
# queue and the earlier request of the opposite mode, with the dead requests of their priority
|
|
@@ -148,7 +175,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
148
175
|
# @param logger [::Logger,#debug]
|
|
149
176
|
# @param log_lock_try [Boolean]
|
|
150
177
|
# @param lock_key [String]
|
|
151
|
-
# @param
|
|
178
|
+
# @param rw_mode [Symbol] `:read` or `:write`
|
|
152
179
|
# @param lock_key_queue [String] Queue of write lock requests.
|
|
153
180
|
# @param read_lock_key_queue [String] Queue of read lock requests.
|
|
154
181
|
# @param lock_readers_key [String] Registry of read lock holders.
|
|
@@ -188,7 +215,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
188
215
|
logger,
|
|
189
216
|
log_lock_try,
|
|
190
217
|
lock_key,
|
|
191
|
-
|
|
218
|
+
rw_mode,
|
|
192
219
|
lock_key_queue,
|
|
193
220
|
read_lock_key_queue,
|
|
194
221
|
lock_readers_key,
|
|
@@ -220,9 +247,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
220
247
|
# Step RW: the mode of the lock that is really held by the acquirer after the attempt
|
|
221
248
|
# (the requested mode or the mode of the already obtained lock for same-process conflicts);
|
|
222
249
|
# @type var held_rw_mode: Symbol
|
|
223
|
-
held_rw_mode =
|
|
250
|
+
held_rw_mode = rw_mode
|
|
224
251
|
# Step RW: read lock request flag
|
|
225
|
-
read_mode = (
|
|
252
|
+
read_mode = (rw_mode == :read)
|
|
226
253
|
# Step RW: request queue of the current mode and the queue of the opposite mode
|
|
227
254
|
request_queue = read_mode ? read_lock_key_queue : lock_key_queue
|
|
228
255
|
opposite_request_queue = read_mode ? lock_key_queue : read_lock_key_queue
|
|
@@ -243,14 +270,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
243
270
|
|
|
244
271
|
LogVisitor.start(
|
|
245
272
|
logger, log_sampled, log_lock_try, lock_key,
|
|
246
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
273
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
247
274
|
)
|
|
248
275
|
|
|
249
276
|
# Step X: start to work with lock acquiring
|
|
250
277
|
result = redis.with do |rconn|
|
|
251
278
|
LogVisitor.rconn_fetched(
|
|
252
279
|
logger, log_sampled, log_lock_try, lock_key,
|
|
253
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
280
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
254
281
|
)
|
|
255
282
|
|
|
256
283
|
# Step PRE: (pre-check) the lock state without WATCH: the attempt that can not obtain
|
|
@@ -260,7 +287,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
260
287
|
# - see `precheck_lock` for details;
|
|
261
288
|
if precheck
|
|
262
289
|
inter_result = precheck_lock(
|
|
263
|
-
rconn, logger, log_sampled, log_lock_try, lock_key,
|
|
290
|
+
rconn, logger, log_sampled, log_lock_try, lock_key, rw_mode, lock_readers_key,
|
|
264
291
|
request_queue, opposite_request_queue, lock_name, acquirer_id, host_id,
|
|
265
292
|
acquirer_position, priority, acquirer_dead_score, queue_ttl, lock_index_enabled,
|
|
266
293
|
lock_index_grace_period, fail_fast, access_strategy
|
|
@@ -324,7 +351,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
324
351
|
if sp_conflict_rw_mode != nil
|
|
325
352
|
LogVisitor.same_process_conflict_detected(
|
|
326
353
|
logger, log_sampled, log_lock_try, lock_key,
|
|
327
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
354
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
328
355
|
)
|
|
329
356
|
|
|
330
357
|
# NOTE:
|
|
@@ -334,30 +361,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
334
361
|
|
|
335
362
|
# SP-Conflict Step X2: self-process dead lock moment started.
|
|
336
363
|
# SP-Conflict CHECK (Step CHECK): check chosen strategy and flag the current status
|
|
337
|
-
|
|
338
|
-
when :work_through
|
|
339
|
-
# <SP-Conflict Moment>: work through => exit
|
|
340
|
-
sp_conflict_status = lock_upgrade ? :conflict_lock_upgrade : :conflict_work_through
|
|
341
|
-
when :extendable_work_through
|
|
342
|
-
# <SP-Conflict Moment>: extendable_work_through => extend the lock pttl and exit
|
|
343
|
-
sp_conflict_status =
|
|
344
|
-
lock_upgrade ? :conflict_lock_upgrade : :extendable_conflict_work_through
|
|
345
|
-
when :wait_for_lock
|
|
346
|
-
# <SP-Conflict Moment>: wait_for_lock => obtain a lock in classic way
|
|
347
|
-
sp_conflict_status = :conflict_wait_for_lock
|
|
348
|
-
when :dead_locking
|
|
349
|
-
# <SP-Conflict Moment>: dead_locking => exit and fail
|
|
350
|
-
sp_conflict_status = :conflict_dead_lock
|
|
351
|
-
else
|
|
352
|
-
# <SP-Conflict Moment>:
|
|
353
|
-
# - unknown status => work in classic way <wait_for_lock>
|
|
354
|
-
# - it is a case when the new status is added to the code base in the past
|
|
355
|
-
# but is forgotten to be added here;
|
|
356
|
-
sp_conflict_status = :conflict_wait_for_lock
|
|
357
|
-
end
|
|
364
|
+
sp_conflict_status = same_process_conflict_status(conflict_strategy, lock_upgrade)
|
|
358
365
|
LogVisitor.same_process_conflict_analyzed(
|
|
359
366
|
logger, log_sampled, log_lock_try, lock_key,
|
|
360
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
367
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, sp_conflict_status
|
|
361
368
|
)
|
|
362
369
|
end
|
|
363
370
|
|
|
@@ -366,71 +373,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
366
373
|
# SP-Conflict-Step FINAL (SPCF): extend the lock and work through
|
|
367
374
|
# - extend the lock ttl;
|
|
368
375
|
# - store extensions in lock metadata;
|
|
376
|
+
spc_processed_timestamp = Time.now.to_f
|
|
369
377
|
if sp_conflict_rw_mode == :write
|
|
370
378
|
held_rw_mode = :write
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
# - [REDIS RESULT]: in normal cases should return the last script command value
|
|
374
|
-
# - for the current script should return:
|
|
375
|
-
# <1> => timeout was set;
|
|
376
|
-
# <0> => timeount was not set;
|
|
377
|
-
transact.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, ttl)
|
|
378
|
-
# SPCF Step <Meta>: store conflict-state additionals in lock metadata:
|
|
379
|
-
# SPCF Step 2: (lock meta-data)
|
|
380
|
-
# - add the added ttl to reflect the real lock TTL in info;
|
|
381
|
-
# - [REDIS RESULT]: in normal cases should return the value of <ttl> key
|
|
382
|
-
# - for non-existent key value starts from <0> (zero)
|
|
383
|
-
transact.call('HINCRBY', lock_key, 'spc_ext_ttl', ttl)
|
|
384
|
-
# SPCF Step 3: (lock meta-data)
|
|
385
|
-
# - increment the conflcit counter in order to remember
|
|
386
|
-
# how many times dead lock happened;
|
|
387
|
-
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
388
|
-
# - for non-existent key starts from 0
|
|
389
|
-
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
390
|
-
# SPCF Step 4: (lock meta-data)
|
|
391
|
-
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
392
|
-
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
393
|
-
# were added/changed;
|
|
394
|
-
transact.call(
|
|
395
|
-
'HSET',
|
|
396
|
-
lock_key,
|
|
397
|
-
'l_spc_ext_ts', spc_processed_timestamp = Time.now.to_f,
|
|
398
|
-
'l_spc_ext_ini_ttl', ttl
|
|
379
|
+
transact_write_lock_extension(
|
|
380
|
+
transact, lock_key, lock_name, ttl, lock_index_enabled, spc_processed_timestamp
|
|
399
381
|
)
|
|
400
382
|
else
|
|
401
383
|
held_rw_mode = :read
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
transact.call('ZADD', lock_readers_key, 'XX', 'INCR', ttl, acquirer_id)
|
|
406
|
-
# SPCF Step 2 (read lock): the registry should live as long as the longest read lock
|
|
407
|
-
# - NX + GT => sets the registry TTL to max(current TTL, extended read lock TTL);
|
|
408
|
-
extended_read_lock_ttl = (own_read_lock_expiration.to_f + ttl.to_i - redis_time).ceil
|
|
409
|
-
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'NX')
|
|
410
|
-
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'GT')
|
|
411
|
-
# SPCF Step 3 (read lock data): the same reentrant lock data as for the write lock
|
|
412
|
-
transact.call('HINCRBY', read_lock_key, 'spc_ext_ttl', ttl)
|
|
413
|
-
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
414
|
-
transact.call(
|
|
415
|
-
'HSET',
|
|
416
|
-
read_lock_key,
|
|
417
|
-
'l_spc_ext_ts', spc_processed_timestamp = Time.now.to_f,
|
|
418
|
-
'l_spc_ext_ini_ttl', ttl
|
|
419
|
-
)
|
|
420
|
-
# SPCF Step 4 (read lock data): the read lock data lives as long as the read lock
|
|
421
|
-
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'NX')
|
|
422
|
-
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'GT')
|
|
423
|
-
end
|
|
424
|
-
# SPCF Step 5: (lock index) the lock (write or read) lives longer by the extension:
|
|
425
|
-
# - the lock index score is increased by the extension (the upper bound of the lock
|
|
426
|
-
# lifetime stays the upper bound);
|
|
427
|
-
# - XX => locks without the index entry (of older RQL versions) are not indexed with
|
|
428
|
-
# a wrong score (see `reindex_locks`);
|
|
429
|
-
# - [REDIS RESULT]: the new lock index score (nil for the lock without the index entry);
|
|
430
|
-
# - only with the enabled lock index (the last command of the transaction);
|
|
431
|
-
if lock_index_enabled
|
|
432
|
-
transact.call(
|
|
433
|
-
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'XX', 'INCR', ttl, lock_name
|
|
384
|
+
transact_read_lock_extension(
|
|
385
|
+
transact, lock_readers_key, read_lock_key, lock_name, acquirer_id, ttl,
|
|
386
|
+
lock_index_enabled, own_read_lock_expiration, redis_time, spc_processed_timestamp
|
|
434
387
|
)
|
|
435
388
|
end
|
|
436
389
|
inter_result = :extendable_conflict_work_through
|
|
@@ -438,53 +391,29 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
438
391
|
# @type var spc_processed_timestamp: Float
|
|
439
392
|
LogVisitor.reentrant_lock__extend_and_work_through(
|
|
440
393
|
logger, log_sampled, log_lock_try, lock_key,
|
|
441
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
394
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
442
395
|
sp_conflict_status, ttl, spc_processed_timestamp
|
|
443
396
|
)
|
|
444
397
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
445
398
|
elsif sp_conflict_status == :conflict_work_through
|
|
446
399
|
inter_result = :conflict_work_through
|
|
447
400
|
|
|
401
|
+
spc_processed_timestamp = Time.now.to_f
|
|
402
|
+
|
|
448
403
|
if sp_conflict_rw_mode == :write
|
|
449
404
|
held_rw_mode = :write
|
|
450
|
-
|
|
451
|
-
# SPCF Step X: (lock meta-data)
|
|
452
|
-
# - increment the conflcit counter in order to remember
|
|
453
|
-
# how many times dead lock happened;
|
|
454
|
-
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
455
|
-
# - for non-existent key starts from 0
|
|
456
|
-
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
457
|
-
# SPCF Step 4: (lock meta-data)
|
|
458
|
-
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
459
|
-
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
460
|
-
# were added/changed;
|
|
461
|
-
transact.call(
|
|
462
|
-
'HSET',
|
|
463
|
-
lock_key,
|
|
464
|
-
'l_spc_ts', spc_processed_timestamp = Time.now.to_f
|
|
465
|
-
)
|
|
405
|
+
transact_write_lock_work_through(transact, lock_key, spc_processed_timestamp)
|
|
466
406
|
else
|
|
467
407
|
held_rw_mode = :read
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
471
|
-
transact.call(
|
|
472
|
-
'HSET', read_lock_key, 'l_spc_ts', spc_processed_timestamp = Time.now.to_f
|
|
473
|
-
)
|
|
474
|
-
# SPCF Step X (read lock data): (NX) the read lock data should not outlive the read lock
|
|
475
|
-
# (it is the case when the read lock data is dropped during the read lock life);
|
|
476
|
-
transact.call(
|
|
477
|
-
'PEXPIRE',
|
|
478
|
-
read_lock_key,
|
|
479
|
-
(own_read_lock_expiration.to_f - redis_time).ceil,
|
|
480
|
-
'NX'
|
|
408
|
+
transact_read_lock_work_through(
|
|
409
|
+
transact, read_lock_key, own_read_lock_expiration, redis_time, spc_processed_timestamp
|
|
481
410
|
)
|
|
482
411
|
end
|
|
483
412
|
|
|
484
413
|
# @type var spc_processed_timestamp: Float
|
|
485
414
|
LogVisitor.reentrant_lock__work_through(
|
|
486
415
|
logger, log_sampled, log_lock_try, lock_key,
|
|
487
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
416
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
488
417
|
sp_conflict_status, spc_processed_timestamp
|
|
489
418
|
)
|
|
490
419
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
@@ -495,7 +424,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
495
424
|
# @type var spc_processed_timestamp: Float
|
|
496
425
|
LogVisitor.single_process_lock_conflict__dead_lock(
|
|
497
426
|
logger, log_sampled, log_lock_try, lock_key,
|
|
498
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
427
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
499
428
|
sp_conflict_status, spc_processed_timestamp
|
|
500
429
|
)
|
|
501
430
|
# SP-Conflict-Step X2 (RW): read-to-write lock upgrade is not supported => exit and fail
|
|
@@ -506,7 +435,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
506
435
|
# @type var spc_processed_timestamp: Float
|
|
507
436
|
LogVisitor.single_process_lock_conflict__lock_upgrade(
|
|
508
437
|
logger, log_sampled, log_lock_try, lock_key,
|
|
509
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
438
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
510
439
|
sp_conflict_status, spc_processed_timestamp
|
|
511
440
|
)
|
|
512
441
|
# Reached the SP-Non-Conflict Mode (NOTE):
|
|
@@ -550,18 +479,18 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
550
479
|
|
|
551
480
|
LogVisitor.acq_added_to_queue(
|
|
552
481
|
logger, log_sampled, log_lock_try, lock_key,
|
|
553
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
482
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
|
|
554
483
|
)
|
|
555
484
|
LogVisitor.remove_expired_acqs(
|
|
556
485
|
logger, log_sampled, log_lock_try, lock_key,
|
|
557
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
486
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
558
487
|
)
|
|
559
488
|
|
|
560
489
|
# Step 2.2: (priorities) drop the dead lock requests of other priority bands that block
|
|
561
490
|
# the current one (the queue head or the earlier request of the opposite mode) and get
|
|
562
491
|
# the actual queue heads (no round trips while no dead request blocks the current one);
|
|
563
492
|
actual_queue_heads = drop_dead_requests_ahead(
|
|
564
|
-
rconn,
|
|
493
|
+
rconn, rw_mode, request_queue, opposite_request_queue, acquirer_id,
|
|
565
494
|
acquirer_position, acquirer_dead_score, access_strategy, waiting_request,
|
|
566
495
|
opposite_waiting_request
|
|
567
496
|
)
|
|
@@ -569,7 +498,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
569
498
|
waiting_request, opposite_waiting_request, dropped_requests = actual_queue_heads
|
|
570
499
|
log_dropped_requests(
|
|
571
500
|
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
572
|
-
access_strategy,
|
|
501
|
+
access_strategy, rw_mode, dropped_requests
|
|
573
502
|
)
|
|
574
503
|
end
|
|
575
504
|
waiting_acquirer = waiting_request ? waiting_request[0] : nil
|
|
@@ -578,7 +507,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
578
507
|
|
|
579
508
|
LogVisitor.get_first_from_queue(
|
|
580
509
|
logger, log_sampled, log_lock_try, lock_key,
|
|
581
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
510
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
582
511
|
waiting_priority
|
|
583
512
|
)
|
|
584
513
|
|
|
@@ -598,102 +527,43 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
598
527
|
# Step ROLLBACK: the lock can not be obtained now. retry!
|
|
599
528
|
log_lock_obstacle(
|
|
600
529
|
rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
|
|
601
|
-
acquirer_id, host_id, access_strategy,
|
|
530
|
+
acquirer_id, host_id, access_strategy, rw_mode,
|
|
602
531
|
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
603
532
|
)
|
|
604
533
|
inter_result = obstacle
|
|
605
534
|
elsif read_mode
|
|
606
535
|
# NOTE: required lock is free and ready to be acquired! acquire! (read lock)
|
|
607
536
|
|
|
608
|
-
#
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
# Step 6.3 (RW): register the read lock (expiration time in redis time)
|
|
615
|
-
transact.call('ZADD', lock_readers_key, redis_time + ttl.to_i, acquirer_id)
|
|
616
|
-
|
|
617
|
-
# Step 6.4 (RW): the registry should live as long as the longest read lock
|
|
618
|
-
# - NX + GT => sets the registry TTL to max(current TTL, read lock TTL);
|
|
619
|
-
# - in order to prevent "infinite registries" after process crashes;
|
|
620
|
-
transact.call('PEXPIRE', lock_readers_key, ttl, 'NX')
|
|
621
|
-
transact.call('PEXPIRE', lock_readers_key, ttl, 'GT')
|
|
622
|
-
|
|
623
|
-
# Step 6.5 (RW): store the read lock data (the same structure as the write lock data
|
|
624
|
-
# without `acq_id`/`hst_id`: both are derived from the read lock acquirer id that is
|
|
625
|
-
# a part of the data key and the registry member, so the info adds them on formatting)
|
|
626
|
-
# - drop the data of the expired read lock of the current acquirer (if any);
|
|
627
|
-
# - the read lock data lives as long as the read lock;
|
|
628
|
-
transact.call('DEL', read_lock_key)
|
|
629
|
-
transact.call(
|
|
630
|
-
'HSET',
|
|
631
|
-
read_lock_key,
|
|
632
|
-
'ts', timestamp = Time.now.to_f,
|
|
633
|
-
'ini_ttl', ttl,
|
|
634
|
-
*(meta.to_a if meta != nil) # steep:ignore
|
|
537
|
+
# Steps 6.1-6.6 (RW): see `transact_read_lock_obtaining`
|
|
538
|
+
timestamp = Time.now.to_f
|
|
539
|
+
# @type var timestamp: Float
|
|
540
|
+
transact_read_lock_obtaining(
|
|
541
|
+
transact, request_queue, lock_readers_key, read_lock_key, lock_name, acquirer_id, ttl,
|
|
542
|
+
lock_index_enabled, lock_index_grace_period, meta, redis_time, timestamp
|
|
635
543
|
)
|
|
636
|
-
transact.call('PEXPIRE', read_lock_key, ttl)
|
|
637
|
-
|
|
638
|
-
# Step 6.6 (lock index): the lock name lives in the lock index as long as the read lock
|
|
639
|
-
# (the upper bound of the read lock lifetime: client time + ttl + grace period);
|
|
640
|
-
# - in the same transaction: an index entry dropped between the queueing and this
|
|
641
|
-
# transaction (`#unlock`, `#clear_locks`) is restored with the read lock;
|
|
642
|
-
# - GT => the score is never decreased (other keys of the lock can live longer);
|
|
643
|
-
# - only with the enabled lock index (the last command of the transaction);
|
|
644
|
-
if lock_index_enabled
|
|
645
|
-
transact.call(
|
|
646
|
-
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'GT',
|
|
647
|
-
RedisQueuedLocks::Resource.lock_index_time + ttl.to_i + lock_index_grace_period,
|
|
648
|
-
lock_name
|
|
649
|
-
)
|
|
650
|
-
end
|
|
651
544
|
|
|
652
545
|
inter_result = :lock_obtaining
|
|
653
546
|
|
|
654
547
|
LogVisitor.obtain__free_to_acquire(
|
|
655
548
|
logger, log_sampled, log_lock_try, lock_key,
|
|
656
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
549
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
657
550
|
)
|
|
658
551
|
else
|
|
659
552
|
# NOTE: required lock is free and ready to be acquired! acquire! (write lock)
|
|
660
553
|
|
|
661
|
-
#
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
lock_key,
|
|
668
|
-
'acq_id', acquirer_id,
|
|
669
|
-
'hst_id', host_id,
|
|
670
|
-
'ts', timestamp = Time.now.to_f,
|
|
671
|
-
'ini_ttl', ttl,
|
|
672
|
-
*(meta.to_a if meta != nil) # steep:ignore
|
|
554
|
+
# Steps 6.1-6.4: see `transact_write_lock_obtaining`
|
|
555
|
+
timestamp = Time.now.to_f
|
|
556
|
+
# @type var timestamp: Float
|
|
557
|
+
transact_write_lock_obtaining(
|
|
558
|
+
transact, lock_key, request_queue, lock_name, acquirer_id, host_id, ttl,
|
|
559
|
+
lock_index_enabled, lock_index_grace_period, meta, timestamp
|
|
673
560
|
)
|
|
674
561
|
|
|
675
|
-
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
|
676
|
-
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
|
677
|
-
|
|
678
|
-
# Step 6.4 (lock index): the lock name lives in the lock index as long as the lock
|
|
679
|
-
# (the upper bound of the lock lifetime: client time + ttl + grace period);
|
|
680
|
-
# - in the same transaction: an index entry dropped between the queueing and this
|
|
681
|
-
# transaction (`#unlock`, `#clear_locks`) is restored with the lock;
|
|
682
|
-
# - GT => the score is never decreased (other keys of the lock can live longer);
|
|
683
|
-
# - only with the enabled lock index (the last command of the transaction);
|
|
684
|
-
if lock_index_enabled
|
|
685
|
-
transact.call(
|
|
686
|
-
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'GT',
|
|
687
|
-
RedisQueuedLocks::Resource.lock_index_time + ttl.to_i + lock_index_grace_period,
|
|
688
|
-
lock_name
|
|
689
|
-
)
|
|
690
|
-
end
|
|
691
|
-
|
|
692
562
|
inter_result = :lock_obtaining
|
|
693
563
|
|
|
694
564
|
LogVisitor.obtain__free_to_acquire(
|
|
695
565
|
logger, log_sampled, log_lock_try, lock_key,
|
|
696
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
566
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
697
567
|
)
|
|
698
568
|
end
|
|
699
569
|
end
|
|
@@ -879,6 +749,374 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
879
749
|
|
|
880
750
|
private
|
|
881
751
|
|
|
752
|
+
# (SP-Conflict) The status of the conflict of the lock request with the lock that is already
|
|
753
|
+
# obtained by the current acquirer (Step CHECK of the attempt).
|
|
754
|
+
#
|
|
755
|
+
# @param conflict_strategy [Symbol]
|
|
756
|
+
# @param lock_upgrade [Boolean]
|
|
757
|
+
# The conflict is a read-to-write lock upgrade: it can not "work through" (the read lock is not
|
|
758
|
+
# exclusive, so the "work through" logic will work without an exclusive lock).
|
|
759
|
+
# @return [Symbol]
|
|
760
|
+
#
|
|
761
|
+
# @api private
|
|
762
|
+
# @since 1.23.0
|
|
763
|
+
def same_process_conflict_status(conflict_strategy, lock_upgrade)
|
|
764
|
+
case conflict_strategy
|
|
765
|
+
when :work_through
|
|
766
|
+
# <SP-Conflict Moment>: work through => exit
|
|
767
|
+
lock_upgrade ? :conflict_lock_upgrade : :conflict_work_through
|
|
768
|
+
when :extendable_work_through
|
|
769
|
+
# <SP-Conflict Moment>: extendable_work_through => extend the lock pttl and exit
|
|
770
|
+
lock_upgrade ? :conflict_lock_upgrade : :extendable_conflict_work_through
|
|
771
|
+
when :dead_locking
|
|
772
|
+
# <SP-Conflict Moment>: dead_locking => exit and fail
|
|
773
|
+
:conflict_dead_lock
|
|
774
|
+
else
|
|
775
|
+
# <SP-Conflict Moment>: wait_for_lock => obtain a lock in classic way
|
|
776
|
+
# - unknown status => work in classic way <wait_for_lock>
|
|
777
|
+
# - it is a case when the new status is added to the code base in the past
|
|
778
|
+
# but is forgotten to be added here;
|
|
779
|
+
:conflict_wait_for_lock
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# (SP-Conflict) Adds the commands of the write lock extension (reentrant
|
|
784
|
+
# `:extendable_work_through`) to the given transaction.
|
|
785
|
+
#
|
|
786
|
+
# NOTE: (transaction layout) 4 commands (5 with the enabled lock index): EVAL, HINCRBY, HINCRBY,
|
|
787
|
+
# HSET (+ ZADD XX INCR); the replies are read by the attempt (`try_to_lock` Step 7);
|
|
788
|
+
#
|
|
789
|
+
# @param transact [RedisClient::Multi]
|
|
790
|
+
# @param lock_key [String]
|
|
791
|
+
# @param lock_name [String] The lock name (the member of the lock index).
|
|
792
|
+
# @param ttl [Integer] The extension (in milliseconds).
|
|
793
|
+
# @param lock_index_enabled [Boolean]
|
|
794
|
+
# @param spc_processed_timestamp [Float]
|
|
795
|
+
# @return [void]
|
|
796
|
+
#
|
|
797
|
+
# @api private
|
|
798
|
+
# @since 1.23.0
|
|
799
|
+
def transact_write_lock_extension(
|
|
800
|
+
transact,
|
|
801
|
+
lock_key,
|
|
802
|
+
lock_name,
|
|
803
|
+
ttl,
|
|
804
|
+
lock_index_enabled,
|
|
805
|
+
spc_processed_timestamp
|
|
806
|
+
)
|
|
807
|
+
# SPCF Step 1: extend the lock pttl
|
|
808
|
+
# - [REDIS RESULT]: in normal cases should return the last script command value
|
|
809
|
+
# - for the current script should return:
|
|
810
|
+
# <1> => timeout was set;
|
|
811
|
+
# <0> => timeount was not set;
|
|
812
|
+
transact.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, ttl)
|
|
813
|
+
# SPCF Step <Meta>: store conflict-state additionals in lock metadata:
|
|
814
|
+
# SPCF Step 2: (lock meta-data)
|
|
815
|
+
# - add the added ttl to reflect the real lock TTL in info;
|
|
816
|
+
# - [REDIS RESULT]: in normal cases should return the value of <ttl> key
|
|
817
|
+
# - for non-existent key value starts from <0> (zero)
|
|
818
|
+
transact.call('HINCRBY', lock_key, 'spc_ext_ttl', ttl)
|
|
819
|
+
# SPCF Step 3: (lock meta-data)
|
|
820
|
+
# - increment the conflcit counter in order to remember
|
|
821
|
+
# how many times dead lock happened;
|
|
822
|
+
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
823
|
+
# - for non-existent key starts from 0
|
|
824
|
+
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
825
|
+
# SPCF Step 4: (lock meta-data)
|
|
826
|
+
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
827
|
+
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
828
|
+
# were added/changed;
|
|
829
|
+
transact.call(
|
|
830
|
+
'HSET',
|
|
831
|
+
lock_key,
|
|
832
|
+
'l_spc_ext_ts', spc_processed_timestamp,
|
|
833
|
+
'l_spc_ext_ini_ttl', ttl
|
|
834
|
+
)
|
|
835
|
+
# SPCF Step 5: (lock index), see `transact_lock_index_extension`
|
|
836
|
+
transact_lock_index_extension(transact, lock_name, ttl, lock_index_enabled)
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
# (SP-Conflict) Adds the commands of the read lock extension (reentrant
|
|
840
|
+
# `:extendable_work_through`) to the given transaction.
|
|
841
|
+
#
|
|
842
|
+
# NOTE: (transaction layout) `READ_LOCK_EXTENSION_COMMANDS` commands (+ one command with
|
|
843
|
+
# the enabled lock index): ZADD XX INCR is the first one (its `nil` reply = the read lock is
|
|
844
|
+
# expired and removed from the registry during the extension);
|
|
845
|
+
#
|
|
846
|
+
# @param transact [RedisClient::Multi]
|
|
847
|
+
# @param lock_readers_key [String]
|
|
848
|
+
# @param read_lock_key [String,NilClass]
|
|
849
|
+
# @param lock_name [String] The lock name (the member of the lock index).
|
|
850
|
+
# @param acquirer_id [String]
|
|
851
|
+
# @param ttl [Integer,NilClass] The extension (in milliseconds).
|
|
852
|
+
# @param lock_index_enabled [Boolean]
|
|
853
|
+
# @param own_read_lock_expiration [Float,NilClass]
|
|
854
|
+
# @param redis_time [Integer] Redis server time (in milliseconds).
|
|
855
|
+
# @param spc_processed_timestamp [Float]
|
|
856
|
+
# @return [void]
|
|
857
|
+
#
|
|
858
|
+
# @api private
|
|
859
|
+
# @since 1.23.0
|
|
860
|
+
def transact_read_lock_extension(
|
|
861
|
+
transact,
|
|
862
|
+
lock_readers_key,
|
|
863
|
+
read_lock_key,
|
|
864
|
+
lock_name,
|
|
865
|
+
acquirer_id,
|
|
866
|
+
ttl,
|
|
867
|
+
lock_index_enabled,
|
|
868
|
+
own_read_lock_expiration,
|
|
869
|
+
redis_time,
|
|
870
|
+
spc_processed_timestamp
|
|
871
|
+
)
|
|
872
|
+
# SPCF Step 1 (read lock): extend the read lock of the current acquirer
|
|
873
|
+
# - [REDIS RESULT]: the new read lock expiration time
|
|
874
|
+
# (or nil if the read lock has been removed from the registry);
|
|
875
|
+
transact.call('ZADD', lock_readers_key, 'XX', 'INCR', ttl, acquirer_id)
|
|
876
|
+
# SPCF Step 2 (read lock): the registry should live as long as the longest read lock
|
|
877
|
+
# - NX + GT => sets the registry TTL to max(current TTL, extended read lock TTL);
|
|
878
|
+
extended_read_lock_ttl = (own_read_lock_expiration.to_f + ttl.to_i - redis_time).ceil
|
|
879
|
+
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'NX')
|
|
880
|
+
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'GT')
|
|
881
|
+
# SPCF Step 3 (read lock data): the same reentrant lock data as for the write lock
|
|
882
|
+
transact.call('HINCRBY', read_lock_key, 'spc_ext_ttl', ttl)
|
|
883
|
+
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
884
|
+
transact.call(
|
|
885
|
+
'HSET',
|
|
886
|
+
read_lock_key,
|
|
887
|
+
'l_spc_ext_ts', spc_processed_timestamp,
|
|
888
|
+
'l_spc_ext_ini_ttl', ttl
|
|
889
|
+
)
|
|
890
|
+
# SPCF Step 4 (read lock data): the read lock data lives as long as the read lock
|
|
891
|
+
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'NX')
|
|
892
|
+
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'GT')
|
|
893
|
+
# SPCF Step 5: (lock index), see `transact_lock_index_extension`
|
|
894
|
+
transact_lock_index_extension(transact, lock_name, ttl, lock_index_enabled)
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# (SP-Conflict) (lock index) The lock (write or read) lives longer by the extension:
|
|
898
|
+
# - the lock index score is increased by the extension (the upper bound of the lock
|
|
899
|
+
# lifetime stays the upper bound);
|
|
900
|
+
# - XX => locks without the index entry (of older RQL versions) are not indexed with
|
|
901
|
+
# a wrong score (see `reindex_locks`);
|
|
902
|
+
# - [REDIS RESULT]: the new lock index score (nil for the lock without the index entry);
|
|
903
|
+
# - only with the enabled lock index (the last command of the transaction);
|
|
904
|
+
#
|
|
905
|
+
# @param transact [RedisClient::Multi]
|
|
906
|
+
# @param lock_name [String]
|
|
907
|
+
# @param ttl [Integer,NilClass] The extension (in milliseconds).
|
|
908
|
+
# @param lock_index_enabled [Boolean]
|
|
909
|
+
# @return [void]
|
|
910
|
+
#
|
|
911
|
+
# @api private
|
|
912
|
+
# @since 1.23.0
|
|
913
|
+
def transact_lock_index_extension(transact, lock_name, ttl, lock_index_enabled)
|
|
914
|
+
return unless lock_index_enabled
|
|
915
|
+
|
|
916
|
+
transact.call(
|
|
917
|
+
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'XX', 'INCR', ttl, lock_name
|
|
918
|
+
)
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# (SP-Conflict) Adds the commands of the write lock "work through" (reentrant `:work_through`)
|
|
922
|
+
# to the given transaction: the reentrant lock data only.
|
|
923
|
+
#
|
|
924
|
+
# @param transact [RedisClient::Multi]
|
|
925
|
+
# @param lock_key [String]
|
|
926
|
+
# @param spc_processed_timestamp [Float]
|
|
927
|
+
# @return [void]
|
|
928
|
+
#
|
|
929
|
+
# @api private
|
|
930
|
+
# @since 1.23.0
|
|
931
|
+
def transact_write_lock_work_through(transact, lock_key, spc_processed_timestamp)
|
|
932
|
+
# SPCF Step X: (lock meta-data)
|
|
933
|
+
# - increment the conflcit counter in order to remember
|
|
934
|
+
# how many times dead lock happened;
|
|
935
|
+
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
936
|
+
# - for non-existent key starts from 0
|
|
937
|
+
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
938
|
+
# SPCF Step 4: (lock meta-data)
|
|
939
|
+
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
940
|
+
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
941
|
+
# were added/changed;
|
|
942
|
+
transact.call(
|
|
943
|
+
'HSET',
|
|
944
|
+
lock_key,
|
|
945
|
+
'l_spc_ts', spc_processed_timestamp
|
|
946
|
+
)
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# (SP-Conflict) Adds the commands of the read lock "work through" (reentrant `:work_through`)
|
|
950
|
+
# to the given transaction: the reentrant lock data only.
|
|
951
|
+
#
|
|
952
|
+
# @param transact [RedisClient::Multi]
|
|
953
|
+
# @param read_lock_key [String,NilClass]
|
|
954
|
+
# @param own_read_lock_expiration [Float,NilClass]
|
|
955
|
+
# @param redis_time [Integer] Redis server time (in milliseconds).
|
|
956
|
+
# @param spc_processed_timestamp [Float]
|
|
957
|
+
# @return [void]
|
|
958
|
+
#
|
|
959
|
+
# @api private
|
|
960
|
+
# @since 1.23.0
|
|
961
|
+
def transact_read_lock_work_through(
|
|
962
|
+
transact,
|
|
963
|
+
read_lock_key,
|
|
964
|
+
own_read_lock_expiration,
|
|
965
|
+
redis_time,
|
|
966
|
+
spc_processed_timestamp
|
|
967
|
+
)
|
|
968
|
+
# SPCF Step X (read lock data): the same reentrant lock data as for the write lock
|
|
969
|
+
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
970
|
+
transact.call('HSET', read_lock_key, 'l_spc_ts', spc_processed_timestamp)
|
|
971
|
+
# SPCF Step X (read lock data): (NX) the read lock data should not outlive the read lock
|
|
972
|
+
# (it is the case when the read lock data is dropped during the read lock life);
|
|
973
|
+
transact.call(
|
|
974
|
+
'PEXPIRE',
|
|
975
|
+
read_lock_key,
|
|
976
|
+
(own_read_lock_expiration.to_f - redis_time).ceil,
|
|
977
|
+
'NX'
|
|
978
|
+
)
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
# Adds the commands of the read lock obtaining to the given transaction.
|
|
982
|
+
#
|
|
983
|
+
# @param transact [RedisClient::Multi]
|
|
984
|
+
# @param request_queue [String]
|
|
985
|
+
# @param lock_readers_key [String]
|
|
986
|
+
# @param read_lock_key [String,NilClass]
|
|
987
|
+
# @param lock_name [String] The lock name (the member of the lock index).
|
|
988
|
+
# @param acquirer_id [String]
|
|
989
|
+
# @param ttl [Integer,NilClass]
|
|
990
|
+
# @param lock_index_enabled [Boolean]
|
|
991
|
+
# @param lock_index_grace_period [Integer] In milliseconds.
|
|
992
|
+
# @param meta [NilClass,Hash<String|Symbol,Any>]
|
|
993
|
+
# @param redis_time [Integer] Redis server time (in milliseconds).
|
|
994
|
+
# @param timestamp [Float]
|
|
995
|
+
# @return [void]
|
|
996
|
+
#
|
|
997
|
+
# @api private
|
|
998
|
+
# @since 1.23.0
|
|
999
|
+
def transact_read_lock_obtaining(
|
|
1000
|
+
transact,
|
|
1001
|
+
request_queue,
|
|
1002
|
+
lock_readers_key,
|
|
1003
|
+
read_lock_key,
|
|
1004
|
+
lock_name,
|
|
1005
|
+
acquirer_id,
|
|
1006
|
+
ttl,
|
|
1007
|
+
lock_index_enabled,
|
|
1008
|
+
lock_index_grace_period,
|
|
1009
|
+
meta,
|
|
1010
|
+
redis_time,
|
|
1011
|
+
timestamp
|
|
1012
|
+
)
|
|
1013
|
+
# Step 6.1: remove our acquirer from waiting queue
|
|
1014
|
+
transact.call('ZREM', request_queue, acquirer_id)
|
|
1015
|
+
|
|
1016
|
+
# Step 6.2 (RW): drop expired read locks from the readers registry
|
|
1017
|
+
transact.call('ZREMRANGEBYSCORE', lock_readers_key, '-inf', "(#{redis_time}")
|
|
1018
|
+
|
|
1019
|
+
# Step 6.3 (RW): register the read lock (expiration time in redis time)
|
|
1020
|
+
transact.call('ZADD', lock_readers_key, redis_time + ttl.to_i, acquirer_id)
|
|
1021
|
+
|
|
1022
|
+
# Step 6.4 (RW): the registry should live as long as the longest read lock
|
|
1023
|
+
# - NX + GT => sets the registry TTL to max(current TTL, read lock TTL);
|
|
1024
|
+
# - in order to prevent "infinite registries" after process crashes;
|
|
1025
|
+
transact.call('PEXPIRE', lock_readers_key, ttl, 'NX')
|
|
1026
|
+
transact.call('PEXPIRE', lock_readers_key, ttl, 'GT')
|
|
1027
|
+
|
|
1028
|
+
# Step 6.5 (RW): store the read lock data (the same structure as the write lock data
|
|
1029
|
+
# without `acq_id`/`hst_id`: both are derived from the read lock acquirer id that is
|
|
1030
|
+
# a part of the data key and the registry member, so the info adds them on formatting)
|
|
1031
|
+
# - drop the data of the expired read lock of the current acquirer (if any);
|
|
1032
|
+
# - the read lock data lives as long as the read lock;
|
|
1033
|
+
transact.call('DEL', read_lock_key)
|
|
1034
|
+
transact.call(
|
|
1035
|
+
'HSET',
|
|
1036
|
+
read_lock_key,
|
|
1037
|
+
'ts', timestamp,
|
|
1038
|
+
'ini_ttl', ttl,
|
|
1039
|
+
*(meta.to_a if meta != nil) # steep:ignore
|
|
1040
|
+
)
|
|
1041
|
+
transact.call('PEXPIRE', read_lock_key, ttl)
|
|
1042
|
+
|
|
1043
|
+
# Step 6.6 (lock index): the lock name lives in the lock index as long as the read lock
|
|
1044
|
+
# (the upper bound of the read lock lifetime: client time + ttl + grace period);
|
|
1045
|
+
# - in the same transaction: an index entry dropped between the queueing and this
|
|
1046
|
+
# transaction (`#unlock`, `#clear_locks`) is restored with the read lock;
|
|
1047
|
+
# - GT => the score is never decreased (other keys of the lock can live longer);
|
|
1048
|
+
# - only with the enabled lock index (the last command of the transaction);
|
|
1049
|
+
if lock_index_enabled
|
|
1050
|
+
transact.call(
|
|
1051
|
+
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'GT',
|
|
1052
|
+
RedisQueuedLocks::Resource.lock_index_time + ttl.to_i + lock_index_grace_period,
|
|
1053
|
+
lock_name
|
|
1054
|
+
)
|
|
1055
|
+
end
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
# Adds the commands of the write lock obtaining to the given transaction.
|
|
1059
|
+
#
|
|
1060
|
+
# @param transact [RedisClient::Multi]
|
|
1061
|
+
# @param lock_key [String]
|
|
1062
|
+
# @param request_queue [String]
|
|
1063
|
+
# @param lock_name [String] The lock name (the member of the lock index).
|
|
1064
|
+
# @param acquirer_id [String]
|
|
1065
|
+
# @param host_id [String]
|
|
1066
|
+
# @param ttl [Integer,NilClass]
|
|
1067
|
+
# @param lock_index_enabled [Boolean]
|
|
1068
|
+
# @param lock_index_grace_period [Integer] In milliseconds.
|
|
1069
|
+
# @param meta [NilClass,Hash<String|Symbol,Any>]
|
|
1070
|
+
# @param timestamp [Float]
|
|
1071
|
+
# @return [void]
|
|
1072
|
+
#
|
|
1073
|
+
# @api private
|
|
1074
|
+
# @since 1.23.0
|
|
1075
|
+
def transact_write_lock_obtaining(
|
|
1076
|
+
transact,
|
|
1077
|
+
lock_key,
|
|
1078
|
+
request_queue,
|
|
1079
|
+
lock_name,
|
|
1080
|
+
acquirer_id,
|
|
1081
|
+
host_id,
|
|
1082
|
+
ttl,
|
|
1083
|
+
lock_index_enabled,
|
|
1084
|
+
lock_index_grace_period,
|
|
1085
|
+
meta,
|
|
1086
|
+
timestamp
|
|
1087
|
+
)
|
|
1088
|
+
# Step 6.1: remove our acquirer from waiting queue
|
|
1089
|
+
transact.call('ZREM', request_queue, acquirer_id)
|
|
1090
|
+
|
|
1091
|
+
# Step 6.2: acquire a lock and store an info about the acquirer and host
|
|
1092
|
+
transact.call(
|
|
1093
|
+
'HSET',
|
|
1094
|
+
lock_key,
|
|
1095
|
+
'acq_id', acquirer_id,
|
|
1096
|
+
'hst_id', host_id,
|
|
1097
|
+
'ts', timestamp,
|
|
1098
|
+
'ini_ttl', ttl,
|
|
1099
|
+
*(meta.to_a if meta != nil) # steep:ignore
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
|
1103
|
+
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
|
1104
|
+
|
|
1105
|
+
# Step 6.4 (lock index): the lock name lives in the lock index as long as the lock
|
|
1106
|
+
# (the upper bound of the lock lifetime: client time + ttl + grace period);
|
|
1107
|
+
# - in the same transaction: an index entry dropped between the queueing and this
|
|
1108
|
+
# transaction (`#unlock`, `#clear_locks`) is restored with the lock;
|
|
1109
|
+
# - GT => the score is never decreased (other keys of the lock can live longer);
|
|
1110
|
+
# - only with the enabled lock index (the last command of the transaction);
|
|
1111
|
+
if lock_index_enabled
|
|
1112
|
+
transact.call(
|
|
1113
|
+
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'GT',
|
|
1114
|
+
RedisQueuedLocks::Resource.lock_index_time + ttl.to_i + lock_index_grace_period,
|
|
1115
|
+
lock_name
|
|
1116
|
+
)
|
|
1117
|
+
end
|
|
1118
|
+
end
|
|
1119
|
+
|
|
882
1120
|
# Queues the lock request and reads the lock queue state (Steps 2.1, 1, 3 and 3.2-3.4 of
|
|
883
1121
|
# the attempt): the commands are added to the given pipeline (one round trip with other
|
|
884
1122
|
# pipelined commands).
|
|
@@ -1179,7 +1417,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1179
1417
|
# @param acquirer_id [String]
|
|
1180
1418
|
# @param host_id [String]
|
|
1181
1419
|
# @param access_strategy [Symbol]
|
|
1182
|
-
# @param
|
|
1420
|
+
# @param rw_mode [Symbol]
|
|
1183
1421
|
# @param waiting_acquirer [String,NilClass]
|
|
1184
1422
|
# @param waiting_priority [Integer,NilClass]
|
|
1185
1423
|
# The priority of the first request (decoded for logs only: `nil` without logs).
|
|
@@ -1204,7 +1442,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1204
1442
|
acquirer_id,
|
|
1205
1443
|
host_id,
|
|
1206
1444
|
access_strategy,
|
|
1207
|
-
|
|
1445
|
+
rw_mode,
|
|
1208
1446
|
waiting_acquirer,
|
|
1209
1447
|
waiting_priority,
|
|
1210
1448
|
opposite_waiting_request,
|
|
@@ -1214,12 +1452,12 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1214
1452
|
when :dead_score_reached
|
|
1215
1453
|
LogVisitor.exit__queue_ttl_reached(
|
|
1216
1454
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1217
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1455
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
1218
1456
|
)
|
|
1219
1457
|
when :acquirer_is_not_first_in_queue
|
|
1220
1458
|
LogVisitor.exit__no_first(
|
|
1221
1459
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1222
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1460
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
1223
1461
|
waiting_priority,
|
|
1224
1462
|
# NOTE: lock data is extracted for logs only
|
|
1225
1463
|
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
|
|
@@ -1231,20 +1469,20 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1231
1469
|
if obstacle == :write_request_is_ahead
|
|
1232
1470
|
LogVisitor.exit__write_request_ahead(
|
|
1233
1471
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1234
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1472
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
1235
1473
|
ahead_request[0], ahead_priority
|
|
1236
1474
|
)
|
|
1237
1475
|
else
|
|
1238
1476
|
LogVisitor.exit__read_request_ahead(
|
|
1239
1477
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1240
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1478
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
1241
1479
|
ahead_request[0], ahead_priority
|
|
1242
1480
|
)
|
|
1243
1481
|
end
|
|
1244
1482
|
when :lock_is_still_acquired
|
|
1245
1483
|
LogVisitor.exit__lock_still_obtained(
|
|
1246
1484
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1247
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1485
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
1248
1486
|
waiting_acquirer, waiting_priority,
|
|
1249
1487
|
current_lock_obtainer, #: String
|
|
1250
1488
|
# NOTE: lock data is extracted for logs only
|
|
@@ -1253,7 +1491,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1253
1491
|
when :read_lock_is_still_acquired
|
|
1254
1492
|
LogVisitor.exit__read_lock_still_obtained(
|
|
1255
1493
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1256
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1494
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
1257
1495
|
waiting_priority
|
|
1258
1496
|
)
|
|
1259
1497
|
end
|
|
@@ -1303,7 +1541,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1303
1541
|
# them at the same step (after the queueing log events, see `log_dropped_requests`);
|
|
1304
1542
|
#
|
|
1305
1543
|
# @param rconn [RedisClient]
|
|
1306
|
-
# @param
|
|
1544
|
+
# @param rw_mode [Symbol]
|
|
1307
1545
|
# @param request_queue [String]
|
|
1308
1546
|
# @param opposite_request_queue [String]
|
|
1309
1547
|
# @param acquirer_id [String]
|
|
@@ -1325,7 +1563,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1325
1563
|
# rubocop:disable Metrics/MethodLength
|
|
1326
1564
|
def drop_dead_requests_ahead(
|
|
1327
1565
|
rconn,
|
|
1328
|
-
|
|
1566
|
+
rw_mode,
|
|
1329
1567
|
request_queue,
|
|
1330
1568
|
opposite_request_queue,
|
|
1331
1569
|
acquirer_id,
|
|
@@ -1337,66 +1575,30 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1337
1575
|
)
|
|
1338
1576
|
return unless access_strategy == :queued
|
|
1339
1577
|
|
|
1340
|
-
read_mode = (
|
|
1578
|
+
read_mode = (rw_mode == :read)
|
|
1341
1579
|
round_trips = 0
|
|
1342
1580
|
# NOTE: allocated by the first removal only (no allocations while nothing dead blocks)
|
|
1343
1581
|
# @type var dropped_requests: Array[[String, Integer]]?
|
|
1344
1582
|
dropped_requests = nil
|
|
1345
1583
|
|
|
1346
1584
|
while round_trips < MAX_DEAD_REQUEST_REMOVALS
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
|
1585
|
+
dead_requests = find_dead_requests_ahead(
|
|
1586
|
+
read_mode, access_strategy, acquirer_id, acquirer_position, acquirer_dead_score,
|
|
1587
|
+
waiting_request, opposite_waiting_request
|
|
1367
1588
|
)
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
end
|
|
1372
|
-
break unless dead_head_priority || dead_ahead_priority
|
|
1589
|
+
break unless dead_requests
|
|
1590
|
+
|
|
1591
|
+
dead_head, dead_head_priority, dead_ahead, dead_ahead_priority = dead_requests
|
|
1373
1592
|
|
|
1374
|
-
# NOTE: (pipeline layout)
|
|
1375
|
-
#
|
|
1376
|
-
# ZRANGE WITHSCORES (the head of the request queue) and [-1] ZRANGE WITHSCORES (the head of
|
|
1593
|
+
# NOTE: (pipeline layout) see `pipeline_dead_requests_ahead_removal`, then [-2] ZRANGE
|
|
1594
|
+
# WITHSCORES (the head of the request queue) and [-1] ZRANGE WITHSCORES (the head of
|
|
1377
1595
|
# the opposite request queue);
|
|
1378
1596
|
# @type var queue_state: Array[untyped]
|
|
1379
1597
|
queue_state = rconn.pipelined do |pipeline|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
|
1598
|
+
pipeline_dead_requests_ahead_removal(
|
|
1599
|
+
pipeline, request_queue, opposite_request_queue, acquirer_dead_score,
|
|
1600
|
+
dead_head, dead_head_priority, dead_ahead, dead_ahead_priority
|
|
1601
|
+
)
|
|
1400
1602
|
pipeline.call('ZRANGE', request_queue, '0', '0', 'WITHSCORES')
|
|
1401
1603
|
pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
|
|
1402
1604
|
end
|
|
@@ -1414,6 +1616,110 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1414
1616
|
end
|
|
1415
1617
|
# rubocop:enable Metrics/MethodLength
|
|
1416
1618
|
|
|
1619
|
+
# (priorities) The dead lock requests that block the current lock request (see
|
|
1620
|
+
# `drop_dead_requests_ahead`, the same order as in `lock_obstacle`): the head of the write lock
|
|
1621
|
+
# queue (write lock requests only) and the earlier lock request of the opposite mode; a live head
|
|
1622
|
+
# of the write lock queue stops the removal (the attempt waits for it).
|
|
1623
|
+
#
|
|
1624
|
+
# @param read_mode [Boolean]
|
|
1625
|
+
# @param access_strategy [Symbol]
|
|
1626
|
+
# @param acquirer_id [String]
|
|
1627
|
+
# @param acquirer_position [Numeric]
|
|
1628
|
+
# @param acquirer_dead_score [Float]
|
|
1629
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
1630
|
+
# @param waiting_request [Array<String,Float>,NilClass] The head of the request queue.
|
|
1631
|
+
# @param opposite_waiting_request [Array<String,Float>,NilClass]
|
|
1632
|
+
# The head of the opposite request queue.
|
|
1633
|
+
# @return [Array<Array<String,Float>,Integer,NilClass>,NilClass]
|
|
1634
|
+
# `[dead_head, dead_head_priority, dead_ahead, dead_ahead_priority]` (one of the pairs can be
|
|
1635
|
+
# `nil`) or `nil` when no dead lock request blocks the current one.
|
|
1636
|
+
#
|
|
1637
|
+
# @api private
|
|
1638
|
+
# @since 1.23.0
|
|
1639
|
+
def find_dead_requests_ahead(
|
|
1640
|
+
read_mode,
|
|
1641
|
+
access_strategy,
|
|
1642
|
+
acquirer_id,
|
|
1643
|
+
acquirer_position,
|
|
1644
|
+
acquirer_dead_score,
|
|
1645
|
+
waiting_request,
|
|
1646
|
+
opposite_waiting_request
|
|
1647
|
+
)
|
|
1648
|
+
# @type var dead_head: [String, Float]?
|
|
1649
|
+
dead_head = nil
|
|
1650
|
+
# @type var dead_head_priority: Integer?
|
|
1651
|
+
dead_head_priority = nil
|
|
1652
|
+
if !read_mode && waiting_request && waiting_request[0] != acquirer_id
|
|
1653
|
+
dead_head_priority = dead_request_priority(waiting_request, acquirer_dead_score)
|
|
1654
|
+
return unless dead_head_priority
|
|
1655
|
+
|
|
1656
|
+
dead_head = waiting_request
|
|
1657
|
+
end
|
|
1658
|
+
# @type var dead_ahead: [String, Float]?
|
|
1659
|
+
dead_ahead = nil
|
|
1660
|
+
# @type var dead_ahead_priority: Integer?
|
|
1661
|
+
dead_ahead_priority = nil
|
|
1662
|
+
if find_opposite_request_ahead(
|
|
1663
|
+
access_strategy, opposite_waiting_request, acquirer_position, acquirer_id
|
|
1664
|
+
)
|
|
1665
|
+
ahead_request = opposite_waiting_request #: [String, Float]
|
|
1666
|
+
dead_ahead_priority = dead_request_priority(ahead_request, acquirer_dead_score)
|
|
1667
|
+
dead_ahead = ahead_request if dead_ahead_priority
|
|
1668
|
+
end
|
|
1669
|
+
return unless dead_head_priority || dead_ahead_priority
|
|
1670
|
+
|
|
1671
|
+
[dead_head, dead_head_priority, dead_ahead, dead_ahead_priority]
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1674
|
+
# (priorities) Adds the removal of the dead blocking lock requests (see
|
|
1675
|
+
# `find_dead_requests_ahead`) to the given pipeline: ZREMRANGEBYSCORE of both queues for each
|
|
1676
|
+
# dropped band (one or two bands) and for the requests at `-inf` (a dead blocking request at
|
|
1677
|
+
# `-inf` only).
|
|
1678
|
+
#
|
|
1679
|
+
# @param pipeline [RedisClient::Pipeline]
|
|
1680
|
+
# @param request_queue [String]
|
|
1681
|
+
# @param opposite_request_queue [String]
|
|
1682
|
+
# @param acquirer_dead_score [Float]
|
|
1683
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
1684
|
+
# @param dead_head [Array<String,Float>,NilClass]
|
|
1685
|
+
# @param dead_head_priority [Integer,NilClass]
|
|
1686
|
+
# @param dead_ahead [Array<String,Float>,NilClass]
|
|
1687
|
+
# @param dead_ahead_priority [Integer,NilClass]
|
|
1688
|
+
# @return [void]
|
|
1689
|
+
#
|
|
1690
|
+
# @api private
|
|
1691
|
+
# @since 1.23.0
|
|
1692
|
+
def pipeline_dead_requests_ahead_removal(
|
|
1693
|
+
pipeline,
|
|
1694
|
+
request_queue,
|
|
1695
|
+
opposite_request_queue,
|
|
1696
|
+
acquirer_dead_score,
|
|
1697
|
+
dead_head,
|
|
1698
|
+
dead_head_priority,
|
|
1699
|
+
dead_ahead,
|
|
1700
|
+
dead_ahead_priority
|
|
1701
|
+
)
|
|
1702
|
+
if dead_head_priority
|
|
1703
|
+
pipeline_dead_requests_removal(
|
|
1704
|
+
pipeline, request_queue, opposite_request_queue, acquirer_dead_score, dead_head_priority
|
|
1705
|
+
)
|
|
1706
|
+
end
|
|
1707
|
+
if dead_ahead_priority && dead_ahead_priority != dead_head_priority
|
|
1708
|
+
pipeline_dead_requests_removal(
|
|
1709
|
+
pipeline, request_queue, opposite_request_queue, acquirer_dead_score, dead_ahead_priority
|
|
1710
|
+
)
|
|
1711
|
+
end
|
|
1712
|
+
# NOTE: (infinite positions) a dead blocking request at `-inf` (a manual write: out of all
|
|
1713
|
+
# bands, see `RedisQueuedLocks::Resource.lock_request_priority`) is not covered by
|
|
1714
|
+
# the band removal of its decoded priority (it drops the dead requests of that band
|
|
1715
|
+
# only): the requests at `-inf` are dropped from both queues by their position;
|
|
1716
|
+
if (dead_head && dead_head[1] == -Float::INFINITY) ||
|
|
1717
|
+
(dead_ahead && dead_ahead[1] == -Float::INFINITY)
|
|
1718
|
+
pipeline.call('ZREMRANGEBYSCORE', request_queue, '-inf', '-inf')
|
|
1719
|
+
pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, '-inf', '-inf')
|
|
1720
|
+
end
|
|
1721
|
+
end
|
|
1722
|
+
|
|
1417
1723
|
# (priorities) Logs the dead lock requests dropped by `drop_dead_requests_ahead` (the attempt
|
|
1418
1724
|
# and its pre-check log them at the same step: after the queueing log events).
|
|
1419
1725
|
#
|
|
@@ -1425,7 +1731,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1425
1731
|
# @param acquirer_id [String]
|
|
1426
1732
|
# @param host_id [String]
|
|
1427
1733
|
# @param access_strategy [Symbol]
|
|
1428
|
-
# @param
|
|
1734
|
+
# @param rw_mode [Symbol]
|
|
1429
1735
|
# @param dropped_requests [Array<Array<String,Integer>>]
|
|
1430
1736
|
# The acquirer and the priority of each dropped lock request.
|
|
1431
1737
|
# @return [void]
|
|
@@ -1441,7 +1747,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1441
1747
|
acquirer_id,
|
|
1442
1748
|
host_id,
|
|
1443
1749
|
access_strategy,
|
|
1444
|
-
|
|
1750
|
+
rw_mode,
|
|
1445
1751
|
dropped_requests
|
|
1446
1752
|
)
|
|
1447
1753
|
return unless log_sampled && log_lock_try
|
|
@@ -1449,7 +1755,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1449
1755
|
dropped_requests.each do |dead_acquirer_id, dead_priority|
|
|
1450
1756
|
LogVisitor.dead_request_removed(
|
|
1451
1757
|
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
1452
|
-
access_strategy,
|
|
1758
|
+
access_strategy, rw_mode, dead_acquirer_id, dead_priority
|
|
1453
1759
|
)
|
|
1454
1760
|
end
|
|
1455
1761
|
end
|
|
@@ -1493,7 +1799,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1493
1799
|
# @param log_sampled [Boolean]
|
|
1494
1800
|
# @param log_lock_try [Boolean]
|
|
1495
1801
|
# @param lock_key [String]
|
|
1496
|
-
# @param
|
|
1802
|
+
# @param rw_mode [Symbol]
|
|
1497
1803
|
# @param lock_readers_key [String]
|
|
1498
1804
|
# @param request_queue [String]
|
|
1499
1805
|
# @param opposite_request_queue [String]
|
|
@@ -1520,7 +1826,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1520
1826
|
log_sampled,
|
|
1521
1827
|
log_lock_try,
|
|
1522
1828
|
lock_key,
|
|
1523
|
-
|
|
1829
|
+
rw_mode,
|
|
1524
1830
|
lock_readers_key,
|
|
1525
1831
|
request_queue,
|
|
1526
1832
|
opposite_request_queue,
|
|
@@ -1536,7 +1842,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1536
1842
|
fail_fast,
|
|
1537
1843
|
access_strategy
|
|
1538
1844
|
)
|
|
1539
|
-
read_mode = (
|
|
1845
|
+
read_mode = (rw_mode == :read)
|
|
1540
1846
|
# NOTE: the same pipeline layout and the same reply reading as in Step 0.1 of the attempt
|
|
1541
1847
|
# @type var lock_state: Array[untyped]
|
|
1542
1848
|
lock_state = rconn.pipelined do |pipeline|
|
|
@@ -1572,7 +1878,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1572
1878
|
# the step of the attempt (after the queueing log events of the failed pre-check, they
|
|
1573
1879
|
# follow the decision) or before the attempt that continues (it logs its own queueing);
|
|
1574
1880
|
actual_queue_heads = drop_dead_requests_ahead(
|
|
1575
|
-
rconn,
|
|
1881
|
+
rconn, rw_mode, request_queue, opposite_request_queue, acquirer_id,
|
|
1576
1882
|
acquirer_position, acquirer_dead_score, access_strategy, waiting_request,
|
|
1577
1883
|
opposite_waiting_request
|
|
1578
1884
|
)
|
|
@@ -1594,7 +1900,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1594
1900
|
if dropped_requests
|
|
1595
1901
|
log_dropped_requests(
|
|
1596
1902
|
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
1597
|
-
access_strategy,
|
|
1903
|
+
access_strategy, rw_mode, dropped_requests
|
|
1598
1904
|
)
|
|
1599
1905
|
end
|
|
1600
1906
|
return
|
|
@@ -1607,26 +1913,26 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1607
1913
|
# dead requests, the first request, the exit event)
|
|
1608
1914
|
LogVisitor.acq_added_to_queue(
|
|
1609
1915
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1610
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1916
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
|
|
1611
1917
|
)
|
|
1612
1918
|
LogVisitor.remove_expired_acqs(
|
|
1613
1919
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1614
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1920
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
1615
1921
|
)
|
|
1616
1922
|
if dropped_requests
|
|
1617
1923
|
log_dropped_requests(
|
|
1618
1924
|
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
1619
|
-
access_strategy,
|
|
1925
|
+
access_strategy, rw_mode, dropped_requests
|
|
1620
1926
|
)
|
|
1621
1927
|
end
|
|
1622
1928
|
LogVisitor.get_first_from_queue(
|
|
1623
1929
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1624
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1930
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
1625
1931
|
waiting_priority
|
|
1626
1932
|
)
|
|
1627
1933
|
log_lock_obstacle(
|
|
1628
1934
|
rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
|
|
1629
|
-
acquirer_id, host_id, access_strategy,
|
|
1935
|
+
acquirer_id, host_id, access_strategy, rw_mode,
|
|
1630
1936
|
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
1631
1937
|
)
|
|
1632
1938
|
obstacle
|