redis_queued_locks 1.1.0 → 1.3.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/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
|
|