redis_queued_locks 1.2.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 +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
|