redis_queued_locks 1.21.0 → 1.23.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 +294 -51
- data/.claude/rules/acquirer.md +16 -11
- 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 +14 -15
- data/.claude/rules/swarm.md +3 -4
- data/.claude/rules/tests.md +9 -9
- data/.claude/rules/type-checking.md +14 -16
- data/.claude/rules/visitors.md +11 -9
- data/.claude/skills/rql-code-review/SKILL.md +129 -47
- data/.ruby-version +1 -1
- data/CHANGELOG.md +122 -245
- data/CLAUDE.md +12 -10
- data/README.md +516 -175
- data/Rakefile +11 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
- 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 +945 -272
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +82 -32
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +15 -13
- data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
- 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 +948 -0
- 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/acquirer.rb +1 -1
- data/lib/redis_queued_locks/client.rb +116 -44
- 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/redis_queued_locks.gemspec +21 -14
- 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 +160 -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/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/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +77 -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 +35 -13
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -56
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -95
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -484
- /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
|
@@ -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
|
|
@@ -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.23.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,11 +133,49 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
120
133
|
# @since 1.20.0
|
|
121
134
|
OPPOSITE_QUEUE_HEAD_REPLY = 4
|
|
122
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
|
+
|
|
163
|
+
# The maximum number of the round trips of one call of `drop_dead_requests_ahead` (the attempt or
|
|
164
|
+
# its pre-check; each round trip drops the dead blocking requests, the head of the write lock
|
|
165
|
+
# queue and the earlier request of the opposite mode, with the dead requests of their priority
|
|
166
|
+
# bands): the attempt fails when more dead lock requests block it (the next attempts drop them).
|
|
167
|
+
#
|
|
168
|
+
# @return [Integer]
|
|
169
|
+
#
|
|
170
|
+
# @api private
|
|
171
|
+
# @since 1.22.0
|
|
172
|
+
MAX_DEAD_REQUEST_REMOVALS = 5
|
|
173
|
+
|
|
123
174
|
# @param redis [RedisClient]
|
|
124
175
|
# @param logger [::Logger,#debug]
|
|
125
176
|
# @param log_lock_try [Boolean]
|
|
126
177
|
# @param lock_key [String]
|
|
127
|
-
# @param
|
|
178
|
+
# @param rw_mode [Symbol] `:read` or `:write`
|
|
128
179
|
# @param lock_key_queue [String] Queue of write lock requests.
|
|
129
180
|
# @param read_lock_key_queue [String] Queue of read lock requests.
|
|
130
181
|
# @param lock_readers_key [String] Registry of read lock holders.
|
|
@@ -135,6 +186,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
135
186
|
# @param acquirer_id [String]
|
|
136
187
|
# @param host_id [String]
|
|
137
188
|
# @param acquirer_position [Numeric]
|
|
189
|
+
# The position of the lock request (the score: the enqueue time shifted by the priority band).
|
|
190
|
+
# @param priority [Integer]
|
|
191
|
+
# The lock request priority (the band of `acquirer_position`).
|
|
138
192
|
# @param ttl [Integer]
|
|
139
193
|
# @param queue_ttl [Integer]
|
|
140
194
|
# @param lock_index_enabled [Boolean]
|
|
@@ -154,14 +208,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
154
208
|
#
|
|
155
209
|
# @api private
|
|
156
210
|
# @since 1.0.0
|
|
157
|
-
# @version 1.
|
|
211
|
+
# @version 1.22.0
|
|
158
212
|
# rubocop:disable Metrics/MethodLength
|
|
159
213
|
def try_to_lock(
|
|
160
214
|
redis,
|
|
161
215
|
logger,
|
|
162
216
|
log_lock_try,
|
|
163
217
|
lock_key,
|
|
164
|
-
|
|
218
|
+
rw_mode,
|
|
165
219
|
lock_key_queue,
|
|
166
220
|
read_lock_key_queue,
|
|
167
221
|
lock_readers_key,
|
|
@@ -170,6 +224,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
170
224
|
acquirer_id,
|
|
171
225
|
host_id,
|
|
172
226
|
acquirer_position,
|
|
227
|
+
priority,
|
|
173
228
|
ttl,
|
|
174
229
|
queue_ttl,
|
|
175
230
|
lock_index_enabled,
|
|
@@ -192,9 +247,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
192
247
|
# Step RW: the mode of the lock that is really held by the acquirer after the attempt
|
|
193
248
|
# (the requested mode or the mode of the already obtained lock for same-process conflicts);
|
|
194
249
|
# @type var held_rw_mode: Symbol
|
|
195
|
-
held_rw_mode =
|
|
250
|
+
held_rw_mode = rw_mode
|
|
196
251
|
# Step RW: read lock request flag
|
|
197
|
-
read_mode = (
|
|
252
|
+
read_mode = (rw_mode == :read)
|
|
198
253
|
# Step RW: request queue of the current mode and the queue of the opposite mode
|
|
199
254
|
request_queue = read_mode ? read_lock_key_queue : lock_key_queue
|
|
200
255
|
opposite_request_queue = read_mode ? lock_key_queue : read_lock_key_queue
|
|
@@ -215,14 +270,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
215
270
|
|
|
216
271
|
LogVisitor.start(
|
|
217
272
|
logger, log_sampled, log_lock_try, lock_key,
|
|
218
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
273
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
219
274
|
)
|
|
220
275
|
|
|
221
276
|
# Step X: start to work with lock acquiring
|
|
222
277
|
result = redis.with do |rconn|
|
|
223
278
|
LogVisitor.rconn_fetched(
|
|
224
279
|
logger, log_sampled, log_lock_try, lock_key,
|
|
225
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
280
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
226
281
|
)
|
|
227
282
|
|
|
228
283
|
# Step PRE: (pre-check) the lock state without WATCH: the attempt that can not obtain
|
|
@@ -232,9 +287,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
232
287
|
# - see `precheck_lock` for details;
|
|
233
288
|
if precheck
|
|
234
289
|
inter_result = precheck_lock(
|
|
235
|
-
rconn, logger, log_sampled, log_lock_try, lock_key,
|
|
290
|
+
rconn, logger, log_sampled, log_lock_try, lock_key, rw_mode, lock_readers_key,
|
|
236
291
|
request_queue, opposite_request_queue, lock_name, acquirer_id, host_id,
|
|
237
|
-
acquirer_position, acquirer_dead_score, queue_ttl, lock_index_enabled,
|
|
292
|
+
acquirer_position, priority, acquirer_dead_score, queue_ttl, lock_index_enabled,
|
|
238
293
|
lock_index_grace_period, fail_fast, access_strategy
|
|
239
294
|
)
|
|
240
295
|
next if inter_result
|
|
@@ -255,14 +310,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
255
310
|
# - read lock expiration of the current acquirer;
|
|
256
311
|
# - the longest read lock (the last one in the readers registry): write lock requests
|
|
257
312
|
# only (read lock requests do not wait for other readers);
|
|
258
|
-
# - (eager queueing) the lock request queueing: Steps 1,
|
|
313
|
+
# - (eager queueing) the lock request queueing: Steps 2.1, 1, 3 (`queue_lock_request`);
|
|
259
314
|
# NOTE: replies are read by their positions: see "pipeline layout" in `pipeline_lock_state`
|
|
260
315
|
# @type var lock_state: Array[untyped]
|
|
261
316
|
lock_state = rconn.pipelined do |pipeline|
|
|
262
317
|
pipeline_lock_state(
|
|
263
318
|
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
|
|
319
|
+
request_queue, opposite_request_queue, acquirer_position, priority,
|
|
320
|
+
acquirer_dead_score, lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
|
|
266
321
|
)
|
|
267
322
|
end
|
|
268
323
|
# @type var current_lock_obtainer: String?
|
|
@@ -296,7 +351,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
296
351
|
if sp_conflict_rw_mode != nil
|
|
297
352
|
LogVisitor.same_process_conflict_detected(
|
|
298
353
|
logger, log_sampled, log_lock_try, lock_key,
|
|
299
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
354
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
300
355
|
)
|
|
301
356
|
|
|
302
357
|
# NOTE:
|
|
@@ -306,30 +361,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
306
361
|
|
|
307
362
|
# SP-Conflict Step X2: self-process dead lock moment started.
|
|
308
363
|
# SP-Conflict CHECK (Step CHECK): check chosen strategy and flag the current status
|
|
309
|
-
|
|
310
|
-
when :work_through
|
|
311
|
-
# <SP-Conflict Moment>: work through => exit
|
|
312
|
-
sp_conflict_status = lock_upgrade ? :conflict_lock_upgrade : :conflict_work_through
|
|
313
|
-
when :extendable_work_through
|
|
314
|
-
# <SP-Conflict Moment>: extendable_work_through => extend the lock pttl and exit
|
|
315
|
-
sp_conflict_status =
|
|
316
|
-
lock_upgrade ? :conflict_lock_upgrade : :extendable_conflict_work_through
|
|
317
|
-
when :wait_for_lock
|
|
318
|
-
# <SP-Conflict Moment>: wait_for_lock => obtain a lock in classic way
|
|
319
|
-
sp_conflict_status = :conflict_wait_for_lock
|
|
320
|
-
when :dead_locking
|
|
321
|
-
# <SP-Conflict Moment>: dead_locking => exit and fail
|
|
322
|
-
sp_conflict_status = :conflict_dead_lock
|
|
323
|
-
else
|
|
324
|
-
# <SP-Conflict Moment>:
|
|
325
|
-
# - unknown status => work in classic way <wait_for_lock>
|
|
326
|
-
# - it is a case when the new status is added to the code base in the past
|
|
327
|
-
# but is forgotten to be added here;
|
|
328
|
-
sp_conflict_status = :conflict_wait_for_lock
|
|
329
|
-
end
|
|
364
|
+
sp_conflict_status = same_process_conflict_status(conflict_strategy, lock_upgrade)
|
|
330
365
|
LogVisitor.same_process_conflict_analyzed(
|
|
331
366
|
logger, log_sampled, log_lock_try, lock_key,
|
|
332
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
367
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, sp_conflict_status
|
|
333
368
|
)
|
|
334
369
|
end
|
|
335
370
|
|
|
@@ -338,71 +373,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
338
373
|
# SP-Conflict-Step FINAL (SPCF): extend the lock and work through
|
|
339
374
|
# - extend the lock ttl;
|
|
340
375
|
# - store extensions in lock metadata;
|
|
376
|
+
spc_processed_timestamp = Time.now.to_f
|
|
341
377
|
if sp_conflict_rw_mode == :write
|
|
342
378
|
held_rw_mode = :write
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
# - [REDIS RESULT]: in normal cases should return the last script command value
|
|
346
|
-
# - for the current script should return:
|
|
347
|
-
# <1> => timeout was set;
|
|
348
|
-
# <0> => timeount was not set;
|
|
349
|
-
transact.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, ttl)
|
|
350
|
-
# SPCF Step <Meta>: store conflict-state additionals in lock metadata:
|
|
351
|
-
# SPCF Step 2: (lock meta-data)
|
|
352
|
-
# - add the added ttl to reflect the real lock TTL in info;
|
|
353
|
-
# - [REDIS RESULT]: in normal cases should return the value of <ttl> key
|
|
354
|
-
# - for non-existent key value starts from <0> (zero)
|
|
355
|
-
transact.call('HINCRBY', lock_key, 'spc_ext_ttl', ttl)
|
|
356
|
-
# SPCF Step 3: (lock meta-data)
|
|
357
|
-
# - increment the conflcit counter in order to remember
|
|
358
|
-
# how many times dead lock happened;
|
|
359
|
-
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
360
|
-
# - for non-existent key starts from 0
|
|
361
|
-
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
362
|
-
# SPCF Step 4: (lock meta-data)
|
|
363
|
-
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
364
|
-
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
365
|
-
# were added/changed;
|
|
366
|
-
transact.call(
|
|
367
|
-
'HSET',
|
|
368
|
-
lock_key,
|
|
369
|
-
'l_spc_ext_ts', spc_processed_timestamp = Time.now.to_f,
|
|
370
|
-
'l_spc_ext_ini_ttl', ttl
|
|
379
|
+
transact_write_lock_extension(
|
|
380
|
+
transact, lock_key, lock_name, ttl, lock_index_enabled, spc_processed_timestamp
|
|
371
381
|
)
|
|
372
382
|
else
|
|
373
383
|
held_rw_mode = :read
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
transact.call('ZADD', lock_readers_key, 'XX', 'INCR', ttl, acquirer_id)
|
|
378
|
-
# SPCF Step 2 (read lock): the registry should live as long as the longest read lock
|
|
379
|
-
# - NX + GT => sets the registry TTL to max(current TTL, extended read lock TTL);
|
|
380
|
-
extended_read_lock_ttl = (own_read_lock_expiration.to_f + ttl.to_i - redis_time).ceil
|
|
381
|
-
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'NX')
|
|
382
|
-
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'GT')
|
|
383
|
-
# SPCF Step 3 (read lock data): the same reentrant lock data as for the write lock
|
|
384
|
-
transact.call('HINCRBY', read_lock_key, 'spc_ext_ttl', ttl)
|
|
385
|
-
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
386
|
-
transact.call(
|
|
387
|
-
'HSET',
|
|
388
|
-
read_lock_key,
|
|
389
|
-
'l_spc_ext_ts', spc_processed_timestamp = Time.now.to_f,
|
|
390
|
-
'l_spc_ext_ini_ttl', ttl
|
|
391
|
-
)
|
|
392
|
-
# SPCF Step 4 (read lock data): the read lock data lives as long as the read lock
|
|
393
|
-
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'NX')
|
|
394
|
-
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'GT')
|
|
395
|
-
end
|
|
396
|
-
# SPCF Step 5: (lock index) the lock (write or read) lives longer by the extension:
|
|
397
|
-
# - the lock index score is increased by the extension (the upper bound of the lock
|
|
398
|
-
# lifetime stays the upper bound);
|
|
399
|
-
# - XX => locks without the index entry (of older RQL versions) are not indexed with
|
|
400
|
-
# a wrong score (see `reindex_locks`);
|
|
401
|
-
# - [REDIS RESULT]: the new lock index score (nil for the lock without the index entry);
|
|
402
|
-
# - only with the enabled lock index (the last command of the transaction);
|
|
403
|
-
if lock_index_enabled
|
|
404
|
-
transact.call(
|
|
405
|
-
'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
|
|
406
387
|
)
|
|
407
388
|
end
|
|
408
389
|
inter_result = :extendable_conflict_work_through
|
|
@@ -410,53 +391,29 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
410
391
|
# @type var spc_processed_timestamp: Float
|
|
411
392
|
LogVisitor.reentrant_lock__extend_and_work_through(
|
|
412
393
|
logger, log_sampled, log_lock_try, lock_key,
|
|
413
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
394
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
414
395
|
sp_conflict_status, ttl, spc_processed_timestamp
|
|
415
396
|
)
|
|
416
397
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
417
398
|
elsif sp_conflict_status == :conflict_work_through
|
|
418
399
|
inter_result = :conflict_work_through
|
|
419
400
|
|
|
401
|
+
spc_processed_timestamp = Time.now.to_f
|
|
402
|
+
|
|
420
403
|
if sp_conflict_rw_mode == :write
|
|
421
404
|
held_rw_mode = :write
|
|
422
|
-
|
|
423
|
-
# SPCF Step X: (lock meta-data)
|
|
424
|
-
# - increment the conflcit counter in order to remember
|
|
425
|
-
# how many times dead lock happened;
|
|
426
|
-
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
427
|
-
# - for non-existent key starts from 0
|
|
428
|
-
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
429
|
-
# SPCF Step 4: (lock meta-data)
|
|
430
|
-
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
431
|
-
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
432
|
-
# were added/changed;
|
|
433
|
-
transact.call(
|
|
434
|
-
'HSET',
|
|
435
|
-
lock_key,
|
|
436
|
-
'l_spc_ts', spc_processed_timestamp = Time.now.to_f
|
|
437
|
-
)
|
|
405
|
+
transact_write_lock_work_through(transact, lock_key, spc_processed_timestamp)
|
|
438
406
|
else
|
|
439
407
|
held_rw_mode = :read
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
443
|
-
transact.call(
|
|
444
|
-
'HSET', read_lock_key, 'l_spc_ts', spc_processed_timestamp = Time.now.to_f
|
|
445
|
-
)
|
|
446
|
-
# SPCF Step X (read lock data): (NX) the read lock data should not outlive the read lock
|
|
447
|
-
# (it is the case when the read lock data is dropped during the read lock life);
|
|
448
|
-
transact.call(
|
|
449
|
-
'PEXPIRE',
|
|
450
|
-
read_lock_key,
|
|
451
|
-
(own_read_lock_expiration.to_f - redis_time).ceil,
|
|
452
|
-
'NX'
|
|
408
|
+
transact_read_lock_work_through(
|
|
409
|
+
transact, read_lock_key, own_read_lock_expiration, redis_time, spc_processed_timestamp
|
|
453
410
|
)
|
|
454
411
|
end
|
|
455
412
|
|
|
456
413
|
# @type var spc_processed_timestamp: Float
|
|
457
414
|
LogVisitor.reentrant_lock__work_through(
|
|
458
415
|
logger, log_sampled, log_lock_try, lock_key,
|
|
459
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
416
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
460
417
|
sp_conflict_status, spc_processed_timestamp
|
|
461
418
|
)
|
|
462
419
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
@@ -467,7 +424,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
467
424
|
# @type var spc_processed_timestamp: Float
|
|
468
425
|
LogVisitor.single_process_lock_conflict__dead_lock(
|
|
469
426
|
logger, log_sampled, log_lock_try, lock_key,
|
|
470
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
427
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
471
428
|
sp_conflict_status, spc_processed_timestamp
|
|
472
429
|
)
|
|
473
430
|
# SP-Conflict-Step X2 (RW): read-to-write lock upgrade is not supported => exit and fail
|
|
@@ -478,7 +435,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
478
435
|
# @type var spc_processed_timestamp: Float
|
|
479
436
|
LogVisitor.single_process_lock_conflict__lock_upgrade(
|
|
480
437
|
logger, log_sampled, log_lock_try, lock_key,
|
|
481
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
438
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
482
439
|
sp_conflict_status, spc_processed_timestamp
|
|
483
440
|
)
|
|
484
441
|
# Reached the SP-Non-Conflict Mode (NOTE):
|
|
@@ -493,11 +450,12 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
493
450
|
# Fast-Step X1: lock is already obtained. fail fast leads to "no try".
|
|
494
451
|
inter_result = :fail_fast_no_try
|
|
495
452
|
else
|
|
496
|
-
# Step 1: add an acquirer to the lock acquirement queue
|
|
497
453
|
# Step 2.1: drop expired acquirers from the lock queues
|
|
454
|
+
# Step 1: add an acquirer to the lock acquirement queue
|
|
498
455
|
# Step 3: get the actual acquirer waiting in the queue
|
|
499
456
|
# - one round trip for all of them (see `queue_lock_request`);
|
|
500
457
|
# - eager queueing: already done in the lock state round trip (Step 0.1);
|
|
458
|
+
# - Step 2.2 (priorities) runs after them (see below);
|
|
501
459
|
# @type var queue_replies: Array[untyped]
|
|
502
460
|
queue_replies =
|
|
503
461
|
if eager_queueing
|
|
@@ -506,30 +464,51 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
506
464
|
rconn.pipelined do |pipeline|
|
|
507
465
|
queue_lock_request(
|
|
508
466
|
pipeline, request_queue, opposite_request_queue,
|
|
509
|
-
acquirer_position, acquirer_id, acquirer_dead_score,
|
|
467
|
+
acquirer_position, priority, acquirer_id, acquirer_dead_score,
|
|
510
468
|
lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
|
|
511
469
|
)
|
|
512
470
|
end
|
|
513
471
|
end
|
|
514
472
|
# NOTE: the position of the first queueing reply (see "pipeline layout" in Step 0.1)
|
|
515
473
|
queue_replies_at = eager_queueing ? LOCK_STATE_QUEUE_REPLIES_AT : 0
|
|
516
|
-
# @type var
|
|
517
|
-
|
|
474
|
+
# @type var waiting_request: [String, Float]?
|
|
475
|
+
waiting_request = queue_replies[queue_replies_at + QUEUE_HEAD_REPLY].first
|
|
518
476
|
# @type var opposite_waiting_request: [String, Float]?
|
|
519
477
|
opposite_waiting_request =
|
|
520
478
|
queue_replies[queue_replies_at + OPPOSITE_QUEUE_HEAD_REPLY].first
|
|
521
479
|
|
|
522
480
|
LogVisitor.acq_added_to_queue(
|
|
523
481
|
logger, log_sampled, log_lock_try, lock_key,
|
|
524
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
482
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
|
|
525
483
|
)
|
|
526
484
|
LogVisitor.remove_expired_acqs(
|
|
527
485
|
logger, log_sampled, log_lock_try, lock_key,
|
|
528
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
486
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Step 2.2: (priorities) drop the dead lock requests of other priority bands that block
|
|
490
|
+
# the current one (the queue head or the earlier request of the opposite mode) and get
|
|
491
|
+
# the actual queue heads (no round trips while no dead request blocks the current one);
|
|
492
|
+
actual_queue_heads = drop_dead_requests_ahead(
|
|
493
|
+
rconn, rw_mode, request_queue, opposite_request_queue, acquirer_id,
|
|
494
|
+
acquirer_position, acquirer_dead_score, access_strategy, waiting_request,
|
|
495
|
+
opposite_waiting_request
|
|
529
496
|
)
|
|
497
|
+
if actual_queue_heads
|
|
498
|
+
waiting_request, opposite_waiting_request, dropped_requests = actual_queue_heads
|
|
499
|
+
log_dropped_requests(
|
|
500
|
+
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
501
|
+
access_strategy, rw_mode, dropped_requests
|
|
502
|
+
)
|
|
503
|
+
end
|
|
504
|
+
waiting_acquirer = waiting_request ? waiting_request[0] : nil
|
|
505
|
+
# NOTE: the priority of the first request is decoded for logs only
|
|
506
|
+
waiting_priority = logged_request_priority(log_sampled, log_lock_try, waiting_request)
|
|
507
|
+
|
|
530
508
|
LogVisitor.get_first_from_queue(
|
|
531
509
|
logger, log_sampled, log_lock_try, lock_key,
|
|
532
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
510
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
511
|
+
waiting_priority
|
|
533
512
|
)
|
|
534
513
|
|
|
535
514
|
# Step 3.1 (RW) + Steps PRE-4.x, ROLLBACK 1.1, 1.2, 2, 3: the obstacle to the lock
|
|
@@ -548,102 +527,43 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
548
527
|
# Step ROLLBACK: the lock can not be obtained now. retry!
|
|
549
528
|
log_lock_obstacle(
|
|
550
529
|
rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
|
|
551
|
-
acquirer_id, host_id, access_strategy,
|
|
552
|
-
waiting_acquirer,
|
|
530
|
+
acquirer_id, host_id, access_strategy, rw_mode,
|
|
531
|
+
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
553
532
|
)
|
|
554
533
|
inter_result = obstacle
|
|
555
534
|
elsif read_mode
|
|
556
535
|
# NOTE: required lock is free and ready to be acquired! acquire! (read lock)
|
|
557
536
|
|
|
558
|
-
#
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
# Step 6.3 (RW): register the read lock (expiration time in redis time)
|
|
565
|
-
transact.call('ZADD', lock_readers_key, redis_time + ttl.to_i, acquirer_id)
|
|
566
|
-
|
|
567
|
-
# Step 6.4 (RW): the registry should live as long as the longest read lock
|
|
568
|
-
# - NX + GT => sets the registry TTL to max(current TTL, read lock TTL);
|
|
569
|
-
# - in order to prevent "infinite registries" after process crashes;
|
|
570
|
-
transact.call('PEXPIRE', lock_readers_key, ttl, 'NX')
|
|
571
|
-
transact.call('PEXPIRE', lock_readers_key, ttl, 'GT')
|
|
572
|
-
|
|
573
|
-
# Step 6.5 (RW): store the read lock data (the same structure as the write lock data
|
|
574
|
-
# without `acq_id`/`hst_id`: both are derived from the read lock acquirer id that is
|
|
575
|
-
# a part of the data key and the registry member, so the info adds them on formatting)
|
|
576
|
-
# - drop the data of the expired read lock of the current acquirer (if any);
|
|
577
|
-
# - the read lock data lives as long as the read lock;
|
|
578
|
-
transact.call('DEL', read_lock_key)
|
|
579
|
-
transact.call(
|
|
580
|
-
'HSET',
|
|
581
|
-
read_lock_key,
|
|
582
|
-
'ts', timestamp = Time.now.to_f,
|
|
583
|
-
'ini_ttl', ttl,
|
|
584
|
-
*(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
|
|
585
543
|
)
|
|
586
|
-
transact.call('PEXPIRE', read_lock_key, ttl)
|
|
587
|
-
|
|
588
|
-
# Step 6.6 (lock index): the lock name lives in the lock index as long as the read lock
|
|
589
|
-
# (the upper bound of the read lock lifetime: client time + ttl + grace period);
|
|
590
|
-
# - in the same transaction: an index entry dropped between the queueing and this
|
|
591
|
-
# transaction (`#unlock`, `#clear_locks`) is restored with the read lock;
|
|
592
|
-
# - GT => the score is never decreased (other keys of the lock can live longer);
|
|
593
|
-
# - only with the enabled lock index (the last command of the transaction);
|
|
594
|
-
if lock_index_enabled
|
|
595
|
-
transact.call(
|
|
596
|
-
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'GT',
|
|
597
|
-
RedisQueuedLocks::Resource.lock_index_time + ttl.to_i + lock_index_grace_period,
|
|
598
|
-
lock_name
|
|
599
|
-
)
|
|
600
|
-
end
|
|
601
544
|
|
|
602
545
|
inter_result = :lock_obtaining
|
|
603
546
|
|
|
604
547
|
LogVisitor.obtain__free_to_acquire(
|
|
605
548
|
logger, log_sampled, log_lock_try, lock_key,
|
|
606
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
549
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
607
550
|
)
|
|
608
551
|
else
|
|
609
552
|
# NOTE: required lock is free and ready to be acquired! acquire! (write lock)
|
|
610
553
|
|
|
611
|
-
#
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
lock_key,
|
|
618
|
-
'acq_id', acquirer_id,
|
|
619
|
-
'hst_id', host_id,
|
|
620
|
-
'ts', timestamp = Time.now.to_f,
|
|
621
|
-
'ini_ttl', ttl,
|
|
622
|
-
*(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
|
|
623
560
|
)
|
|
624
561
|
|
|
625
|
-
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
|
626
|
-
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
|
627
|
-
|
|
628
|
-
# Step 6.4 (lock index): the lock name lives in the lock index as long as the lock
|
|
629
|
-
# (the upper bound of the lock lifetime: client time + ttl + grace period);
|
|
630
|
-
# - in the same transaction: an index entry dropped between the queueing and this
|
|
631
|
-
# transaction (`#unlock`, `#clear_locks`) is restored with the lock;
|
|
632
|
-
# - GT => the score is never decreased (other keys of the lock can live longer);
|
|
633
|
-
# - only with the enabled lock index (the last command of the transaction);
|
|
634
|
-
if lock_index_enabled
|
|
635
|
-
transact.call(
|
|
636
|
-
'ZADD', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, 'GT',
|
|
637
|
-
RedisQueuedLocks::Resource.lock_index_time + ttl.to_i + lock_index_grace_period,
|
|
638
|
-
lock_name
|
|
639
|
-
)
|
|
640
|
-
end
|
|
641
|
-
|
|
642
562
|
inter_result = :lock_obtaining
|
|
643
563
|
|
|
644
564
|
LogVisitor.obtain__free_to_acquire(
|
|
645
565
|
logger, log_sampled, log_lock_try, lock_key,
|
|
646
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
566
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
647
567
|
)
|
|
648
568
|
end
|
|
649
569
|
end
|
|
@@ -829,24 +749,395 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
829
749
|
|
|
830
750
|
private
|
|
831
751
|
|
|
832
|
-
#
|
|
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
|
+
|
|
1120
|
+
# Queues the lock request and reads the lock queue state (Steps 2.1, 1, 3 and 3.2-3.4 of
|
|
833
1121
|
# the attempt): the commands are added to the given pipeline (one round trip with other
|
|
834
1122
|
# pipelined commands).
|
|
835
|
-
# Replies (positions relative to the first one): [0]
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
#
|
|
839
|
-
#
|
|
840
|
-
#
|
|
841
|
-
#
|
|
842
|
-
# the
|
|
1123
|
+
# Replies (positions relative to the first one): [0] ZREMRANGEBYSCORE (request queue, the
|
|
1124
|
+
# priority band of the request), [1] ZREMRANGEBYSCORE (opposite request queue, the priority band
|
|
1125
|
+
# of the request), [2] ZADD NX, [QUEUE_HEAD_REPLY] ZRANGE WITHSCORES (the first request of
|
|
1126
|
+
# the request queue), [OPPOSITE_QUEUE_HEAD_REPLY] ZRANGE WITHSCORES (the first request of
|
|
1127
|
+
# the opposite request queue); the enabled lock index only: [5] PEXPIRE NX, [6] PEXPIRE GT (the
|
|
1128
|
+
# request queue TTL), [7] ZADD GT (the lock index), [8] ZREMRANGEBYSCORE (expired lock names of
|
|
1129
|
+
# the lock index). Update these constants when the commands change (new commands go to the end:
|
|
1130
|
+
# the replies are not read after `OPPOSITE_QUEUE_HEAD_REPLY`, so the conditional lock index
|
|
1131
|
+
# commands do not shift the read replies).
|
|
843
1132
|
#
|
|
844
1133
|
# @param pipeline [RedisClient::Pipeline]
|
|
845
1134
|
# @param request_queue [String] Queue of the requests of the requested lock mode.
|
|
846
1135
|
# @param opposite_request_queue [String] Queue of the requests of the opposite lock mode.
|
|
847
|
-
# @param acquirer_position [Numeric]
|
|
1136
|
+
# @param acquirer_position [Numeric] The enqueue time shifted by the priority band.
|
|
1137
|
+
# @param priority [Integer] The priority band of `acquirer_position`.
|
|
848
1138
|
# @param acquirer_id [String]
|
|
849
|
-
# @param acquirer_dead_score [Float]
|
|
1139
|
+
# @param acquirer_dead_score [Float]
|
|
1140
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
850
1141
|
# @param lock_name [String] The lock name (the member of the lock index).
|
|
851
1142
|
# @param queue_ttl [Integer] Lifetime of the lock request (in seconds).
|
|
852
1143
|
# @param lock_index_enabled [Boolean] Send the lock index commands (Steps 3.2-3.4).
|
|
@@ -855,11 +1146,13 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
855
1146
|
#
|
|
856
1147
|
# @api private
|
|
857
1148
|
# @since 1.20.0
|
|
1149
|
+
# @version 1.22.0
|
|
858
1150
|
def queue_lock_request(
|
|
859
1151
|
pipeline,
|
|
860
1152
|
request_queue,
|
|
861
1153
|
opposite_request_queue,
|
|
862
1154
|
acquirer_position,
|
|
1155
|
+
priority,
|
|
863
1156
|
acquirer_id,
|
|
864
1157
|
acquirer_dead_score,
|
|
865
1158
|
lock_name,
|
|
@@ -867,6 +1160,22 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
867
1160
|
lock_index_enabled,
|
|
868
1161
|
lock_index_grace_period
|
|
869
1162
|
)
|
|
1163
|
+
# Step 2.1: drop expired acquirers from the lock queues
|
|
1164
|
+
# - (RW) both queues are cleared: an expired request of the opposite mode should not
|
|
1165
|
+
# block the current request forever;
|
|
1166
|
+
# - (priorities) the requests of the priority band of the current request (the band has its
|
|
1167
|
+
# own time barrier, see `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`); a dead request of
|
|
1168
|
+
# another band is dropped when it blocks the current request (`drop_dead_requests_ahead`),
|
|
1169
|
+
# requests of lower priorities are never ahead of the current request;
|
|
1170
|
+
# - before the queueing (Step 1): the old (dead) position of the requeued request (its
|
|
1171
|
+
# position is reset after `queue_ttl`, see `dead_score_reached?`) is dropped first, so
|
|
1172
|
+
# `ZADD NX` adds its new position in the same round trip (after `ZADD NX` the old position
|
|
1173
|
+
# would be kept by `NX` and dropped by the removal: the request would be missing in the
|
|
1174
|
+
# queue in this attempt);
|
|
1175
|
+
pipeline_dead_requests_removal(
|
|
1176
|
+
pipeline, request_queue, opposite_request_queue, acquirer_dead_score, priority
|
|
1177
|
+
)
|
|
1178
|
+
|
|
870
1179
|
# Step 1: add an acquirer to the lock acquirement queue
|
|
871
1180
|
# NOTE:
|
|
872
1181
|
# 'NX' means "Only add new elements. Don't update already existing elements."
|
|
@@ -876,15 +1185,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
876
1185
|
# not in the queue now>>;
|
|
877
1186
|
pipeline.call('ZADD', request_queue, 'NX', acquirer_position, acquirer_id)
|
|
878
1187
|
|
|
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
|
|
1188
|
+
# Step 3: get the actual acquirer waiting in the queue (with its position: the band and
|
|
1189
|
+
# the liveness of the queue head are decoded from it, see `drop_dead_requests_ahead`)
|
|
886
1190
|
# - (RW) and the first request of the opposite mode;
|
|
887
|
-
pipeline.call('ZRANGE', request_queue, '0', '0')
|
|
1191
|
+
pipeline.call('ZRANGE', request_queue, '0', '0', 'WITHSCORES')
|
|
888
1192
|
pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
|
|
889
1193
|
|
|
890
1194
|
# NOTE: the disabled lock index: no lock index commands, lock request queues have no TTL
|
|
@@ -921,6 +1225,34 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
921
1225
|
)
|
|
922
1226
|
end
|
|
923
1227
|
|
|
1228
|
+
# Adds the removal of the dead lock requests of the given priority band (both lock queues) to
|
|
1229
|
+
# the given pipeline (see `queue_lock_request` and `drop_dead_requests_ahead`).
|
|
1230
|
+
#
|
|
1231
|
+
# @param pipeline [RedisClient::Pipeline]
|
|
1232
|
+
# @param request_queue [String]
|
|
1233
|
+
# @param opposite_request_queue [String]
|
|
1234
|
+
# @param acquirer_dead_score [Float]
|
|
1235
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
1236
|
+
# @param priority [Integer]
|
|
1237
|
+
# The priority band (see `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`).
|
|
1238
|
+
# @return [void]
|
|
1239
|
+
#
|
|
1240
|
+
# @api private
|
|
1241
|
+
# @since 1.22.0
|
|
1242
|
+
def pipeline_dead_requests_removal(
|
|
1243
|
+
pipeline,
|
|
1244
|
+
request_queue,
|
|
1245
|
+
opposite_request_queue,
|
|
1246
|
+
acquirer_dead_score,
|
|
1247
|
+
priority
|
|
1248
|
+
)
|
|
1249
|
+
band_floor = RedisQueuedLocks::Resource.lock_priority_band_floor(priority)
|
|
1250
|
+
band_dead_score =
|
|
1251
|
+
RedisQueuedLocks::Resource.lock_priority_band_dead_score(acquirer_dead_score, priority)
|
|
1252
|
+
pipeline.call('ZREMRANGEBYSCORE', request_queue, band_floor, band_dead_score)
|
|
1253
|
+
pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, band_floor, band_dead_score)
|
|
1254
|
+
end
|
|
1255
|
+
|
|
924
1256
|
# Adds the lock state commands to the given pipeline (the lock state of the attempt and of
|
|
925
1257
|
# the pre-check: one round trip).
|
|
926
1258
|
#
|
|
@@ -949,6 +1281,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
949
1281
|
# @param request_queue [String]
|
|
950
1282
|
# @param opposite_request_queue [String]
|
|
951
1283
|
# @param acquirer_position [Numeric]
|
|
1284
|
+
# @param priority [Integer]
|
|
952
1285
|
# @param acquirer_dead_score [Float]
|
|
953
1286
|
# @param lock_name [String] The lock name (the member of the lock index).
|
|
954
1287
|
# @param queue_ttl [Integer] In seconds.
|
|
@@ -958,6 +1291,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
958
1291
|
#
|
|
959
1292
|
# @api private
|
|
960
1293
|
# @since 1.20.0
|
|
1294
|
+
# @version 1.22.0
|
|
961
1295
|
def pipeline_lock_state(
|
|
962
1296
|
pipeline,
|
|
963
1297
|
lock_key,
|
|
@@ -968,6 +1302,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
968
1302
|
request_queue,
|
|
969
1303
|
opposite_request_queue,
|
|
970
1304
|
acquirer_position,
|
|
1305
|
+
priority,
|
|
971
1306
|
acquirer_dead_score,
|
|
972
1307
|
lock_name,
|
|
973
1308
|
queue_ttl,
|
|
@@ -981,7 +1316,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
981
1316
|
if queueing
|
|
982
1317
|
queue_lock_request(
|
|
983
1318
|
pipeline, request_queue, opposite_request_queue,
|
|
984
|
-
acquirer_position, acquirer_id, acquirer_dead_score,
|
|
1319
|
+
acquirer_position, priority, acquirer_id, acquirer_dead_score,
|
|
985
1320
|
lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
|
|
986
1321
|
)
|
|
987
1322
|
end
|
|
@@ -991,6 +1326,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
991
1326
|
|
|
992
1327
|
# (RW) The request of the opposite mode that should obtain the lock earlier
|
|
993
1328
|
# - FIFO between modes (`:queued` access strategy only);
|
|
1329
|
+
# - (priorities) positions are shifted by the priority bands: a request of a higher priority
|
|
1330
|
+
# is ahead of any request of a lower priority, requests of the same priority are FIFO;
|
|
994
1331
|
# - equal positions are ordered by acquirer ids;
|
|
995
1332
|
#
|
|
996
1333
|
# @param access_strategy [Symbol]
|
|
@@ -1080,14 +1417,19 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1080
1417
|
# @param acquirer_id [String]
|
|
1081
1418
|
# @param host_id [String]
|
|
1082
1419
|
# @param access_strategy [Symbol]
|
|
1083
|
-
# @param
|
|
1420
|
+
# @param rw_mode [Symbol]
|
|
1084
1421
|
# @param waiting_acquirer [String,NilClass]
|
|
1085
|
-
# @param
|
|
1422
|
+
# @param waiting_priority [Integer,NilClass]
|
|
1423
|
+
# The priority of the first request (decoded for logs only: `nil` without logs).
|
|
1424
|
+
# @param opposite_waiting_request [Array<String,Float>,NilClass]
|
|
1425
|
+
# The first request of the opposite queue (the request ahead of the `*_request_is_ahead`
|
|
1426
|
+
# obstacles, see `find_opposite_request_ahead`).
|
|
1086
1427
|
# @param current_lock_obtainer [String,NilClass]
|
|
1087
1428
|
# @return [void]
|
|
1088
1429
|
#
|
|
1089
1430
|
# @api private
|
|
1090
1431
|
# @since 1.20.0
|
|
1432
|
+
# @version 1.22.0
|
|
1091
1433
|
# rubocop:disable Metrics/MethodLength
|
|
1092
1434
|
def log_lock_obstacle(
|
|
1093
1435
|
rconn,
|
|
@@ -1100,59 +1442,354 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1100
1442
|
acquirer_id,
|
|
1101
1443
|
host_id,
|
|
1102
1444
|
access_strategy,
|
|
1103
|
-
|
|
1445
|
+
rw_mode,
|
|
1104
1446
|
waiting_acquirer,
|
|
1105
|
-
|
|
1447
|
+
waiting_priority,
|
|
1448
|
+
opposite_waiting_request,
|
|
1106
1449
|
current_lock_obtainer
|
|
1107
1450
|
)
|
|
1108
1451
|
case obstacle
|
|
1109
1452
|
when :dead_score_reached
|
|
1110
1453
|
LogVisitor.exit__queue_ttl_reached(
|
|
1111
1454
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1112
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1455
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
1113
1456
|
)
|
|
1114
1457
|
when :acquirer_is_not_first_in_queue
|
|
1115
1458
|
LogVisitor.exit__no_first(
|
|
1116
1459
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1117
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1460
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
1461
|
+
waiting_priority,
|
|
1118
1462
|
# NOTE: lock data is extracted for logs only
|
|
1119
1463
|
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
|
|
1120
1464
|
)
|
|
1121
|
-
when :write_request_is_ahead
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1465
|
+
when :write_request_is_ahead, :read_request_is_ahead
|
|
1466
|
+
ahead_request = opposite_waiting_request #: [String, Float]
|
|
1467
|
+
# NOTE: the priority of the request ahead is decoded for logs only
|
|
1468
|
+
ahead_priority = logged_request_priority(log_sampled, log_lock_try, ahead_request)
|
|
1469
|
+
if obstacle == :write_request_is_ahead
|
|
1470
|
+
LogVisitor.exit__write_request_ahead(
|
|
1471
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
1472
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
1473
|
+
ahead_request[0], ahead_priority
|
|
1474
|
+
)
|
|
1475
|
+
else
|
|
1476
|
+
LogVisitor.exit__read_request_ahead(
|
|
1477
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
1478
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
1479
|
+
ahead_request[0], ahead_priority
|
|
1480
|
+
)
|
|
1481
|
+
end
|
|
1133
1482
|
when :lock_is_still_acquired
|
|
1134
1483
|
LogVisitor.exit__lock_still_obtained(
|
|
1135
1484
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1136
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1137
|
-
waiting_acquirer,
|
|
1485
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode,
|
|
1486
|
+
waiting_acquirer, waiting_priority,
|
|
1487
|
+
current_lock_obtainer, #: String
|
|
1138
1488
|
# NOTE: lock data is extracted for logs only
|
|
1139
1489
|
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : NO_LOCK_DATA)
|
|
1140
1490
|
)
|
|
1141
1491
|
when :read_lock_is_still_acquired
|
|
1142
1492
|
LogVisitor.exit__read_lock_still_obtained(
|
|
1143
1493
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1144
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1494
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
1495
|
+
waiting_priority
|
|
1145
1496
|
)
|
|
1146
1497
|
end
|
|
1147
1498
|
end
|
|
1148
1499
|
# rubocop:enable Metrics/MethodLength
|
|
1149
1500
|
|
|
1501
|
+
# (priorities) The priority of the lock request for the try-lock logs (the first request of
|
|
1502
|
+
# the queue, the request ahead): decoded only when the try-lock logs are written (log-only values
|
|
1503
|
+
# are never computed without the logs: `log_sampled && log_lock_try`).
|
|
1504
|
+
#
|
|
1505
|
+
# @param log_sampled [Boolean]
|
|
1506
|
+
# @param log_lock_try [Boolean]
|
|
1507
|
+
# @param lock_request [Array<String,Float>,NilClass]
|
|
1508
|
+
# The acquirer and the position of the lock request.
|
|
1509
|
+
# @return [Integer,NilClass] `nil` without the try-lock logs or without the lock request.
|
|
1510
|
+
#
|
|
1511
|
+
# @api private
|
|
1512
|
+
# @since 1.22.0
|
|
1513
|
+
def logged_request_priority(log_sampled, log_lock_try, lock_request)
|
|
1514
|
+
return unless log_sampled && log_lock_try && lock_request
|
|
1515
|
+
|
|
1516
|
+
RedisQueuedLocks::Resource.lock_request_priority(lock_request[1])
|
|
1517
|
+
end
|
|
1518
|
+
|
|
1519
|
+
# (priorities) Drops the dead lock requests of other priority bands that block the current lock
|
|
1520
|
+
# request (`:queued` access strategy: the head of the write lock queue for write lock requests,
|
|
1521
|
+
# the earlier lock request of the opposite mode for both modes) and reads the actual queue heads
|
|
1522
|
+
# (Step 2.2 of the attempt and of its pre-check):
|
|
1523
|
+
# - the dead lock requests of the band of the current request are dropped by the queueing
|
|
1524
|
+
# (Step 2.1, before the queue heads are read) and requests of lower priorities are never
|
|
1525
|
+
# ahead of it: only a request of a higher priority can be a dead blocking request;
|
|
1526
|
+
# - the band and the enqueue time of the blocking request are decoded from its position (see
|
|
1527
|
+
# `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`): no Redis commands to check the liveness,
|
|
1528
|
+
# any band is recognized (the priority range of the current RQL version is not used), the
|
|
1529
|
+
# time barrier is the barrier of the current request (`acquirer_dead_score`: its `queue_ttl`);
|
|
1530
|
+
# - the dead blocking requests (the head of the write lock queue and the earlier request of the
|
|
1531
|
+
# opposite mode) are dropped with all dead requests of their bands (both queues) in one round
|
|
1532
|
+
# trip with the reading of the actual queue heads; no round trips while no dead request
|
|
1533
|
+
# blocks the current one (the lock state does not matter: the attempt drops the dead blocking
|
|
1534
|
+
# requests even when the lock is obtained by another acquirer);
|
|
1535
|
+
# - up to `MAX_DEAD_REQUEST_REMOVALS` round trips (one or two bands each) per call;
|
|
1536
|
+
# - (infinite positions) RQL never writes them (manual writes of the queues, see
|
|
1537
|
+
# `RedisQueuedLocks::Resource.lock_request_priority`): a blocking request at `-inf` is always
|
|
1538
|
+
# dead (the highest band, no enqueue time) and is dropped with all requests at `-inf` of both
|
|
1539
|
+
# queues in the same round trip; a request at `+inf` is never ahead of the current request;
|
|
1540
|
+
# - the dropped blocking requests are returned, not logged: the attempt and its pre-check log
|
|
1541
|
+
# them at the same step (after the queueing log events, see `log_dropped_requests`);
|
|
1542
|
+
#
|
|
1543
|
+
# @param rconn [RedisClient]
|
|
1544
|
+
# @param rw_mode [Symbol]
|
|
1545
|
+
# @param request_queue [String]
|
|
1546
|
+
# @param opposite_request_queue [String]
|
|
1547
|
+
# @param acquirer_id [String]
|
|
1548
|
+
# @param acquirer_position [Numeric]
|
|
1549
|
+
# @param acquirer_dead_score [Float]
|
|
1550
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
1551
|
+
# @param access_strategy [Symbol]
|
|
1552
|
+
# @param waiting_request [Array<String,Float>,NilClass] The head of the request queue.
|
|
1553
|
+
# @param opposite_waiting_request [Array<String,Float>,NilClass]
|
|
1554
|
+
# The head of the opposite request queue.
|
|
1555
|
+
# @return [Array<Array<String,Float>,NilClass,Array<Array<String,Integer>>>,NilClass]
|
|
1556
|
+
# - the actual queue heads and the dropped blocking requests (the acquirer and the priority of
|
|
1557
|
+
# each one, in the order of the removal): `[waiting_request, opposite_waiting_request,
|
|
1558
|
+
# dropped_requests]` when at least one dead lock request is dropped;
|
|
1559
|
+
# - `nil` otherwise;
|
|
1560
|
+
#
|
|
1561
|
+
# @api private
|
|
1562
|
+
# @since 1.22.0
|
|
1563
|
+
# rubocop:disable Metrics/MethodLength
|
|
1564
|
+
def drop_dead_requests_ahead(
|
|
1565
|
+
rconn,
|
|
1566
|
+
rw_mode,
|
|
1567
|
+
request_queue,
|
|
1568
|
+
opposite_request_queue,
|
|
1569
|
+
acquirer_id,
|
|
1570
|
+
acquirer_position,
|
|
1571
|
+
acquirer_dead_score,
|
|
1572
|
+
access_strategy,
|
|
1573
|
+
waiting_request,
|
|
1574
|
+
opposite_waiting_request
|
|
1575
|
+
)
|
|
1576
|
+
return unless access_strategy == :queued
|
|
1577
|
+
|
|
1578
|
+
read_mode = (rw_mode == :read)
|
|
1579
|
+
round_trips = 0
|
|
1580
|
+
# NOTE: allocated by the first removal only (no allocations while nothing dead blocks)
|
|
1581
|
+
# @type var dropped_requests: Array[[String, Integer]]?
|
|
1582
|
+
dropped_requests = nil
|
|
1583
|
+
|
|
1584
|
+
while round_trips < MAX_DEAD_REQUEST_REMOVALS
|
|
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
|
|
1588
|
+
)
|
|
1589
|
+
break unless dead_requests
|
|
1590
|
+
|
|
1591
|
+
dead_head, dead_head_priority, dead_ahead, dead_ahead_priority = dead_requests
|
|
1592
|
+
|
|
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
|
|
1595
|
+
# the opposite request queue);
|
|
1596
|
+
# @type var queue_state: Array[untyped]
|
|
1597
|
+
queue_state = rconn.pipelined do |pipeline|
|
|
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
|
+
)
|
|
1602
|
+
pipeline.call('ZRANGE', request_queue, '0', '0', 'WITHSCORES')
|
|
1603
|
+
pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
|
|
1604
|
+
end
|
|
1605
|
+
# @type var removed_requests: Array[[String, Integer]]
|
|
1606
|
+
removed_requests = dropped_requests || []
|
|
1607
|
+
removed_requests << [dead_head[0], dead_head_priority] if dead_head && dead_head_priority
|
|
1608
|
+
removed_requests << [dead_ahead[0], dead_ahead_priority] if dead_ahead && dead_ahead_priority
|
|
1609
|
+
dropped_requests = removed_requests
|
|
1610
|
+
waiting_request = queue_state[-2].first
|
|
1611
|
+
opposite_waiting_request = queue_state[-1].first
|
|
1612
|
+
round_trips += 1
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
[waiting_request, opposite_waiting_request, dropped_requests] if dropped_requests
|
|
1616
|
+
end
|
|
1617
|
+
# rubocop:enable Metrics/MethodLength
|
|
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
|
+
|
|
1723
|
+
# (priorities) Logs the dead lock requests dropped by `drop_dead_requests_ahead` (the attempt
|
|
1724
|
+
# and its pre-check log them at the same step: after the queueing log events).
|
|
1725
|
+
#
|
|
1726
|
+
# @param logger [::Logger,#debug]
|
|
1727
|
+
# @param log_sampled [Boolean]
|
|
1728
|
+
# @param log_lock_try [Boolean]
|
|
1729
|
+
# @param lock_key [String]
|
|
1730
|
+
# @param queue_ttl [Integer]
|
|
1731
|
+
# @param acquirer_id [String]
|
|
1732
|
+
# @param host_id [String]
|
|
1733
|
+
# @param access_strategy [Symbol]
|
|
1734
|
+
# @param rw_mode [Symbol]
|
|
1735
|
+
# @param dropped_requests [Array<Array<String,Integer>>]
|
|
1736
|
+
# The acquirer and the priority of each dropped lock request.
|
|
1737
|
+
# @return [void]
|
|
1738
|
+
#
|
|
1739
|
+
# @api private
|
|
1740
|
+
# @since 1.22.0
|
|
1741
|
+
def log_dropped_requests(
|
|
1742
|
+
logger,
|
|
1743
|
+
log_sampled,
|
|
1744
|
+
log_lock_try,
|
|
1745
|
+
lock_key,
|
|
1746
|
+
queue_ttl,
|
|
1747
|
+
acquirer_id,
|
|
1748
|
+
host_id,
|
|
1749
|
+
access_strategy,
|
|
1750
|
+
rw_mode,
|
|
1751
|
+
dropped_requests
|
|
1752
|
+
)
|
|
1753
|
+
return unless log_sampled && log_lock_try
|
|
1754
|
+
|
|
1755
|
+
dropped_requests.each do |dead_acquirer_id, dead_priority|
|
|
1756
|
+
LogVisitor.dead_request_removed(
|
|
1757
|
+
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
1758
|
+
access_strategy, rw_mode, dead_acquirer_id, dead_priority
|
|
1759
|
+
)
|
|
1760
|
+
end
|
|
1761
|
+
end
|
|
1762
|
+
|
|
1763
|
+
# (priorities) The priority band of the dead lock request (see `drop_dead_requests_ahead`): the
|
|
1764
|
+
# band and the enqueue time are decoded from the position of the request (infinite positions:
|
|
1765
|
+
# `-inf` is decoded to the highest band and is always dead, `+inf` to the lowest band and is never
|
|
1766
|
+
# dead, see `RedisQueuedLocks::Resource.lock_request_priority`).
|
|
1767
|
+
#
|
|
1768
|
+
# @param lock_request [Array<String,Float>] The acquirer and the position of the lock request.
|
|
1769
|
+
# @param acquirer_dead_score [Float]
|
|
1770
|
+
# Requests queued before this time are expired (the barrier of the default priority band).
|
|
1771
|
+
# @return [Integer,NilClass]
|
|
1772
|
+
# The priority of the dead lock request, `nil` for a live one (its position is after the time
|
|
1773
|
+
# barrier of its band: the same barrier as the dropping of the dead requests of the band).
|
|
1774
|
+
#
|
|
1775
|
+
# @api private
|
|
1776
|
+
# @since 1.22.0
|
|
1777
|
+
def dead_request_priority(lock_request, acquirer_dead_score)
|
|
1778
|
+
priority = RedisQueuedLocks::Resource.lock_request_priority(lock_request[1])
|
|
1779
|
+
band_dead_score =
|
|
1780
|
+
RedisQueuedLocks::Resource.lock_priority_band_dead_score(acquirer_dead_score, priority)
|
|
1781
|
+
priority if lock_request[1] <= band_dead_score
|
|
1782
|
+
end
|
|
1783
|
+
|
|
1150
1784
|
# Pre-check of the attempt (Step PRE): the lock state without WATCH (one round trip).
|
|
1151
1785
|
# - `fail_fast` attempts: the lock state only (the request is not queued: the failed "no try"
|
|
1152
1786
|
# attempt does not dequeue the request); fails with `:fail_fast_no_try` on the obtained lock;
|
|
1153
1787
|
# - retries: the lock state and the lock request queueing (the request is already queued by
|
|
1154
1788
|
# the previous attempts); fails with the obstacle of the classic logic (`lock_obstacle`)
|
|
1155
|
-
# and logs the same events as the failed attempt;
|
|
1789
|
+
# and logs the same events as the failed attempt in the same order;
|
|
1790
|
+
# - (priorities) retries drop the dead lock requests of other priority bands that block the
|
|
1791
|
+
# request before the decision (Step 2.2 of the attempt: one more round trip per dropped
|
|
1792
|
+
# band, see `drop_dead_requests_ahead`);
|
|
1156
1793
|
# - same-process conflicts (the lock is obtained by the current acquirer) and locks that can be
|
|
1157
1794
|
# obtained are not decided: the attempt continues with the WATCH/MULTI logic (`nil`);
|
|
1158
1795
|
# - the pre-check never obtains the lock (mutual exclusion relies on WATCH/MULTI only);
|
|
@@ -1162,7 +1799,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1162
1799
|
# @param log_sampled [Boolean]
|
|
1163
1800
|
# @param log_lock_try [Boolean]
|
|
1164
1801
|
# @param lock_key [String]
|
|
1165
|
-
# @param
|
|
1802
|
+
# @param rw_mode [Symbol]
|
|
1166
1803
|
# @param lock_readers_key [String]
|
|
1167
1804
|
# @param request_queue [String]
|
|
1168
1805
|
# @param opposite_request_queue [String]
|
|
@@ -1170,6 +1807,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1170
1807
|
# @param acquirer_id [String]
|
|
1171
1808
|
# @param host_id [String]
|
|
1172
1809
|
# @param acquirer_position [Numeric]
|
|
1810
|
+
# @param priority [Integer]
|
|
1173
1811
|
# @param acquirer_dead_score [Float]
|
|
1174
1812
|
# @param queue_ttl [Integer]
|
|
1175
1813
|
# @param lock_index_enabled [Boolean]
|
|
@@ -1180,6 +1818,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1180
1818
|
#
|
|
1181
1819
|
# @api private
|
|
1182
1820
|
# @since 1.20.0
|
|
1821
|
+
# @version 1.22.0
|
|
1183
1822
|
# rubocop:disable Metrics/MethodLength
|
|
1184
1823
|
def precheck_lock(
|
|
1185
1824
|
rconn,
|
|
@@ -1187,7 +1826,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1187
1826
|
log_sampled,
|
|
1188
1827
|
log_lock_try,
|
|
1189
1828
|
lock_key,
|
|
1190
|
-
|
|
1829
|
+
rw_mode,
|
|
1191
1830
|
lock_readers_key,
|
|
1192
1831
|
request_queue,
|
|
1193
1832
|
opposite_request_queue,
|
|
@@ -1195,6 +1834,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1195
1834
|
acquirer_id,
|
|
1196
1835
|
host_id,
|
|
1197
1836
|
acquirer_position,
|
|
1837
|
+
priority,
|
|
1198
1838
|
acquirer_dead_score,
|
|
1199
1839
|
queue_ttl,
|
|
1200
1840
|
lock_index_enabled,
|
|
@@ -1202,13 +1842,13 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1202
1842
|
fail_fast,
|
|
1203
1843
|
access_strategy
|
|
1204
1844
|
)
|
|
1205
|
-
read_mode = (
|
|
1845
|
+
read_mode = (rw_mode == :read)
|
|
1206
1846
|
# NOTE: the same pipeline layout and the same reply reading as in Step 0.1 of the attempt
|
|
1207
1847
|
# @type var lock_state: Array[untyped]
|
|
1208
1848
|
lock_state = rconn.pipelined do |pipeline|
|
|
1209
1849
|
pipeline_lock_state(
|
|
1210
1850
|
pipeline, lock_key, lock_readers_key, acquirer_id, read_mode, !fail_fast,
|
|
1211
|
-
request_queue, opposite_request_queue, acquirer_position, acquirer_dead_score,
|
|
1851
|
+
request_queue, opposite_request_queue, acquirer_position, priority, acquirer_dead_score,
|
|
1212
1852
|
lock_name, queue_ttl, lock_index_enabled, lock_index_grace_period
|
|
1213
1853
|
)
|
|
1214
1854
|
end
|
|
@@ -1228,12 +1868,26 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1228
1868
|
# NOTE: the fail-fast check of the attempt (Fast-Step X0) without same-process conflicts
|
|
1229
1869
|
return ((current_lock_obtainer || read_locked) ? :fail_fast_no_try : nil) if fail_fast
|
|
1230
1870
|
|
|
1231
|
-
# @type var
|
|
1232
|
-
|
|
1233
|
-
lock_state[LOCK_STATE_QUEUE_REPLIES_AT + QUEUE_HEAD_REPLY].first
|
|
1871
|
+
# @type var waiting_request: [String, Float]?
|
|
1872
|
+
waiting_request = lock_state[LOCK_STATE_QUEUE_REPLIES_AT + QUEUE_HEAD_REPLY].first
|
|
1234
1873
|
# @type var opposite_waiting_request: [String, Float]?
|
|
1235
1874
|
opposite_waiting_request =
|
|
1236
1875
|
lock_state[LOCK_STATE_QUEUE_REPLIES_AT + OPPOSITE_QUEUE_HEAD_REPLY].first
|
|
1876
|
+
# NOTE: (priorities) Step 2.2 of the attempt: the dead lock requests of other priority bands
|
|
1877
|
+
# that block the current one are dropped before the obstacle decision; they are logged at
|
|
1878
|
+
# the step of the attempt (after the queueing log events of the failed pre-check, they
|
|
1879
|
+
# follow the decision) or before the attempt that continues (it logs its own queueing);
|
|
1880
|
+
actual_queue_heads = drop_dead_requests_ahead(
|
|
1881
|
+
rconn, rw_mode, request_queue, opposite_request_queue, acquirer_id,
|
|
1882
|
+
acquirer_position, acquirer_dead_score, access_strategy, waiting_request,
|
|
1883
|
+
opposite_waiting_request
|
|
1884
|
+
)
|
|
1885
|
+
# @type var dropped_requests: Array[[String, Integer]]?
|
|
1886
|
+
dropped_requests = nil
|
|
1887
|
+
if actual_queue_heads
|
|
1888
|
+
waiting_request, opposite_waiting_request, dropped_requests = actual_queue_heads
|
|
1889
|
+
end
|
|
1890
|
+
waiting_acquirer = waiting_request ? waiting_request[0] : nil
|
|
1237
1891
|
# @type var opposite_request_ahead: String?
|
|
1238
1892
|
opposite_request_ahead = find_opposite_request_ahead(
|
|
1239
1893
|
access_strategy, opposite_waiting_request, acquirer_position, acquirer_id
|
|
@@ -1242,25 +1896,44 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
1242
1896
|
read_mode, access_strategy, acquirer_id, waiting_acquirer, opposite_request_ahead,
|
|
1243
1897
|
current_lock_obtainer, read_locked
|
|
1244
1898
|
)
|
|
1245
|
-
|
|
1899
|
+
unless obstacle
|
|
1900
|
+
if dropped_requests
|
|
1901
|
+
log_dropped_requests(
|
|
1902
|
+
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
1903
|
+
access_strategy, rw_mode, dropped_requests
|
|
1904
|
+
)
|
|
1905
|
+
end
|
|
1906
|
+
return
|
|
1907
|
+
end
|
|
1908
|
+
|
|
1909
|
+
# NOTE: the priority of the first request is decoded for logs only
|
|
1910
|
+
waiting_priority = logged_request_priority(log_sampled, log_lock_try, waiting_request)
|
|
1246
1911
|
|
|
1247
|
-
# NOTE: the same log events as the failed attempt (queueing steps
|
|
1912
|
+
# NOTE: the same log events as the failed attempt in the same order (queueing steps, the dropped
|
|
1913
|
+
# dead requests, the first request, the exit event)
|
|
1248
1914
|
LogVisitor.acq_added_to_queue(
|
|
1249
1915
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1250
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1916
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, priority
|
|
1251
1917
|
)
|
|
1252
1918
|
LogVisitor.remove_expired_acqs(
|
|
1253
1919
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1254
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1920
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
|
|
1255
1921
|
)
|
|
1922
|
+
if dropped_requests
|
|
1923
|
+
log_dropped_requests(
|
|
1924
|
+
logger, log_sampled, log_lock_try, lock_key, queue_ttl, acquirer_id, host_id,
|
|
1925
|
+
access_strategy, rw_mode, dropped_requests
|
|
1926
|
+
)
|
|
1927
|
+
end
|
|
1256
1928
|
LogVisitor.get_first_from_queue(
|
|
1257
1929
|
logger, log_sampled, log_lock_try, lock_key,
|
|
1258
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
1930
|
+
queue_ttl, acquirer_id, host_id, access_strategy, rw_mode, waiting_acquirer,
|
|
1931
|
+
waiting_priority
|
|
1259
1932
|
)
|
|
1260
1933
|
log_lock_obstacle(
|
|
1261
1934
|
rconn, obstacle, logger, log_sampled, log_lock_try, lock_key, queue_ttl,
|
|
1262
|
-
acquirer_id, host_id, access_strategy,
|
|
1263
|
-
waiting_acquirer,
|
|
1935
|
+
acquirer_id, host_id, access_strategy, rw_mode,
|
|
1936
|
+
waiting_acquirer, waiting_priority, opposite_waiting_request, current_lock_obtainer
|
|
1264
1937
|
)
|
|
1265
1938
|
obstacle
|
|
1266
1939
|
end
|