redis_queued_locks 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -3
- data/README.md +262 -60
- data/lib/redis_queued_locks/acquier/acquire_lock/delay_execution.rb +2 -2
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +239 -12
- data/lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +115 -0
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +200 -89
- data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +3 -3
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +3 -3
- data/lib/redis_queued_locks/acquier/is_locked.rb +2 -2
- data/lib/redis_queued_locks/acquier/is_queued.rb +2 -2
- data/lib/redis_queued_locks/acquier/keys.rb +2 -2
- data/lib/redis_queued_locks/acquier/lock_info.rb +19 -3
- data/lib/redis_queued_locks/acquier/locks.rb +13 -4
- data/lib/redis_queued_locks/acquier/queue_info.rb +2 -2
- data/lib/redis_queued_locks/acquier/queues.rb +4 -4
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +4 -4
- data/lib/redis_queued_locks/acquier/release_lock.rb +4 -4
- data/lib/redis_queued_locks/acquier.rb +1 -1
- data/lib/redis_queued_locks/client.rb +50 -22
- data/lib/redis_queued_locks/debugger/interface.rb +4 -4
- data/lib/redis_queued_locks/debugger.rb +8 -8
- data/lib/redis_queued_locks/errors.rb +10 -6
- data/lib/redis_queued_locks/instrument/active_support.rb +2 -2
- data/lib/redis_queued_locks/instrument/void_notifier.rb +2 -2
- data/lib/redis_queued_locks/instrument.rb +2 -2
- data/lib/redis_queued_locks/logging/void_logger.rb +10 -10
- data/lib/redis_queued_locks/logging.rb +10 -3
- data/lib/redis_queued_locks/resource.rb +22 -16
- data/lib/redis_queued_locks/utilities.rb +2 -2
- data/lib/redis_queued_locks/version.rb +2 -2
- data/lib/redis_queued_locks.rb +2 -2
- metadata +4 -4
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +0 -72
@@ -1,34 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
# rubocop:disable Metrics/ModuleLength
|
6
6
|
# rubocop:disable Metrics/MethodLength
|
7
7
|
# rubocop:disable Metrics/ClassLength
|
8
8
|
# rubocop:disable Metrics/BlockNesting
|
9
|
+
# rubocop:disable Style/IfInsideElse
|
9
10
|
module RedisQueuedLocks::Acquier::AcquireLock
|
10
11
|
require_relative 'acquire_lock/delay_execution'
|
11
12
|
require_relative 'acquire_lock/with_acq_timeout'
|
12
|
-
require_relative 'acquire_lock/
|
13
|
+
require_relative 'acquire_lock/yield_expire'
|
13
14
|
require_relative 'acquire_lock/try_to_lock'
|
14
15
|
|
15
|
-
# @since
|
16
|
+
# @since 1.0.0
|
16
17
|
extend TryToLock
|
17
|
-
# @since
|
18
|
+
# @since 1.0.0
|
18
19
|
extend DelayExecution
|
19
|
-
# @since
|
20
|
-
extend
|
21
|
-
# @since
|
20
|
+
# @since 1.3.0
|
21
|
+
extend YieldExpire
|
22
|
+
# @since 1.0.0
|
22
23
|
extend WithAcqTimeout
|
23
|
-
# @since
|
24
|
+
# @since 1.0.0
|
24
25
|
extend RedisQueuedLocks::Utilities
|
25
26
|
|
26
|
-
# @return [Integer] Redis time error (in milliseconds).
|
27
|
-
#
|
28
|
-
# @api private
|
29
|
-
# @since 0.1.0
|
30
|
-
REDIS_TIMESHIFT_ERROR = 2
|
31
|
-
|
32
27
|
class << self
|
33
28
|
# @param redis [RedisClient]
|
34
29
|
# Redis connection client.
|
@@ -72,14 +67,25 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
72
67
|
# - Metadata can not contain reserved lock data keys;
|
73
68
|
# @option logger [::Logger,#debug]
|
74
69
|
# - Logger object used from the configuration layer (see config[:logger]);
|
75
|
-
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
70
|
+
# - See `RedisQueuedLocks::Logging::VoidLogger` for example;
|
71
|
+
# - Supports `SemanticLogger::Logger` (see "semantic_logger" gem)
|
76
72
|
# @option log_lock_try [Boolean]
|
77
73
|
# - should be logged the each try of lock acquiring (a lot of logs can be generated depending
|
78
74
|
# on your retry configurations);
|
79
75
|
# - see `config[:log_lock_try]`;
|
80
76
|
# @option instrument [NilClass,Any]
|
81
|
-
#
|
82
|
-
#
|
77
|
+
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
78
|
+
# with :instrument key;
|
79
|
+
# @option conflict_strategy [Symbol]
|
80
|
+
# - The conflict strategy mode for cases when the process that obtained the lock
|
81
|
+
# want to acquire this lock again;
|
82
|
+
# - By default uses `:wait_for_lock` strategy;
|
83
|
+
# - pre-confured in `config[:default_conflict_strategy]`;
|
84
|
+
# - Supports:
|
85
|
+
# - `:work_through`;
|
86
|
+
# - `:extendable_work_through`;
|
87
|
+
# - `:wait_for_lock`;
|
88
|
+
# - `:dead_locking`;
|
83
89
|
# @param [Block]
|
84
90
|
# A block of code that should be executed after the successfully acquired lock.
|
85
91
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -87,7 +93,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
87
93
|
# - If block is given the result of block's yeld will be returned.
|
88
94
|
#
|
89
95
|
# @api private
|
90
|
-
# @since
|
96
|
+
# @since 1.0.0
|
97
|
+
# @version 1.3.0
|
91
98
|
def acquire_lock(
|
92
99
|
redis,
|
93
100
|
lock_name,
|
@@ -110,6 +117,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
110
117
|
instrument:,
|
111
118
|
logger:,
|
112
119
|
log_lock_try:,
|
120
|
+
conflict_strategy:,
|
113
121
|
&block
|
114
122
|
)
|
115
123
|
# Step 0: Prevent argument type incompatabilities
|
@@ -129,12 +137,18 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
129
137
|
key == 'ts' ||
|
130
138
|
key == 'ini_ttl' ||
|
131
139
|
key == 'lock_key' ||
|
132
|
-
key == 'rem_ttl'
|
140
|
+
key == 'rem_ttl' ||
|
141
|
+
key == 'spc_ext_ttl' ||
|
142
|
+
key == 'spc_cnt' ||
|
143
|
+
key == 'l_spc_ext_ini_ttl' ||
|
144
|
+
key == 'l_spc_ext_ts' ||
|
145
|
+
key == 'l_spc_ts'
|
133
146
|
end)
|
134
147
|
raise(
|
135
148
|
RedisQueuedLocks::ArgumentError,
|
136
|
-
'`:meta` keys can not overlap reserved lock data keys' \
|
137
|
-
'"acq_id", "ts", "ini_ttl", "lock_key", "rem_ttl"'
|
149
|
+
'`:meta` keys can not overlap reserved lock data keys ' \
|
150
|
+
'"acq_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
|
151
|
+
'"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts"'
|
138
152
|
)
|
139
153
|
end
|
140
154
|
|
@@ -223,6 +237,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
223
237
|
lock_ttl,
|
224
238
|
queue_ttl,
|
225
239
|
fail_fast,
|
240
|
+
conflict_strategy,
|
226
241
|
meta
|
227
242
|
) => { ok:, result: }
|
228
243
|
|
@@ -235,26 +250,75 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
235
250
|
|
236
251
|
# Step 2.1: analyze an acquirement attempt
|
237
252
|
if ok
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
253
|
+
# Step X: (instrumentation)
|
254
|
+
if acq_process[:result][:process] == :extendable_conflict_work_through
|
255
|
+
# instrumetnation: (reentrant lock with ttl extension)
|
256
|
+
run_non_critical do
|
257
|
+
logger.debug do
|
258
|
+
"[redis_queued_locks.extendable_reentrant_lock_obtained] " \
|
259
|
+
"lock_key => '#{result[:lock_key]}' " \
|
260
|
+
"queue_ttl => #{queue_ttl} " \
|
261
|
+
"acq_id => '#{acquier_id}' " \
|
262
|
+
"acq_time => #{acq_time} (ms)"
|
263
|
+
end
|
245
264
|
end
|
246
|
-
end
|
247
265
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
266
|
+
run_non_critical do
|
267
|
+
instrumenter.notify('redis_queued_locks.extendable_reentrant_lock_obtained', {
|
268
|
+
lock_key: result[:lock_key],
|
269
|
+
ttl: result[:ttl],
|
270
|
+
acq_id: result[:acq_id],
|
271
|
+
ts: result[:ts],
|
272
|
+
acq_time: acq_time,
|
273
|
+
instrument:
|
274
|
+
})
|
275
|
+
end
|
276
|
+
elsif acq_process[:result][:process] == :conflict_work_through
|
277
|
+
# instrumetnation: (reentrant lock without ttl extension)
|
278
|
+
run_non_critical do
|
279
|
+
logger.debug do
|
280
|
+
"[redis_queued_locks.reentrant_lock_obtained] " \
|
281
|
+
"lock_key => '#{result[:lock_key]}' " \
|
282
|
+
"queue_ttl => #{queue_ttl} " \
|
283
|
+
"acq_id => '#{acquier_id}' " \
|
284
|
+
"acq_time => #{acq_time} (ms)"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
run_non_critical do
|
289
|
+
instrumenter.notify('redis_queued_locks.reentrant_lock_obtained', {
|
290
|
+
lock_key: result[:lock_key],
|
291
|
+
ttl: result[:ttl],
|
292
|
+
acq_id: result[:acq_id],
|
293
|
+
ts: result[:ts],
|
294
|
+
acq_time: acq_time,
|
295
|
+
instrument:
|
296
|
+
})
|
297
|
+
end
|
298
|
+
else
|
299
|
+
# instrumentation: (classic lock obtain)
|
300
|
+
# NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
|
301
|
+
run_non_critical do
|
302
|
+
logger.debug do
|
303
|
+
"[redis_queued_locks.lock_obtained] " \
|
304
|
+
"lock_key => '#{result[:lock_key]}' " \
|
305
|
+
"queue_ttl => #{queue_ttl} " \
|
306
|
+
"acq_id => '#{acquier_id}' " \
|
307
|
+
"acq_time => #{acq_time} (ms)"
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Step X (instrumentation): lock obtained
|
312
|
+
run_non_critical do
|
313
|
+
instrumenter.notify('redis_queued_locks.lock_obtained', {
|
314
|
+
lock_key: result[:lock_key],
|
315
|
+
ttl: result[:ttl],
|
316
|
+
acq_id: result[:acq_id],
|
317
|
+
ts: result[:ts],
|
318
|
+
acq_time: acq_time,
|
319
|
+
instrument:
|
320
|
+
})
|
321
|
+
end
|
258
322
|
end
|
259
323
|
|
260
324
|
# Step 2.1.a: successfully acquired => build the result
|
@@ -262,54 +326,73 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
262
326
|
lock_key: result[:lock_key],
|
263
327
|
acq_id: result[:acq_id],
|
264
328
|
ts: result[:ts],
|
265
|
-
ttl: result[:ttl]
|
329
|
+
ttl: result[:ttl],
|
330
|
+
process: result[:process]
|
266
331
|
}
|
267
332
|
acq_process[:acquired] = true
|
268
333
|
acq_process[:should_try] = false
|
269
334
|
acq_process[:acq_time] = acq_time
|
270
335
|
acq_process[:acq_end_time] = acq_end_time
|
271
|
-
elsif fail_fast && acq_process[:result] == :fail_fast_no_try
|
272
|
-
acq_process[:should_try] = false
|
273
|
-
if raise_errors
|
274
|
-
raise(
|
275
|
-
RedisQueuedLocks::LockAlreadyObtainedError,
|
276
|
-
"Lock \"#{lock_key}\" is already obtained."
|
277
|
-
)
|
278
|
-
end
|
279
336
|
else
|
280
|
-
# Step 2.
|
281
|
-
acq_process[:
|
282
|
-
|
283
|
-
if (retry_count != nil && acq_process[:tries] >= retry_count) || fail_fast
|
284
|
-
# NOTE:
|
285
|
-
# - reached the retry limit => quit from the loop
|
286
|
-
# - should fail fast => quit from the loop
|
337
|
+
# Step 2.2: failed to acquire. anylize each case and act in accordance
|
338
|
+
if acq_process[:result] == :fail_fast_no_try # Step 2.2.a: fail without try
|
287
339
|
acq_process[:should_try] = false
|
288
|
-
acq_process[:result] = fail_fast ? :fail_fast_after_try : :retry_limit_reached
|
289
340
|
|
290
|
-
|
291
|
-
# - reached the retry limit => dequeue from the lock queue
|
292
|
-
# - should fail fast => dequeue from the lock queue
|
293
|
-
acq_dequeue.call
|
294
|
-
|
295
|
-
# NOTE: check and raise an error
|
296
|
-
if fail_fast && raise_errors
|
341
|
+
if raise_errors
|
297
342
|
raise(
|
298
343
|
RedisQueuedLocks::LockAlreadyObtainedError,
|
299
344
|
"Lock \"#{lock_key}\" is already obtained."
|
300
345
|
)
|
301
|
-
|
346
|
+
end
|
347
|
+
elsif acq_process[:result] == :conflict_dead_lock # Step 2.2.b: fail after dead lock
|
348
|
+
acq_process[:tries] += 1
|
349
|
+
acq_process[:should_try] = false
|
350
|
+
acq_process[:result] = :conflict_dead_lock
|
351
|
+
acq_dequeue.call
|
352
|
+
|
353
|
+
if raise_errors
|
302
354
|
raise(
|
303
|
-
|
304
|
-
"
|
305
|
-
"
|
355
|
+
RedisQueuedLock::ConflictLockObtainError,
|
356
|
+
"Lock Conflict: trying to acquire the lock \"#{lock_key}\" " \
|
357
|
+
"that is already acquired by the current acquier (acq_id: \"#{acquired_id}\")."
|
306
358
|
)
|
307
359
|
end
|
308
360
|
else
|
309
|
-
#
|
310
|
-
|
311
|
-
|
312
|
-
|
361
|
+
acq_process[:tries] += 1 # Step RETRY: possible retry case
|
362
|
+
|
363
|
+
if fail_fast # Step RETRY.A: fail after try
|
364
|
+
acq_process[:should_try] = false
|
365
|
+
acq_process[:result] = :fail_fast_after_try
|
366
|
+
acq_dequeue.call
|
367
|
+
|
368
|
+
if raise_errors
|
369
|
+
raise(
|
370
|
+
RedisQueuedLocks::LockAlreadyObtainedError,
|
371
|
+
"Lock \"#{lock_key}\" is already obtained."
|
372
|
+
)
|
373
|
+
end
|
374
|
+
else
|
375
|
+
# Step RETRY.B: fail cuz the retry count is reached
|
376
|
+
if retry_count != nil && acq_process[:tries] >= retry_count
|
377
|
+
acq_process[:should_try] = false
|
378
|
+
acq_process[:result] = :retry_limit_reached
|
379
|
+
acq_dequeue.call
|
380
|
+
|
381
|
+
if raise_errors
|
382
|
+
raise(
|
383
|
+
RedisQueuedLocks::LockAcquiermentRetryLimitError,
|
384
|
+
"Failed to acquire the lock \"#{lock_key}\" " \
|
385
|
+
"for the given retry_count limit (#{retry_count} times)."
|
386
|
+
)
|
387
|
+
end
|
388
|
+
else
|
389
|
+
# Step RETRY.X: no significant failures => retry easily :)
|
390
|
+
# NOTE:
|
391
|
+
# delay the exceution in order to prevent chaotic lock-acquire attempts
|
392
|
+
# and to allow other processes and threads to obtain the lock too.
|
393
|
+
delay_execution(retry_delay, retry_jitter)
|
394
|
+
end
|
395
|
+
end
|
313
396
|
end
|
314
397
|
end
|
315
398
|
end
|
@@ -323,10 +406,18 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
323
406
|
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
324
407
|
|
325
408
|
ttl_shift = (
|
326
|
-
(yield_time - acq_process[:acq_end_time]) * 1000 -
|
409
|
+
(yield_time - acq_process[:acq_end_time]) * 1000 -
|
410
|
+
RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
|
327
411
|
).ceil(2)
|
328
412
|
|
329
|
-
|
413
|
+
should_expire =
|
414
|
+
acq_process[:result][:process] != :extendable_conflict_work_through &&
|
415
|
+
acq_process[:result][:process] != :conflict_work_through
|
416
|
+
|
417
|
+
should_decrease =
|
418
|
+
acq_process[:result][:process] == :extendable_conflict_work_through
|
419
|
+
|
420
|
+
yield_expire(
|
330
421
|
redis,
|
331
422
|
logger,
|
332
423
|
lock_key,
|
@@ -335,6 +426,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
335
426
|
ttl_shift,
|
336
427
|
ttl,
|
337
428
|
queue_ttl,
|
429
|
+
should_expire, # NOTE: should expire the lock after the block execution
|
430
|
+
should_decrease, # NOTE: should decrease the lock ttl in reentrant locks?
|
338
431
|
&block
|
339
432
|
)
|
340
433
|
ensure
|
@@ -343,17 +436,33 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
343
436
|
(acq_process[:rel_time] - acq_process[:acq_end_time]) * 1000
|
344
437
|
).ceil(2)
|
345
438
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
439
|
+
if acq_process[:result][:process] == :extendable_conflict_work_through ||
|
440
|
+
acq_process[:result][:process] == :conflict_work_through
|
441
|
+
# Step X (instrumentation): reentrant_lock_hold_completes
|
442
|
+
run_non_critical do
|
443
|
+
instrumenter.notify('redis_queued_locks.reentrant_lock_hold_completes', {
|
444
|
+
hold_time: acq_process[:hold_time],
|
445
|
+
ttl: acq_process[:lock_info][:ttl],
|
446
|
+
acq_id: acq_process[:lock_info][:acq_id],
|
447
|
+
ts: acq_process[:lock_info][:ts],
|
448
|
+
lock_key: acq_process[:lock_info][:lock_key],
|
449
|
+
acq_time: acq_process[:acq_time],
|
450
|
+
instrument:
|
451
|
+
})
|
452
|
+
end
|
453
|
+
else
|
454
|
+
# Step X (instrumentation): lock_hold_and_release
|
455
|
+
run_non_critical do
|
456
|
+
instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
|
457
|
+
hold_time: acq_process[:hold_time],
|
458
|
+
ttl: acq_process[:lock_info][:ttl],
|
459
|
+
acq_id: acq_process[:lock_info][:acq_id],
|
460
|
+
ts: acq_process[:lock_info][:ts],
|
461
|
+
lock_key: acq_process[:lock_info][:lock_key],
|
462
|
+
acq_time: acq_process[:acq_time],
|
463
|
+
instrument:
|
464
|
+
})
|
465
|
+
end
|
357
466
|
end
|
358
467
|
end
|
359
468
|
else
|
@@ -362,11 +471,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
362
471
|
else
|
363
472
|
if acq_process[:result] != :retry_limit_reached &&
|
364
473
|
acq_process[:result] != :fail_fast_no_try &&
|
365
|
-
acq_process[:result] != :fail_fast_after_try
|
366
|
-
|
367
|
-
#
|
368
|
-
# -
|
369
|
-
#
|
474
|
+
acq_process[:result] != :fail_fast_after_try &&
|
475
|
+
acq_process[:result] != :conflict_dead_lock
|
476
|
+
# NOTE: we have only two situations if lock is not acquired without explicit failures:
|
477
|
+
# - time limit is reached;
|
478
|
+
# - retry count limit is reached;
|
479
|
+
# - **(notice: in other cases the lock obtaining time and tries count are infinite)
|
370
480
|
acq_process[:result] = :timeout_reached
|
371
481
|
end
|
372
482
|
# Step 3.b: lock is not acquired (acquier is dequeued by timeout callback)
|
@@ -379,3 +489,4 @@ end
|
|
379
489
|
# rubocop:enable Metrics/MethodLength
|
380
490
|
# rubocop:enable Metrics/ClassLength
|
381
491
|
# rubocop:enable Metrics/BlockNesting
|
492
|
+
# rubocop:enable Style/IfInsideElse
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::ClearDeadRequests
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
@@ -13,7 +13,7 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
|
|
13
13
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
|
14
14
|
#
|
15
15
|
# @api private
|
16
|
-
# @since
|
16
|
+
# @since 1.0.0
|
17
17
|
def clear_dead_requests(redis_client, scan_size, dead_ttl, logger, instrumenter, instrument)
|
18
18
|
dead_score = RedisQueuedLocks::Resource.acquier_dead_score(dead_ttl / 1000.0)
|
19
19
|
|
@@ -39,7 +39,7 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
|
|
39
39
|
# @return [Enumerator]
|
40
40
|
#
|
41
41
|
# @api private
|
42
|
-
# @since
|
42
|
+
# @since 1.0.0
|
43
43
|
def each_lock_queue(redis_client, scan_size, &block)
|
44
44
|
redis_client.scan(
|
45
45
|
'MATCH',
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::ExtendLockTTL
|
6
6
|
# @return [String]
|
7
7
|
#
|
8
8
|
# @api private
|
9
|
-
# @since
|
9
|
+
# @since 1.0.0
|
10
10
|
EXTEND_LOCK_PTTL = <<~LUA_SCRIPT.strip.tr("\n", '').freeze
|
11
11
|
local new_lock_pttl = redis.call("PTTL", KEYS[1]) + ARGV[1];
|
12
12
|
return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
|
@@ -20,7 +20,7 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
|
|
20
20
|
# @return [Hash<Symbol,Boolean|Symbol>]
|
21
21
|
#
|
22
22
|
# @api private
|
23
|
-
# @since
|
23
|
+
# @since 1.0.0
|
24
24
|
def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
|
25
25
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
26
26
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::IsLocked
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
@@ -9,7 +9,7 @@ module RedisQueuedLocks::Acquier::IsLocked
|
|
9
9
|
# @return [Boolean]
|
10
10
|
#
|
11
11
|
# @api private
|
12
|
-
# @since
|
12
|
+
# @since 1.0.0
|
13
13
|
def locked?(redis_client, lock_name)
|
14
14
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
15
15
|
redis_client.call('EXISTS', lock_key) == 1
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::IsQueued
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
@@ -9,7 +9,7 @@ module RedisQueuedLocks::Acquier::IsQueued
|
|
9
9
|
# @return [Boolean]
|
10
10
|
#
|
11
11
|
# @api private
|
12
|
-
# @since
|
12
|
+
# @since 1.0.0
|
13
13
|
def queued?(redis_client, lock_name)
|
14
14
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
15
15
|
redis_client.call('EXISTS', lock_key_queue) == 1
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::Keys
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
@@ -9,7 +9,7 @@ module RedisQueuedLocks::Acquier::Keys
|
|
9
9
|
# @return [Array<String>]
|
10
10
|
#
|
11
11
|
# @api private
|
12
|
-
# @since
|
12
|
+
# @since 1.0.0
|
13
13
|
def keys(redis_client, scan_size:)
|
14
14
|
Set.new.tap do |keys|
|
15
15
|
redis_client.scan(
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::LockInfo
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
@@ -12,12 +12,20 @@ module RedisQueuedLocks::Acquier::LockInfo
|
|
12
12
|
# 'lock_key' => "rql:lock:your_lockname", # acquired lock key
|
13
13
|
# 'acq_id' => "rql:acq:process_id/thread_id", # lock acquier identifier
|
14
14
|
# 'ts' => 123456789.2649841, # <locked at> time stamp (epoch, seconds.microseconds)
|
15
|
-
# 'ini_ttl' => 123456789, # initial lock key ttl (milliseconds)
|
15
|
+
# 'ini_ttl' => 123456789, # initial lock key ttl (milliseconds)
|
16
16
|
# 'rem_ttl' => 123456789, # remaining lock key ttl (milliseconds)
|
17
|
+
# <additional keys for reentrant locks>:
|
18
|
+
# 'spc_cnt' => 2, # lock reentreing count (if lock was used as reentrant lock)
|
19
|
+
# 'l_spc_ts' => 123456.1234 # (epoch) <non-extendable reentrant lock obtained at> timestamp
|
20
|
+
# 'spc_ext_ttl' => 14500, # (milliseconds) the sum of all ttl extensions
|
21
|
+
# 'l_spc_ext_ini_ttl' => 5000, # (milliseconds) the last ttl of reentrant lock
|
22
|
+
# 'l_spc_ext_ts' => 123456.789 # (epoch) <extendable reentrant lock obtained at> timestamp
|
17
23
|
# }
|
18
24
|
#
|
19
25
|
# @api private
|
20
|
-
# @since
|
26
|
+
# @since 1.0.0
|
27
|
+
# @version 1.3.0
|
28
|
+
# rubocop:disable Metrics/MethodLength
|
21
29
|
def lock_info(redis_client, lock_name)
|
22
30
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
23
31
|
|
@@ -48,9 +56,17 @@ module RedisQueuedLocks::Acquier::LockInfo
|
|
48
56
|
lock_data['ts'] = Float(lock_data['ts'])
|
49
57
|
lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
|
50
58
|
lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
|
59
|
+
lock_data['spc_cnt'] = Integer(lock_data['spc_cnt']) if lock_data['spc_cnt']
|
60
|
+
lock_data['l_spc_ts'] = Float(lock_data['l_spc_ts']) if lock_data['l_spc_ts']
|
61
|
+
lock_data['spc_ext_ttl'] = Integer(lock_data['spc_ext_ttl']) if lock_data['spc_ext_ttl']
|
62
|
+
lock_data['l_spc_ext_ini_ttl'] =
|
63
|
+
Integer(lock_data['l_spc_ext_ini_ttl']) if lock_data.key?('l_spc_ext_ini_ttl')
|
64
|
+
lock_data['l_spc_ext_ts'] =
|
65
|
+
Float(lock_data['l_spc_ext_ts']) if lock_data['l_spc_ext_ts']
|
51
66
|
end
|
52
67
|
end
|
53
68
|
end
|
54
69
|
end
|
70
|
+
# rubocop:enable Metrics/MethodLength
|
55
71
|
end
|
56
72
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::Locks
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
@@ -10,7 +10,7 @@ module RedisQueuedLocks::Acquier::Locks
|
|
10
10
|
# @return [Set<String>,Set<Hash<Symbol,Any>>]
|
11
11
|
#
|
12
12
|
# @api private
|
13
|
-
# @since
|
13
|
+
# @since 1.0.0
|
14
14
|
def locks(redis_client, scan_size:, with_info:)
|
15
15
|
redis_client.with do |rconn|
|
16
16
|
lock_keys = scan_locks(rconn, scan_size)
|
@@ -25,7 +25,7 @@ module RedisQueuedLocks::Acquier::Locks
|
|
25
25
|
# @return [Set<String>]
|
26
26
|
#
|
27
27
|
# @api private
|
28
|
-
# @since
|
28
|
+
# @since 1.0.0
|
29
29
|
def scan_locks(redis_client, scan_size)
|
30
30
|
Set.new.tap do |lock_keys|
|
31
31
|
redis_client.scan(
|
@@ -44,7 +44,8 @@ module RedisQueuedLocks::Acquier::Locks
|
|
44
44
|
# @return [Set<Hash<Symbol,Any>>]
|
45
45
|
#
|
46
46
|
# @api private
|
47
|
-
# @since
|
47
|
+
# @since 1.0.0
|
48
|
+
# @version 1.3.0
|
48
49
|
# rubocop:disable Metrics/MethodLength
|
49
50
|
def extract_locks_info(redis_client, lock_keys)
|
50
51
|
# TODO: refactor with RedisQueuedLocks::Acquier::LockInfo
|
@@ -72,6 +73,14 @@ module RedisQueuedLocks::Acquier::Locks
|
|
72
73
|
lock_data['ts'] = Float(lock_data['ts'])
|
73
74
|
lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
|
74
75
|
lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
|
76
|
+
lock_data['spc_cnt'] = Integer(lock_data['spc_cnt']) if lock_data['spc_cnt']
|
77
|
+
lock_data['l_spc_ts'] = Float(lock_data['l_spc_ts']) if lock_data['l_spc_ts']
|
78
|
+
lock_data['spc_ext_ttl'] =
|
79
|
+
Integer(lock_data['spc_ext_ttl']) if lock_data['spc_ext_ttl']
|
80
|
+
lock_data['l_spc_ext_ini_ttl'] =
|
81
|
+
Integer(lock_data['l_spc_ext_ini_ttl']) if lock_data.key?('l_spc_ext_ini_ttl')
|
82
|
+
lock_data['l_spc_ext_ts'] =
|
83
|
+
Float(lock_data['l_spc_ext_ts']) if lock_data['l_spc_ext_ts']
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
# @since
|
4
|
+
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Acquier::QueueInfo
|
6
6
|
class << self
|
7
7
|
# Returns an information about the required lock queue by the lock name. The result
|
@@ -23,7 +23,7 @@ module RedisQueuedLocks::Acquier::QueueInfo
|
|
23
23
|
# }
|
24
24
|
#
|
25
25
|
# @api private
|
26
|
-
# @since
|
26
|
+
# @since 1.0.0
|
27
27
|
def queue_info(redis_client, lock_name)
|
28
28
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
29
29
|
|