redis_queued_locks 1.2.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 +11 -0
- data/README.md +206 -47
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +235 -8
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +115 -0
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +194 -83
- data/lib/redis_queued_locks/acquier/lock_info.rb +17 -1
- data/lib/redis_queued_locks/acquier/locks.rb +9 -0
- data/lib/redis_queued_locks/client.rb +29 -2
- data/lib/redis_queued_locks/errors.rb +4 -0
- data/lib/redis_queued_locks/logging.rb +1 -1
- data/lib/redis_queued_locks/resource.rb +6 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +4 -4
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +0 -72
@@ -7,6 +7,15 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
7
7
|
# @since 1.0.0
|
8
8
|
extend RedisQueuedLocks::Utilities
|
9
9
|
|
10
|
+
# @return [String]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 1.3.0
|
14
|
+
EXTEND_LOCK_PTTL = <<~LUA_SCRIPT.strip.tr("\n", '').freeze
|
15
|
+
local new_lock_pttl = redis.call("PTTL", KEYS[1]) + ARGV[1];
|
16
|
+
return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
|
17
|
+
LUA_SCRIPT
|
18
|
+
|
10
19
|
# @param redis [RedisClient]
|
11
20
|
# @param logger [::Logger,#debug]
|
12
21
|
# @param log_lock_try [Boolean]
|
@@ -17,11 +26,13 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
17
26
|
# @param ttl [Integer]
|
18
27
|
# @param queue_ttl [Integer]
|
19
28
|
# @param fail_fast [Boolean]
|
29
|
+
# @param conflict_strategy [Symbol]
|
20
30
|
# @param meta [NilClass,Hash<String|Symbol,Any>]
|
21
31
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol|Hash<Symbol,Any> }
|
22
32
|
#
|
23
33
|
# @api private
|
24
34
|
# @since 1.0.0
|
35
|
+
# @version 1.3.0
|
25
36
|
# rubocop:disable Metrics/MethodLength
|
26
37
|
def try_to_lock(
|
27
38
|
redis,
|
@@ -34,11 +45,13 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
34
45
|
ttl,
|
35
46
|
queue_ttl,
|
36
47
|
fail_fast,
|
48
|
+
conflict_strategy,
|
37
49
|
meta
|
38
50
|
)
|
39
51
|
# Step X: intermediate invocation results
|
40
52
|
inter_result = nil
|
41
53
|
timestamp = nil
|
54
|
+
spc_processed_timestamp = nil
|
42
55
|
|
43
56
|
if log_lock_try
|
44
57
|
run_non_critical do
|
@@ -68,9 +81,162 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
68
81
|
# watch the lock key changes (and discard acquirement if lock is already
|
69
82
|
# obtained by another acquier during the current lock acquiremntt)
|
70
83
|
rconn.multi(watch: [lock_key]) do |transact|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
84
|
+
# SP-Conflict status PREPARING: get the current lock obtainer
|
85
|
+
current_lock_obtainer = rconn.call('HGET', lock_key, 'acq_id')
|
86
|
+
# SP-Conflict status PREPARING: status flag variable
|
87
|
+
sp_conflict_status = nil
|
88
|
+
|
89
|
+
# SP-Conflict Step X1: calculate the current deadlock status
|
90
|
+
if current_lock_obtainer != nil && acquier_id == current_lock_obtainer
|
91
|
+
if log_lock_try
|
92
|
+
run_non_critical do
|
93
|
+
logger.debug do
|
94
|
+
"[redis_queued_locks.try_lock.same_process_conflict_detected] " \
|
95
|
+
"lock_key => '#{lock_key}' " \
|
96
|
+
"queue_ttl => #{queue_ttl} " \
|
97
|
+
"acq_id => '#{acquier_id}'"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# SP-Conflict Step X2: self-process dead lock moment started.
|
103
|
+
# SP-Conflict CHECK (Step CHECK): check chosen strategy and flag the current status
|
104
|
+
# rubocop:disable Lint/DuplicateBranch
|
105
|
+
case conflict_strategy
|
106
|
+
when :work_through
|
107
|
+
# <SP-Conflict Moment>: work through => exit
|
108
|
+
sp_conflict_status = :conflict_work_through
|
109
|
+
when :extendable_work_through
|
110
|
+
# <SP-Conflict Moment>: extendable_work_through => extend the lock pttl and exit
|
111
|
+
sp_conflict_status = :extendable_conflict_work_through
|
112
|
+
when :wait_for_lock
|
113
|
+
# <SP-Conflict Moment>: wait_for_lock => obtain a lock in classic way
|
114
|
+
sp_conflict_status = :conflict_wait_for_lock
|
115
|
+
when :dead_locking
|
116
|
+
# <SP-Conflict Moment>: dead_locking => exit and fail
|
117
|
+
sp_conflict_status = :conflict_dead_lock
|
118
|
+
else
|
119
|
+
# <SP-Conflict Moment>:
|
120
|
+
# - unknown status => work in classic way <wait_for_lock>
|
121
|
+
# - it is a case when the new status is added to the code base in the past
|
122
|
+
# but is forgotten to be added here;
|
123
|
+
sp_conflict_status = :conflict_wait_for_lock
|
124
|
+
end
|
125
|
+
# rubocop:enable Lint/DuplicateBranch
|
126
|
+
|
127
|
+
if log_lock_try
|
128
|
+
run_non_critical do
|
129
|
+
logger.debug do
|
130
|
+
"[redis_queued_locks.try_lock.same_process_conflict_analyzed] " \
|
131
|
+
"lock_key => '#{lock_key}' " \
|
132
|
+
"queue_ttl => #{queue_ttl} " \
|
133
|
+
"acq_id => '#{acquier_id}' " \
|
134
|
+
"spc_status => '#{sp_conflict_status}'"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# SP-Conflict-Step X2: switch to conflict-based logic or not
|
141
|
+
if sp_conflict_status == :extendable_conflict_work_through
|
142
|
+
# SP-Conflict-Step FINAL (SPCF): extend the lock and work through
|
143
|
+
# - extend the lock ttl;
|
144
|
+
# - store extensions in lock metadata;
|
145
|
+
# SPCF Step 1: extend the lock pttl
|
146
|
+
# - [REDIS RESULT]: in normal cases should return the last script command value
|
147
|
+
# - for the current script should return:
|
148
|
+
# <1> => timeout was set;
|
149
|
+
# <0> => timeount was not set;
|
150
|
+
transact.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, ttl)
|
151
|
+
# SPCF Step <Meta>: store conflict-state additionals in lock metadata:
|
152
|
+
# SPCF Step 2: (lock meta-data)
|
153
|
+
# - add the added ttl to reflect the real lock TTL in info;
|
154
|
+
# - [REDIS RESULT]: in normal cases should return the value of <ttl> key
|
155
|
+
# - for non-existent key value starts from <0> (zero)
|
156
|
+
transact.call('HINCRBY', lock_key, 'spc_ext_ttl', ttl)
|
157
|
+
# SPCF Step 3: (lock meta-data)
|
158
|
+
# - increment the conflcit counter in order to remember
|
159
|
+
# how many times dead lock happened;
|
160
|
+
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
161
|
+
# - for non-existent key starts from 0
|
162
|
+
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
163
|
+
# SPCF Step 4: (lock meta-data)
|
164
|
+
# - remember the last ext-timestamp and the last ext-initial ttl;
|
165
|
+
# - [REDIS RESULT]: for normal cases should return the number of fields
|
166
|
+
# were added/changed;
|
167
|
+
transact.call(
|
168
|
+
'HSET',
|
169
|
+
lock_key,
|
170
|
+
'l_spc_ext_ts', (spc_processed_timestamp = Time.now.to_f),
|
171
|
+
'l_spc_ext_ini_ttl', ttl
|
172
|
+
)
|
173
|
+
inter_result = :extendable_conflict_work_through
|
174
|
+
|
175
|
+
if log_lock_try
|
176
|
+
run_non_critical do
|
177
|
+
logger.debug do
|
178
|
+
"[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through] " \
|
179
|
+
"lock_key => '#{lock_key}' " \
|
180
|
+
"queue_ttl => #{queue_ttl} " \
|
181
|
+
"acq_id => '#{acquier_id}'" \
|
182
|
+
"spc_status => '#{sp_conflict_status} '" \
|
183
|
+
"last_ext_ttl => '#{ttl}' " \
|
184
|
+
"last_ext_ts => '#{spc_processed_timestamp}'"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
# SP-Conflict-Step X2: switch to dead lock logic or not
|
189
|
+
elsif sp_conflict_status == :conflict_work_through
|
190
|
+
inter_result = :conflict_work_through
|
191
|
+
|
192
|
+
# SPCF Step X: (lock meta-data)
|
193
|
+
# - increment the conflcit counter in order to remember
|
194
|
+
# how many times dead lock happened;
|
195
|
+
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
196
|
+
# - for non-existent key starts from 0
|
197
|
+
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
198
|
+
# SPCF Step 4: (lock meta-data)
|
199
|
+
# - remember the last ext-timestamp and the last ext-initial ttl;
|
200
|
+
# - [REDIS RESULT]: for normal cases should return the number of fields
|
201
|
+
# were added/changed;
|
202
|
+
transact.call(
|
203
|
+
'HSET',
|
204
|
+
lock_key,
|
205
|
+
'l_spc_ts', (spc_processed_timestamp = Time.now.to_f)
|
206
|
+
)
|
207
|
+
|
208
|
+
if log_lock_try
|
209
|
+
run_non_critical do
|
210
|
+
logger.debug do
|
211
|
+
"[redis_queued_locks.try_lock.reentrant_lock__work_through] " \
|
212
|
+
"lock_key => '#{lock_key}' " \
|
213
|
+
"queue_ttl => #{queue_ttl} " \
|
214
|
+
"acq_id => '#{acquier_id}' " \
|
215
|
+
"spc_status => '#{sp_conflict_status} ' " \
|
216
|
+
"last_spc_ts => '#{spc_processed_timestamp}'"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
# SP-Conflict-Step X2: switch to dead lock logic or not
|
221
|
+
elsif sp_conflict_status == :conflict_dead_lock
|
222
|
+
inter_result = :conflict_dead_lock
|
223
|
+
spc_processed_timestamp = Time.now.to_f
|
224
|
+
|
225
|
+
if log_lock_try
|
226
|
+
logger.debug do
|
227
|
+
"[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock] " \
|
228
|
+
"lock_key => '#{lock_key}' " \
|
229
|
+
"queue_ttl => #{queue_ttl} " \
|
230
|
+
"acq_id => '#{acquier_id}' " \
|
231
|
+
"spc_status => '#{sp_conflict_status}' " \
|
232
|
+
"last_spc_ts => '#{spc_processed_timestamp}'"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
# Reached the SP-Non-Conflict Mode (NOTE):
|
236
|
+
# - in other sp-conflict cases we are in <wait_for_lock> (non-conflict) status and should
|
237
|
+
# continue to work in classic way (next lines of code):
|
238
|
+
elsif fail_fast && current_lock_obtainer != nil # Fast-Step X0: fail-fast check
|
239
|
+
# Fast-Step X1: lock is already obtained. fail fast leads to "no try".
|
74
240
|
inter_result = :fail_fast_no_try
|
75
241
|
else
|
76
242
|
# Step 1: add an acquier to the lock acquirement queue
|
@@ -216,6 +382,63 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
216
382
|
# Step 7: Analyze the aquirement attempt:
|
217
383
|
# rubocop:disable Lint/DuplicateBranch
|
218
384
|
case
|
385
|
+
when inter_result == :extendable_conflict_work_through
|
386
|
+
# Step 7.same_process_conflict.A:
|
387
|
+
# - extendable_conflict_work_through case => yield <block> without lock realesing/extending;
|
388
|
+
# - lock is extended in logic above;
|
389
|
+
# - if <result == nil> => the lock was changed during an extention:
|
390
|
+
# it is the fail case => go retry.
|
391
|
+
# - else: let's go! :))
|
392
|
+
if result.is_a?(::Array) && result.size == 4 # NOTE: four commands should be processed
|
393
|
+
# TODO:
|
394
|
+
# => (!) analyze the command result and do actions with the depending on it
|
395
|
+
# 1. EVAL (extend lock pttl) (OK for != nil)
|
396
|
+
# 2. HINCRBY (ttl extension) (OK for != nil)
|
397
|
+
# 3. HINCRBY (increased spc count) (OK for != nil)
|
398
|
+
# 4. HSET (store the last spc time and ttl data) (OK for == 2 or != nil)
|
399
|
+
if result[0] != nil && result[1] != nil && result[2] != nil && result[3] != nil
|
400
|
+
RedisQueuedLocks::Data[ok: true, result: {
|
401
|
+
process: :extendable_conflict_work_through,
|
402
|
+
lock_key: lock_key,
|
403
|
+
acq_id: acquier_id,
|
404
|
+
ts: spc_processed_timestamp,
|
405
|
+
ttl: ttl
|
406
|
+
}]
|
407
|
+
elsif result[0] != nil
|
408
|
+
# NOTE: that is enough to the fact that the lock is extended but <TODO>
|
409
|
+
# TODO: add detalized overview (log? some in-line code clarifications?) of the result
|
410
|
+
RedisQueuedLocks::Data[ok: true, result: {
|
411
|
+
process: :extendable_conflict_work_through,
|
412
|
+
lock_key: lock_key,
|
413
|
+
acq_id: acquier_id,
|
414
|
+
ts: spc_processed_timestamp,
|
415
|
+
ttl: ttl
|
416
|
+
}]
|
417
|
+
else
|
418
|
+
# NOTE: unknown behaviour :thinking:
|
419
|
+
RedisQueuedLocks::Data[ok: false, result: :unknown]
|
420
|
+
end
|
421
|
+
elsif result == nil || (result.is_a?(::Array) && result.empty?)
|
422
|
+
# NOTE: the lock key was changed durign an SPC logic execution
|
423
|
+
RedisQueuedLocks::Data[ok: false, result: :lock_is_acquired_during_acquire_race]
|
424
|
+
else
|
425
|
+
# NOTE: unknown behaviour :thinking:. this part is not reachable at the moment.
|
426
|
+
RedisQueuedLocks::Data[ok: false, result: :unknown]
|
427
|
+
end
|
428
|
+
when inter_result == :conflict_work_through
|
429
|
+
# Step 7.same_process_conflict.B:
|
430
|
+
# - conflict_work_through case => yield <block> without lock realesing/extending
|
431
|
+
RedisQueuedLocks::Data[ok: true, result: {
|
432
|
+
process: :conflict_work_through,
|
433
|
+
lock_key: lock_key,
|
434
|
+
acq_id: acquier_id,
|
435
|
+
ts: spc_processed_timestamp,
|
436
|
+
ttl: ttl
|
437
|
+
}]
|
438
|
+
when inter_result == :conflict_dead_lock
|
439
|
+
# Step 7.same_process_conflict.C:
|
440
|
+
# - deadlock. should fail in acquirement logic;
|
441
|
+
RedisQueuedLocks::Data[ok: false, result: inter_result]
|
219
442
|
when fail_fast && inter_result == :fail_fast_no_try
|
220
443
|
# Step 7.a: lock is still acquired and we should exit from the logic as soon as possible
|
221
444
|
RedisQueuedLocks::Data[ok: false, result: inter_result]
|
@@ -238,10 +461,13 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
238
461
|
# 3. pexpire should return 1 (expiration time is successfully applied)
|
239
462
|
|
240
463
|
# Step 7.d: locked! :) let's go! => successfully acquired
|
241
|
-
RedisQueuedLocks::Data[
|
242
|
-
|
243
|
-
|
244
|
-
|
464
|
+
RedisQueuedLocks::Data[ok: true, result: {
|
465
|
+
process: :lock_obtaining,
|
466
|
+
lock_key: lock_key,
|
467
|
+
acq_id: acquier_id,
|
468
|
+
ts: timestamp,
|
469
|
+
ttl: ttl
|
470
|
+
}]
|
245
471
|
else
|
246
472
|
# Ste 7.3: unknown behaviour :thinking:
|
247
473
|
RedisQueuedLocks::Data[ok: false, result: :unknown]
|
@@ -260,12 +486,13 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
260
486
|
#
|
261
487
|
# @api private
|
262
488
|
# @since 1.0.0
|
489
|
+
# @version 1.3.0
|
263
490
|
def dequeue_from_lock_queue(redis, logger, lock_key, lock_key_queue, queue_ttl, acquier_id)
|
264
491
|
result = redis.call('ZREM', lock_key_queue, acquier_id)
|
265
492
|
|
266
493
|
run_non_critical do
|
267
494
|
logger.debug do
|
268
|
-
"[redis_queued_locks.
|
495
|
+
"[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue] " \
|
269
496
|
"lock_key => '#{lock_key}' " \
|
270
497
|
"queue_ttl => '#{queue_ttl}' " \
|
271
498
|
"acq_id => '#{acquier_id}'"
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.3.0
|
5
|
+
module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
6
|
+
# @since 1.3.0
|
7
|
+
extend RedisQueuedLocks::Utilities
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 1.3.0
|
13
|
+
DECREASE_LOCK_PTTL = <<~LUA_SCRIPT.strip.tr("\n", '').freeze
|
14
|
+
local new_lock_pttl = redis.call("PTTL", KEYS[1]) - ARGV[1];
|
15
|
+
return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
|
16
|
+
LUA_SCRIPT
|
17
|
+
|
18
|
+
# @param redis [RedisClient] Redis connection.
|
19
|
+
# @param logger [::Logger,#debug] Logger object.
|
20
|
+
# @param lock_key [String] Obtained lock key that should be expired.
|
21
|
+
# @param acquier_id [String] Acquier identifier.
|
22
|
+
# @param timed [Boolean] Should the lock be wrapped by Timeout with with lock's ttl
|
23
|
+
# @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
|
24
|
+
# @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
|
25
|
+
# @param queue_ttl [Integer] Lock request lifetime.
|
26
|
+
# @param block [Block] Custom logic that should be invoked unter the obtained lock.
|
27
|
+
# @param should_expire [Boolean] Should the lock be expired after the block invocation.
|
28
|
+
# @param should_decrease [Boolean]
|
29
|
+
# - Should decrease the lock TTL after the lock invocation;
|
30
|
+
# - It is suitable for extendable reentrant locks;
|
31
|
+
# @return [Any,NilClass] nil is returned no block parametr is provided.
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
# @since 1.3.0
|
35
|
+
# rubocop:disable Metrics/MethodLength
|
36
|
+
def yield_expire(
|
37
|
+
redis,
|
38
|
+
logger,
|
39
|
+
lock_key,
|
40
|
+
acquier_id,
|
41
|
+
timed,
|
42
|
+
ttl_shift,
|
43
|
+
ttl,
|
44
|
+
queue_ttl,
|
45
|
+
should_expire,
|
46
|
+
should_decrease,
|
47
|
+
&block
|
48
|
+
)
|
49
|
+
initial_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
50
|
+
|
51
|
+
if block_given?
|
52
|
+
timeout = ((ttl - ttl_shift) / 1000.0).yield_self do |time|
|
53
|
+
# NOTE: time in <seconds> cuz Ruby's Timeout requires <seconds>
|
54
|
+
(time < 0) ? 0.0 : time
|
55
|
+
end
|
56
|
+
|
57
|
+
if timed && ttl != nil
|
58
|
+
yield_with_timeout(timeout, lock_key, ttl, &block)
|
59
|
+
else
|
60
|
+
yield
|
61
|
+
end
|
62
|
+
end
|
63
|
+
ensure
|
64
|
+
if should_expire
|
65
|
+
run_non_critical do
|
66
|
+
logger.debug do
|
67
|
+
"[redis_queued_locks.expire_lock] " \
|
68
|
+
"lock_key => '#{lock_key}' " \
|
69
|
+
"queue_ttl => #{queue_ttl} " \
|
70
|
+
"acq_id => '#{acquier_id}'"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
redis.call('EXPIRE', lock_key, '0')
|
74
|
+
elsif should_decrease
|
75
|
+
finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
76
|
+
spent_time = (finish_time - initial_time)
|
77
|
+
decreased_ttl = ttl - spent_time - RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
|
78
|
+
if decreased_ttl > 0
|
79
|
+
run_non_critical do
|
80
|
+
logger.debug do
|
81
|
+
"[redis_queued_locks.decrease_lock] " \
|
82
|
+
"lock_key => '#{lock_key}' " \
|
83
|
+
"decreased_ttl => '#{decreased_ttl} " \
|
84
|
+
"queue_ttl => #{queue_ttl} " \
|
85
|
+
"acq_id => '#{acquier_id}' " \
|
86
|
+
end
|
87
|
+
end
|
88
|
+
# NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
|
89
|
+
redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
|
90
|
+
# TODO: upload scripts to the redis
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
# rubocop:enable Metrics/MethodLength
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# @param timeout [Float]
|
99
|
+
# @parma lock_key [String]
|
100
|
+
# @param lock_ttl [Integer,NilClass]
|
101
|
+
# @param block [Blcok]
|
102
|
+
# @return [Any]
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
# @since 1.3.0
|
106
|
+
def yield_with_timeout(timeout, lock_key, lock_ttl, &block)
|
107
|
+
::Timeout.timeout(timeout, &block)
|
108
|
+
rescue ::Timeout::Error
|
109
|
+
raise(
|
110
|
+
RedisQueuedLocks::TimedLockTimeoutError,
|
111
|
+
"Passed <timed> block of code exceeded " \
|
112
|
+
"the lock TTL (lock: \"#{lock_key}\", ttl: #{lock_ttl})"
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|