redis_queued_locks 0.0.19 → 0.0.21
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 +9 -0
- data/README.md +12 -1
- data/lib/redis_queued_locks/acquier/{delay.rb → acquire_lock/delay_execution.rb} +2 -2
- data/lib/redis_queued_locks/acquier/{try.rb → acquire_lock/try_to_lock.rb} +1 -1
- data/lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb +29 -0
- data/lib/redis_queued_locks/acquier/{yield_expire.rb → acquire_lock/yield_with_expire.rb} +1 -1
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +270 -0
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +18 -0
- data/lib/redis_queued_locks/acquier/is_locked.rb +18 -0
- data/lib/redis_queued_locks/acquier/is_queued.rb +18 -0
- data/lib/redis_queued_locks/acquier/keys.rb +26 -0
- data/lib/redis_queued_locks/acquier/lock_info.rb +57 -0
- data/lib/redis_queued_locks/acquier/locks.rb +26 -0
- data/lib/redis_queued_locks/acquier/queue_info.rb +50 -0
- data/lib/redis_queued_locks/acquier/queues.rb +26 -0
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +85 -0
- data/lib/redis_queued_locks/acquier/release_lock.rb +73 -0
- data/lib/redis_queued_locks/acquier.rb +11 -552
- data/lib/redis_queued_locks/client.rb +25 -11
- data/lib/redis_queued_locks/utilities.rb +16 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- data/lib/redis_queued_locks.rb +1 -0
- metadata +18 -6
- data/lib/redis_queued_locks/acquier/release.rb +0 -60
@@ -2,557 +2,16 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
|
-
# rubocop:disable Metrics/ModuleLength
|
6
5
|
module RedisQueuedLocks::Acquier
|
7
|
-
require_relative 'acquier/
|
8
|
-
require_relative 'acquier/
|
9
|
-
require_relative 'acquier/
|
10
|
-
require_relative 'acquier/
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# @since 0.1.0
|
19
|
-
extend Release
|
20
|
-
|
21
|
-
# @return [Integer] Redis expiration error in milliseconds.
|
22
|
-
#
|
23
|
-
# @api private
|
24
|
-
# @since 0.1.0
|
25
|
-
REDIS_EXPIRE_ERROR = 1
|
26
|
-
|
27
|
-
# rubocop:disable Metrics/ClassLength
|
28
|
-
class << self
|
29
|
-
# @param redis [RedisClient]
|
30
|
-
# Redis connection client.
|
31
|
-
# @param lock_name [String]
|
32
|
-
# Lock name to be acquier.
|
33
|
-
# @option process_id [Integer,String]
|
34
|
-
# The process that want to acquire a lock.
|
35
|
-
# @option thread_id [Integer,String]
|
36
|
-
# The process's thread that want to acquire a lock.
|
37
|
-
# @option fiber_id [Integer,String]
|
38
|
-
# A current fiber that want to acquire a lock.
|
39
|
-
# @option ractor_id [Integer,String]
|
40
|
-
# The current ractor that want to acquire a lock.
|
41
|
-
# @option ttl [Integer,NilClass]
|
42
|
-
# Lock's time to live (in milliseconds). Nil means "without timeout".
|
43
|
-
# @option queue_ttl [Integer]
|
44
|
-
# Lifetime of the acuier's lock request. In seconds.
|
45
|
-
# @option timeout [Integer]
|
46
|
-
# Time period whe should try to acquire the lock (in seconds).
|
47
|
-
# @option timed [Boolean]
|
48
|
-
# Limit the invocation time period of the passed block of code by the lock's TTL.
|
49
|
-
# @option retry_count [Integer,NilClass]
|
50
|
-
# How many times we should try to acquire a lock. Nil means "infinite retries".
|
51
|
-
# @option retry_delay [Integer]
|
52
|
-
# A time-interval between the each retry (in milliseconds).
|
53
|
-
# @option retry_jitter [Integer]
|
54
|
-
# Time-shift range for retry-delay (in milliseconds).
|
55
|
-
# @option raise_errors [Boolean]
|
56
|
-
# Raise errors on exceptional cases.
|
57
|
-
# @option instrumenter [#notify]
|
58
|
-
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
59
|
-
# @option identity [String]
|
60
|
-
# Unique acquire identifier that is also should be unique between processes and pods
|
61
|
-
# on different machines. By default the uniq identity string is
|
62
|
-
# represented as 10 bytes hexstr.
|
63
|
-
# @option fail_fast [Boolean]
|
64
|
-
# Should the required lock to be checked before the try and exit immidetly if lock is
|
65
|
-
# already obtained.
|
66
|
-
# @param [Block]
|
67
|
-
# A block of code that should be executed after the successfully acquired lock.
|
68
|
-
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
69
|
-
# - Format: { ok: true/false, result: Any }
|
70
|
-
# - If block is given the result of block's yeld will be returned.
|
71
|
-
#
|
72
|
-
# @api private
|
73
|
-
# @since 0.1.0
|
74
|
-
# rubocop:disable Metrics/MethodLength, Metrics/BlockNesting
|
75
|
-
def acquire_lock!(
|
76
|
-
redis,
|
77
|
-
lock_name,
|
78
|
-
process_id:,
|
79
|
-
thread_id:,
|
80
|
-
fiber_id:,
|
81
|
-
ractor_id:,
|
82
|
-
ttl:,
|
83
|
-
queue_ttl:,
|
84
|
-
timeout:,
|
85
|
-
timed:,
|
86
|
-
retry_count:,
|
87
|
-
retry_delay:,
|
88
|
-
retry_jitter:,
|
89
|
-
raise_errors:,
|
90
|
-
instrumenter:,
|
91
|
-
identity:,
|
92
|
-
fail_fast:,
|
93
|
-
&block
|
94
|
-
)
|
95
|
-
# Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
|
96
|
-
acquier_id = RedisQueuedLocks::Resource.acquier_identifier(
|
97
|
-
process_id,
|
98
|
-
thread_id,
|
99
|
-
fiber_id,
|
100
|
-
ractor_id,
|
101
|
-
identity
|
102
|
-
)
|
103
|
-
# NOTE:
|
104
|
-
# - think aobut the redis expiration error
|
105
|
-
# - (ttl - REDIS_EXPIRE_ERROR).yield_self { |val| (val == 0) ? ttl : val }
|
106
|
-
lock_ttl = ttl
|
107
|
-
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
108
|
-
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
109
|
-
acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
|
110
|
-
|
111
|
-
# Step X: intermediate result observer
|
112
|
-
acq_process = {
|
113
|
-
lock_info: {},
|
114
|
-
should_try: true,
|
115
|
-
tries: 0,
|
116
|
-
acquired: false,
|
117
|
-
result: nil,
|
118
|
-
acq_time: nil, # NOTE: in milliseconds
|
119
|
-
hold_time: nil, # NOTE: in milliseconds
|
120
|
-
rel_time: nil # NOTE: in milliseconds
|
121
|
-
}
|
122
|
-
acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
|
123
|
-
|
124
|
-
# Step 2: try to lock with timeout
|
125
|
-
with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
|
126
|
-
acq_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
127
|
-
|
128
|
-
# Step 2.1: caclically try to obtain the lock
|
129
|
-
while acq_process[:should_try]
|
130
|
-
try_to_lock(
|
131
|
-
redis,
|
132
|
-
lock_key,
|
133
|
-
lock_key_queue,
|
134
|
-
acquier_id,
|
135
|
-
acquier_position,
|
136
|
-
lock_ttl,
|
137
|
-
queue_ttl,
|
138
|
-
fail_fast
|
139
|
-
) => { ok:, result: }
|
140
|
-
|
141
|
-
acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
142
|
-
acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil(2)
|
143
|
-
|
144
|
-
# Step X: save the intermediate results to the result observer
|
145
|
-
acq_process[:result] = result
|
146
|
-
acq_process[:acq_end_time] = acq_end_time
|
147
|
-
|
148
|
-
# Step 2.1: analyze an acquirement attempt
|
149
|
-
if ok
|
150
|
-
# Step X (instrumentation): lock obtained
|
151
|
-
instrumenter.notify('redis_queued_locks.lock_obtained', {
|
152
|
-
lock_key: result[:lock_key],
|
153
|
-
ttl: result[:ttl],
|
154
|
-
acq_id: result[:acq_id],
|
155
|
-
ts: result[:ts],
|
156
|
-
acq_time: acq_time
|
157
|
-
})
|
158
|
-
|
159
|
-
# Step 2.1.a: successfully acquired => build the result
|
160
|
-
acq_process[:lock_info] = {
|
161
|
-
lock_key: result[:lock_key],
|
162
|
-
acq_id: result[:acq_id],
|
163
|
-
ts: result[:ts],
|
164
|
-
ttl: result[:ttl]
|
165
|
-
}
|
166
|
-
acq_process[:acquired] = true
|
167
|
-
acq_process[:should_try] = false
|
168
|
-
acq_process[:acq_time] = acq_time
|
169
|
-
acq_process[:acq_end_time] = acq_end_time
|
170
|
-
elsif fail_fast && acq_process[:result] == :fail_fast_no_try
|
171
|
-
acq_process[:should_try] = false
|
172
|
-
if raise_errors
|
173
|
-
raise(RedisQueuedLocks::LockAlreadyObtainedError, <<~ERROR_MESSAGE.strip)
|
174
|
-
Lock "#{lock_key}" is already obtained.
|
175
|
-
ERROR_MESSAGE
|
176
|
-
end
|
177
|
-
else
|
178
|
-
# Step 2.1.b: failed acquirement => retry
|
179
|
-
acq_process[:tries] += 1
|
180
|
-
|
181
|
-
if (retry_count != nil && acq_process[:tries] >= retry_count) || fail_fast
|
182
|
-
# NOTE:
|
183
|
-
# - reached the retry limit => quit from the loop
|
184
|
-
# - should fail fast => quit from the loop
|
185
|
-
acq_process[:should_try] = false
|
186
|
-
acq_process[:result] = fail_fast ? :fail_fast_after_try : :retry_limit_reached
|
187
|
-
|
188
|
-
# NOTE:
|
189
|
-
# - reached the retry limit => dequeue from the lock queue
|
190
|
-
# - should fail fast => dequeue from the lock queue
|
191
|
-
acq_dequeue.call
|
192
|
-
|
193
|
-
# NOTE: check and raise an error
|
194
|
-
if fail_fast && raise_errors
|
195
|
-
raise(RedisQueuedLocks::LockAlreadyObtainedError, <<~ERROR_MESSAGE.strip)
|
196
|
-
Lock "#{lock_key}" is already obtained.
|
197
|
-
ERROR_MESSAGE
|
198
|
-
elsif raise_errors
|
199
|
-
raise(RedisQueuedLocks::LockAcquiermentRetryLimitError, <<~ERROR_MESSAGE.strip)
|
200
|
-
Failed to acquire the lock "#{lock_key}"
|
201
|
-
for the given retry_count limit (#{retry_count} times).
|
202
|
-
ERROR_MESSAGE
|
203
|
-
end
|
204
|
-
else
|
205
|
-
# NOTE:
|
206
|
-
# delay the exceution in order to prevent chaotic attempts
|
207
|
-
# and to allow other processes and threads to obtain the lock too.
|
208
|
-
delay_execution(retry_delay, retry_jitter)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
# Step 3: analyze acquirement result
|
215
|
-
if acq_process[:acquired]
|
216
|
-
# Step 3.a: acquired successfully => run logic or return the result of acquirement
|
217
|
-
if block_given?
|
218
|
-
begin
|
219
|
-
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
220
|
-
ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
|
221
|
-
yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
|
222
|
-
ensure
|
223
|
-
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
224
|
-
acq_process[:hold_time] = (
|
225
|
-
(acq_process[:rel_time] - acq_process[:acq_end_time]) * 1000
|
226
|
-
).ceil(2)
|
227
|
-
|
228
|
-
# Step X (instrumentation): lock_hold_and_release
|
229
|
-
run_non_critical do
|
230
|
-
instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
|
231
|
-
hold_time: acq_process[:hold_time],
|
232
|
-
ttl: acq_process[:lock_info][:ttl],
|
233
|
-
acq_id: acq_process[:lock_info][:acq_id],
|
234
|
-
ts: acq_process[:lock_info][:ts],
|
235
|
-
lock_key: acq_process[:lock_info][:lock_key],
|
236
|
-
acq_time: acq_process[:acq_time]
|
237
|
-
})
|
238
|
-
end
|
239
|
-
end
|
240
|
-
else
|
241
|
-
RedisQueuedLocks::Data[ok: true, result: acq_process[:lock_info]]
|
242
|
-
end
|
243
|
-
else
|
244
|
-
if acq_process[:result] != :retry_limit_reached &&
|
245
|
-
acq_process[:result] != :fail_fast_no_try &&
|
246
|
-
acq_process[:result] != :fail_fast_after_try
|
247
|
-
# NOTE: we have only two situations if lock is not acquired withou fast-fail flag:
|
248
|
-
# - time limit is reached
|
249
|
-
# - retry count limit is reached
|
250
|
-
# In other cases the lock obtaining time and tries count are infinite.
|
251
|
-
acq_process[:result] = :timeout_reached
|
252
|
-
end
|
253
|
-
# Step 3.b: lock is not acquired (acquier is dequeued by timeout callback)
|
254
|
-
RedisQueuedLocks::Data[ok: false, result: acq_process[:result]]
|
255
|
-
end
|
256
|
-
end
|
257
|
-
# rubocop:enable Metrics/MethodLength, Metrics/BlockNesting
|
258
|
-
|
259
|
-
# Release the concrete lock:
|
260
|
-
# - 1. clear lock queue: al; related processes released
|
261
|
-
# from the lock aquierment and should retry;
|
262
|
-
# - 2. delete the lock: drop lock key from Redis;
|
263
|
-
# It is safe because the lock obtain logic is transactional and
|
264
|
-
# watches the original lock for changes.
|
265
|
-
#
|
266
|
-
# @param redis [RedisClient]
|
267
|
-
# Redis connection client.
|
268
|
-
# @param lock_name [String]
|
269
|
-
# The lock name that should be released.
|
270
|
-
# @param isntrumenter [#notify]
|
271
|
-
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
272
|
-
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
|
273
|
-
# Format: { ok: true/false, result: Hash<Symbil,Numeric|String> }
|
274
|
-
#
|
275
|
-
# @api private
|
276
|
-
# @since 0.1.0
|
277
|
-
def release_lock!(redis, lock_name, instrumenter)
|
278
|
-
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
279
|
-
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
280
|
-
|
281
|
-
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
282
|
-
fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
|
283
|
-
time_at = Time.now.to_i
|
284
|
-
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
285
|
-
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
|
286
|
-
|
287
|
-
run_non_critical do
|
288
|
-
instrumenter.notify('redis_queued_locks.explicit_lock_release', {
|
289
|
-
lock_key: lock_key,
|
290
|
-
lock_key_queue: lock_key_queue,
|
291
|
-
rel_time: rel_time,
|
292
|
-
at: time_at
|
293
|
-
})
|
294
|
-
end
|
295
|
-
|
296
|
-
RedisQueuedLocks::Data[
|
297
|
-
ok: true,
|
298
|
-
result: { rel_time: rel_time, rel_key: lock_key, rel_queue: lock_key_queue }
|
299
|
-
]
|
300
|
-
end
|
301
|
-
|
302
|
-
# Release all locks:
|
303
|
-
# - 1. clear all lock queus: drop them all from Redis database by the lock queue pattern;
|
304
|
-
# - 2. delete all locks: drop lock keys from Redis by the lock key pattern;
|
305
|
-
#
|
306
|
-
# @param redis [RedisClient]
|
307
|
-
# Redis connection client.
|
308
|
-
# @param batch_size [Integer]
|
309
|
-
# The number of lock keys that should be released in a time.
|
310
|
-
# @param isntrumenter [#notify]
|
311
|
-
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
312
|
-
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
|
313
|
-
# Format: { ok: true/false, result: Hash<Symbol,Numeric> }
|
314
|
-
#
|
315
|
-
# @api private
|
316
|
-
# @since 0.1.0
|
317
|
-
def release_all_locks!(redis, batch_size, instrumenter)
|
318
|
-
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
319
|
-
fully_release_all_locks(redis, batch_size) => { ok:, result: }
|
320
|
-
time_at = Time.now.to_i
|
321
|
-
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
322
|
-
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
|
323
|
-
|
324
|
-
run_non_critical do
|
325
|
-
instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
|
326
|
-
at: time_at,
|
327
|
-
rel_time: rel_time,
|
328
|
-
rel_keys: result[:rel_keys]
|
329
|
-
})
|
330
|
-
end
|
331
|
-
|
332
|
-
RedisQueuedLocks::Data[
|
333
|
-
ok: true,
|
334
|
-
result: { rel_key_cnt: result[:rel_keys], rel_time: rel_time }
|
335
|
-
]
|
336
|
-
end
|
337
|
-
|
338
|
-
# @param redis_client [RedisClient]
|
339
|
-
# @param lock_name [String]
|
340
|
-
# @return [Boolean]
|
341
|
-
#
|
342
|
-
# @api private
|
343
|
-
# @since 0.1.0
|
344
|
-
def locked?(redis_client, lock_name)
|
345
|
-
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
346
|
-
redis_client.call('EXISTS', lock_key) == 1
|
347
|
-
end
|
348
|
-
|
349
|
-
# @param redis_client [RedisClient]
|
350
|
-
# @param lock_name [String]
|
351
|
-
# @return [Boolean]
|
352
|
-
#
|
353
|
-
# @api private
|
354
|
-
# @since 0.1.0
|
355
|
-
def queued?(redis_client, lock_name)
|
356
|
-
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
357
|
-
redis_client.call('EXISTS', lock_key_queue) == 1
|
358
|
-
end
|
359
|
-
|
360
|
-
# @param redis_client [RedisClient]
|
361
|
-
# @param lock_name [String]
|
362
|
-
# @return [Hash<Symbol,String|Numeric>,NilClass]
|
363
|
-
# - `nil` is returned when lock key does not exist or expired;
|
364
|
-
# - result format: {
|
365
|
-
# lock_key: "rql:lock:your_lockname", # acquired lock key
|
366
|
-
# acq_id: "rql:acq:process_id/thread_id", # lock acquier identifier
|
367
|
-
# ts: 123456789, # <locked at> time stamp (epoch)
|
368
|
-
# ini_ttl: 123456789, # initial lock key ttl (milliseconds),
|
369
|
-
# rem_ttl: 123456789, # remaining lock key ttl (milliseconds)
|
370
|
-
# }
|
371
|
-
#
|
372
|
-
# @api private
|
373
|
-
# @since 0.1.0
|
374
|
-
def lock_info(redis_client, lock_name)
|
375
|
-
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
376
|
-
|
377
|
-
result = redis_client.multi(watch: [lock_key]) do |transact|
|
378
|
-
transact.call('HGETALL', lock_key)
|
379
|
-
transact.call('PTTL', lock_key)
|
380
|
-
end
|
381
|
-
|
382
|
-
if result == nil
|
383
|
-
# NOTE:
|
384
|
-
# - nil result means that during transaction invocation the lock is changed (CAS):
|
385
|
-
# - lock is expired;
|
386
|
-
# - lock is released;
|
387
|
-
# - lock is expired + re-obtained;
|
388
|
-
nil
|
389
|
-
else
|
390
|
-
hget_cmd_res = result[0]
|
391
|
-
pttl_cmd_res = result[1]
|
392
|
-
|
393
|
-
if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
|
394
|
-
nil
|
395
|
-
else
|
396
|
-
# NOTE: the result of MULTI-command is an array of results of each internal command
|
397
|
-
# - result[0] (HGETALL) (Hash<String,String>)
|
398
|
-
# - result[1] (PTTL) (Integer)
|
399
|
-
{
|
400
|
-
lock_key: lock_key,
|
401
|
-
acq_id: hget_cmd_res['acq_id'],
|
402
|
-
ts: Integer(hget_cmd_res['ts']),
|
403
|
-
ini_ttl: Integer(hget_cmd_res['ini_ttl']),
|
404
|
-
rem_ttl: ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
|
405
|
-
}
|
406
|
-
end
|
407
|
-
end
|
408
|
-
end
|
409
|
-
|
410
|
-
# Returns an information about the required lock queue by the lock name. The result
|
411
|
-
# represnts the ordered lock request queue that is ordered by score (Redis sets) and shows
|
412
|
-
# lock acquirers and their position in queue. Async nature with redis communcation can lead
|
413
|
-
# the sitaution when the queue becomes empty during the queue data extraction. So sometimes
|
414
|
-
# you can receive the lock queue info with empty queue.
|
415
|
-
#
|
416
|
-
# @param redis_client [RedisClient]
|
417
|
-
# @param lock_name [String]
|
418
|
-
# @return [Hash<Symbol,String|Array<Hash<Symbol,String|Float>>,NilClass]
|
419
|
-
# - `nil` is returned when lock queue does not exist;
|
420
|
-
# - result format: {
|
421
|
-
# lock_queue: "rql:lock_queue:your_lock_name", # lock queue key in redis,
|
422
|
-
# queue: [
|
423
|
-
# { acq_id: "rql:acq:process_id/thread_id", score: 123 },
|
424
|
-
# { acq_id: "rql:acq:process_id/thread_id", score: 456 },
|
425
|
-
# ] # ordered set (by score) with information about an acquier and their position in queue
|
426
|
-
# }
|
427
|
-
#
|
428
|
-
# @api private
|
429
|
-
# @since 0.1.0
|
430
|
-
def queue_info(redis_client, lock_name)
|
431
|
-
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
432
|
-
|
433
|
-
result = redis_client.pipelined do |pipeline|
|
434
|
-
pipeline.call('EXISTS', lock_key_queue)
|
435
|
-
pipeline.call('ZRANGE', lock_key_queue, '0', '-1', 'WITHSCORES')
|
436
|
-
end
|
437
|
-
|
438
|
-
exists_cmd_res = result[0]
|
439
|
-
zrange_cmd_res = result[1]
|
440
|
-
|
441
|
-
if exists_cmd_res == 1
|
442
|
-
# NOTE: queue existed during the piepline invocation
|
443
|
-
{
|
444
|
-
lock_queue: lock_key_queue,
|
445
|
-
queue: zrange_cmd_res.map { |val| { acq_id: val[0], score: val[1] } }
|
446
|
-
}
|
447
|
-
else
|
448
|
-
# NOTE: queue did not exist during the pipeline invocation
|
449
|
-
nil
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
# @param redis_client [RedisClient]
|
454
|
-
# @param lock_name [String]
|
455
|
-
# @param milliseconds [Integer]
|
456
|
-
# @return [?]
|
457
|
-
#
|
458
|
-
# @api private
|
459
|
-
# @since 0.1.0
|
460
|
-
def extend_lock_ttl(redis_client, lock_name, milliseconds)
|
461
|
-
# TODO: realize
|
462
|
-
end
|
463
|
-
|
464
|
-
# @param redis_client [RedisClient]
|
465
|
-
# @option scan_size [Integer]
|
466
|
-
# @return [Set<String>]
|
467
|
-
#
|
468
|
-
# @api private
|
469
|
-
# @since 0.1.0
|
470
|
-
def locks(redis_client, scan_size:)
|
471
|
-
Set.new.tap do |lock_keys|
|
472
|
-
redis_client.scan(
|
473
|
-
'MATCH',
|
474
|
-
RedisQueuedLocks::Resource::LOCK_PATTERN,
|
475
|
-
count: scan_size
|
476
|
-
) do |lock_key|
|
477
|
-
# TODO: reduce unnecessary iterations
|
478
|
-
lock_keys.add(lock_key)
|
479
|
-
end
|
480
|
-
end
|
481
|
-
end
|
482
|
-
|
483
|
-
# @param redis_client [RedisClient]
|
484
|
-
# @param scan_size [Integer]
|
485
|
-
# @return [Set<String>]
|
486
|
-
#
|
487
|
-
# @api private
|
488
|
-
# @since 0.1.0
|
489
|
-
def queues(redis_client, scan_size:)
|
490
|
-
Set.new.tap do |lock_queues|
|
491
|
-
redis_client.scan(
|
492
|
-
'MATCH',
|
493
|
-
RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
|
494
|
-
count: scan_size
|
495
|
-
) do |lock_queue|
|
496
|
-
# TODO: reduce unnecessary iterations
|
497
|
-
lock_queues.add(lock_queue)
|
498
|
-
end
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
# @param redis_client [RedisClient]
|
503
|
-
# @option scan_size [Integer]
|
504
|
-
# @return [Array<String>]
|
505
|
-
#
|
506
|
-
# @api private
|
507
|
-
# @since 0.1.0
|
508
|
-
def keys(redis_client, scan_size:)
|
509
|
-
Set.new.tap do |keys|
|
510
|
-
redis_client.scan(
|
511
|
-
'MATCH',
|
512
|
-
RedisQueuedLocks::Resource::KEY_PATTERN,
|
513
|
-
count: scan_size
|
514
|
-
) do |key|
|
515
|
-
# TODO: reduce unnecessary iterations
|
516
|
-
keys.add(key)
|
517
|
-
end
|
518
|
-
end
|
519
|
-
end
|
520
|
-
|
521
|
-
private
|
522
|
-
|
523
|
-
# @param timeout [NilClass,Integer]
|
524
|
-
# Time period after which the logic will fail with timeout error.
|
525
|
-
# @param lock_key [String]
|
526
|
-
# Lock name.
|
527
|
-
# @param raise_errors [Boolean]
|
528
|
-
# Raise erros on exceptional cases.
|
529
|
-
# @option on_timeout [Proc,NilClass]
|
530
|
-
# Callback invoked on Timeout::Error.
|
531
|
-
# @return [Any]
|
532
|
-
#
|
533
|
-
# @api private
|
534
|
-
# @since 0.1.0
|
535
|
-
def with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: nil, &block)
|
536
|
-
::Timeout.timeout(timeout, &block)
|
537
|
-
rescue ::Timeout::Error
|
538
|
-
on_timeout.call unless on_timeout == nil
|
539
|
-
|
540
|
-
if raise_errors
|
541
|
-
raise(RedisQueuedLocks::LockAcquiermentTimeoutError, <<~ERROR_MESSAGE.strip)
|
542
|
-
Failed to acquire the lock "#{lock_key}" for the given timeout (#{timeout} seconds).
|
543
|
-
ERROR_MESSAGE
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
# @param block [Block]
|
548
|
-
# @return [Any]
|
549
|
-
#
|
550
|
-
# @api private
|
551
|
-
# @since 0.1.0
|
552
|
-
def run_non_critical(&block)
|
553
|
-
yield rescue nil
|
554
|
-
end
|
555
|
-
end
|
556
|
-
# rubocop:enable Metrics/ClassLength
|
6
|
+
require_relative 'acquier/acquire_lock'
|
7
|
+
require_relative 'acquier/release_lock'
|
8
|
+
require_relative 'acquier/release_all_locks'
|
9
|
+
require_relative 'acquier/is_locked'
|
10
|
+
require_relative 'acquier/is_queued'
|
11
|
+
require_relative 'acquier/lock_info'
|
12
|
+
require_relative 'acquier/queue_info'
|
13
|
+
require_relative 'acquier/locks'
|
14
|
+
require_relative 'acquier/queues'
|
15
|
+
require_relative 'acquier/keys'
|
16
|
+
require_relative 'acquier/extend_lock_ttl'
|
557
17
|
end
|
558
|
-
# rubocop:enable Metrics/ModuleLength
|
@@ -92,6 +92,8 @@ class RedisQueuedLocks::Client
|
|
92
92
|
# already obtained;
|
93
93
|
# - Should the logic exit immidietly after the first try if the lock was obtained
|
94
94
|
# by another process while the lock request queue was initially empty;
|
95
|
+
# @option metadata [NilClass,Any]
|
96
|
+
# - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
|
95
97
|
# @param block [Block]
|
96
98
|
# A block of code that should be executed after the successfully acquired lock.
|
97
99
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -112,9 +114,10 @@ class RedisQueuedLocks::Client
|
|
112
114
|
raise_errors: false,
|
113
115
|
fail_fast: false,
|
114
116
|
identity: uniq_identity,
|
117
|
+
metadata: nil,
|
115
118
|
&block
|
116
119
|
)
|
117
|
-
RedisQueuedLocks::Acquier.acquire_lock
|
120
|
+
RedisQueuedLocks::Acquier::AcquireLock.acquire_lock(
|
118
121
|
redis_client,
|
119
122
|
lock_name,
|
120
123
|
process_id: RedisQueuedLocks::Resource.get_process_id,
|
@@ -132,6 +135,7 @@ class RedisQueuedLocks::Client
|
|
132
135
|
instrumenter: config[:instrumenter],
|
133
136
|
identity:,
|
134
137
|
fail_fast:,
|
138
|
+
metadata:,
|
135
139
|
&block
|
136
140
|
)
|
137
141
|
end
|
@@ -151,6 +155,7 @@ class RedisQueuedLocks::Client
|
|
151
155
|
retry_jitter: config[:retry_jitter],
|
152
156
|
fail_fast: false,
|
153
157
|
identity: uniq_identity,
|
158
|
+
metadata: nil,
|
154
159
|
&block
|
155
160
|
)
|
156
161
|
lock(
|
@@ -165,6 +170,7 @@ class RedisQueuedLocks::Client
|
|
165
170
|
raise_errors: true,
|
166
171
|
identity:,
|
167
172
|
fail_fast:,
|
173
|
+
metadata:,
|
168
174
|
&block
|
169
175
|
)
|
170
176
|
end
|
@@ -177,7 +183,11 @@ class RedisQueuedLocks::Client
|
|
177
183
|
# @api public
|
178
184
|
# @since 0.1.0
|
179
185
|
def unlock(lock_name)
|
180
|
-
RedisQueuedLocks::Acquier.release_lock
|
186
|
+
RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
|
187
|
+
redis_client,
|
188
|
+
lock_name,
|
189
|
+
config[:instrumenter]
|
190
|
+
)
|
181
191
|
end
|
182
192
|
|
183
193
|
# @param lock_name [String]
|
@@ -186,7 +196,7 @@ class RedisQueuedLocks::Client
|
|
186
196
|
# @api public
|
187
197
|
# @since 0.1.0
|
188
198
|
def locked?(lock_name)
|
189
|
-
RedisQueuedLocks::Acquier.locked?(redis_client, lock_name)
|
199
|
+
RedisQueuedLocks::Acquier::IsLocked.locked?(redis_client, lock_name)
|
190
200
|
end
|
191
201
|
|
192
202
|
# @param lock_name [String]
|
@@ -195,7 +205,7 @@ class RedisQueuedLocks::Client
|
|
195
205
|
# @api public
|
196
206
|
# @since 0.1.0
|
197
207
|
def queued?(lock_name)
|
198
|
-
RedisQueuedLocks::Acquier.queued?(redis_client, lock_name)
|
208
|
+
RedisQueuedLocks::Acquier::IsQueued.queued?(redis_client, lock_name)
|
199
209
|
end
|
200
210
|
|
201
211
|
# @param lock_name [String]
|
@@ -204,7 +214,7 @@ class RedisQueuedLocks::Client
|
|
204
214
|
# @api public
|
205
215
|
# @since 0.1.0
|
206
216
|
def lock_info(lock_name)
|
207
|
-
RedisQueuedLocks::Acquier.lock_info(redis_client, lock_name)
|
217
|
+
RedisQueuedLocks::Acquier::LockInfo.lock_info(redis_client, lock_name)
|
208
218
|
end
|
209
219
|
|
210
220
|
# @param lock_name [String]
|
@@ -213,7 +223,7 @@ class RedisQueuedLocks::Client
|
|
213
223
|
# @api public
|
214
224
|
# @since 0.1.0
|
215
225
|
def queue_info(lock_name)
|
216
|
-
RedisQueuedLocks::Acquier.queue_info(redis_client, lock_name)
|
226
|
+
RedisQueuedLocks::Acquier::QueueInfo.queue_info(redis_client, lock_name)
|
217
227
|
end
|
218
228
|
|
219
229
|
# @param lock_name [String]
|
@@ -223,7 +233,7 @@ class RedisQueuedLocks::Client
|
|
223
233
|
# @api public
|
224
234
|
# @since 0.1.0
|
225
235
|
def extend_lock_ttl(lock_name, milliseconds)
|
226
|
-
RedisQueuedLocks::Acquier.extend_lock_ttl(redis_client, lock_name)
|
236
|
+
RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(redis_client, lock_name)
|
227
237
|
end
|
228
238
|
|
229
239
|
# @option batch_size [Integer]
|
@@ -233,7 +243,11 @@ class RedisQueuedLocks::Client
|
|
233
243
|
# @api public
|
234
244
|
# @since 0.1.0
|
235
245
|
def clear_locks(batch_size: config[:lock_release_batch_size])
|
236
|
-
RedisQueuedLocks::Acquier.release_all_locks
|
246
|
+
RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
|
247
|
+
redis_client,
|
248
|
+
batch_size,
|
249
|
+
config[:instrumenter]
|
250
|
+
)
|
237
251
|
end
|
238
252
|
|
239
253
|
# @option scan_size [Integer]
|
@@ -242,7 +256,7 @@ class RedisQueuedLocks::Client
|
|
242
256
|
# @api public
|
243
257
|
# @since 0.1.0
|
244
258
|
def locks(scan_size: config[:key_extraction_batch_size])
|
245
|
-
RedisQueuedLocks::Acquier.locks(redis_client, scan_size:)
|
259
|
+
RedisQueuedLocks::Acquier::Locks.locks(redis_client, scan_size:)
|
246
260
|
end
|
247
261
|
|
248
262
|
# @option scan_size [Integer]
|
@@ -251,7 +265,7 @@ class RedisQueuedLocks::Client
|
|
251
265
|
# @api public
|
252
266
|
# @since 0.1.0
|
253
267
|
def queues(scan_size: config[:key_extraction_batch_size])
|
254
|
-
RedisQueuedLocks::Acquier.queues(redis_client, scan_size:)
|
268
|
+
RedisQueuedLocks::Acquier::Queues.queues(redis_client, scan_size:)
|
255
269
|
end
|
256
270
|
|
257
271
|
# @option scan_size [Integer]
|
@@ -260,7 +274,7 @@ class RedisQueuedLocks::Client
|
|
260
274
|
# @api public
|
261
275
|
# @since 0.1.0
|
262
276
|
def keys(scan_size: config[:key_extraction_batch_size])
|
263
|
-
RedisQueuedLocks::Acquier.keys(redis_client, scan_size:)
|
277
|
+
RedisQueuedLocks::Acquier::Keys.keys(redis_client, scan_size:)
|
264
278
|
end
|
265
279
|
end
|
266
280
|
# rubocop:enable Metrics/ClassLength
|