redis_queued_locks 0.0.38 → 0.0.40
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +32 -0
- data/README.md +393 -78
- data/Rakefile +14 -5
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +35 -73
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +2 -2
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +25 -15
- data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +52 -0
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +21 -2
- data/lib/redis_queued_locks/acquier/lock_info.rb +3 -3
- data/lib/redis_queued_locks/acquier/locks.rb +7 -5
- data/lib/redis_queued_locks/acquier/queues.rb +4 -2
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +14 -13
- data/lib/redis_queued_locks/acquier/release_lock.rb +25 -6
- data/lib/redis_queued_locks/acquier.rb +1 -0
- data/lib/redis_queued_locks/client.rb +96 -17
- data/lib/redis_queued_locks/data.rb +0 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +4 -3
@@ -23,8 +23,8 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
23
23
|
# @param logger [::Logger,#debug]
|
24
24
|
# - Logger object used from `configuration` layer (see config[:logger]);
|
25
25
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
26
|
-
# @return [RedisQueuedLocks::Data,Hash<Symbol,
|
27
|
-
# Format: { ok: true/false, result: Hash<
|
26
|
+
# @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean<Hash<Symbol,Numeric|String|Symbol>>]
|
27
|
+
# Format: { ok: true/false, result: Hash<Symbol,Numeric|String|Symbol> }
|
28
28
|
#
|
29
29
|
# @api private
|
30
30
|
# @since 0.1.0
|
@@ -34,7 +34,7 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
34
34
|
|
35
35
|
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
36
36
|
fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
|
37
|
-
time_at = Time.now.
|
37
|
+
time_at = Time.now.to_f
|
38
38
|
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
39
39
|
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
|
40
40
|
|
@@ -49,7 +49,13 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
49
49
|
|
50
50
|
RedisQueuedLocks::Data[
|
51
51
|
ok: true,
|
52
|
-
result: {
|
52
|
+
result: {
|
53
|
+
rel_time: rel_time,
|
54
|
+
rel_key: lock_key,
|
55
|
+
rel_queue: lock_key_queue,
|
56
|
+
queue_res: result[:queue],
|
57
|
+
lock_res: result[:lock]
|
58
|
+
}
|
53
59
|
]
|
54
60
|
end
|
55
61
|
|
@@ -60,7 +66,14 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
60
66
|
# @param redis [RedisClient]
|
61
67
|
# @param lock_key [String]
|
62
68
|
# @param lock_key_queue [String]
|
63
|
-
# @return [RedisQueuedLocks::Data,Hash<Symbol,
|
69
|
+
# @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Symbol>>]
|
70
|
+
# Format: {
|
71
|
+
# ok: true/false,
|
72
|
+
# result: {
|
73
|
+
# queue: :released/:nothing_to_release,
|
74
|
+
# lock: :released/:nothing_to_release
|
75
|
+
# }
|
76
|
+
# }
|
64
77
|
#
|
65
78
|
# @api private
|
66
79
|
# @since 0.1.0
|
@@ -72,7 +85,13 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
72
85
|
end
|
73
86
|
end
|
74
87
|
|
75
|
-
RedisQueuedLocks::Data[
|
88
|
+
RedisQueuedLocks::Data[
|
89
|
+
ok: true,
|
90
|
+
result: {
|
91
|
+
queue: (result[0] != 0) ? :released : :nothing_to_release,
|
92
|
+
lock: (result[1] != 0) ? :released : :nothing_to_release
|
93
|
+
}
|
94
|
+
]
|
76
95
|
end
|
77
96
|
end
|
78
97
|
end
|
@@ -20,6 +20,8 @@ class RedisQueuedLocks::Client
|
|
20
20
|
setting :uniq_identifier, -> { RedisQueuedLocks::Resource.calc_uniq_identity }
|
21
21
|
setting :logger, RedisQueuedLocks::Logging::VoidLogger
|
22
22
|
setting :log_lock_try, false
|
23
|
+
setting :dead_request_ttl, (1 * 24 * 60 * 60 * 1000) # NOTE: 1 day in milliseconds
|
24
|
+
setting :is_timed_by_default, false
|
23
25
|
|
24
26
|
validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
|
25
27
|
validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
|
@@ -32,6 +34,8 @@ class RedisQueuedLocks::Client
|
|
32
34
|
validate('uniq_identifier', :proc)
|
33
35
|
validate('logger') { |val| RedisQueuedLocks::Logging.valid_interface?(val) }
|
34
36
|
validate('log_lock_try', :boolean)
|
37
|
+
validate('dead_request_ttl') { |val| val.is_a?(::Integer) && val > 0 }
|
38
|
+
validate('is_timed_by_default', :boolean)
|
35
39
|
end
|
36
40
|
|
37
41
|
# @return [RedisClient]
|
@@ -40,15 +44,14 @@ class RedisQueuedLocks::Client
|
|
40
44
|
# @since 0.1.0
|
41
45
|
attr_reader :redis_client
|
42
46
|
|
47
|
+
# NOTE: attr_access here is chosen intentionally in order to have an ability to change
|
48
|
+
# uniq_identity value for debug purposes in runtime;
|
43
49
|
# @return [String]
|
44
50
|
#
|
45
51
|
# @api private
|
46
52
|
# @since 0.1.0
|
47
53
|
attr_accessor :uniq_identity
|
48
54
|
|
49
|
-
# NOTE: attr_access is chosen intentionally in order to have an ability to change
|
50
|
-
# uniq_identity values for debug purposes in runtime;
|
51
|
-
|
52
55
|
# @param redis_client [RedisClient]
|
53
56
|
# Redis connection manager, which will be used for the lock acquierment and distribution.
|
54
57
|
# It should be an instance of RedisClient.
|
@@ -119,7 +122,7 @@ class RedisQueuedLocks::Client
|
|
119
122
|
ttl: config[:default_lock_ttl],
|
120
123
|
queue_ttl: config[:default_queue_ttl],
|
121
124
|
timeout: config[:try_to_lock_timeout],
|
122
|
-
timed:
|
125
|
+
timed: config[:is_timed_by_default],
|
123
126
|
retry_count: config[:retry_count],
|
124
127
|
retry_delay: config[:retry_delay],
|
125
128
|
retry_jitter: config[:retry_jitter],
|
@@ -167,7 +170,7 @@ class RedisQueuedLocks::Client
|
|
167
170
|
ttl: config[:default_lock_ttl],
|
168
171
|
queue_ttl: config[:default_queue_ttl],
|
169
172
|
timeout: config[:try_to_lock_timeout],
|
170
|
-
timed:
|
173
|
+
timed: config[:is_timed_by_default],
|
171
174
|
retry_count: config[:retry_count],
|
172
175
|
retry_delay: config[:retry_delay],
|
173
176
|
retry_jitter: config[:retry_jitter],
|
@@ -191,20 +194,38 @@ class RedisQueuedLocks::Client
|
|
191
194
|
raise_errors: true,
|
192
195
|
identity:,
|
193
196
|
fail_fast:,
|
197
|
+
logger:,
|
198
|
+
log_lock_try:,
|
194
199
|
meta:,
|
195
200
|
instrument:,
|
196
201
|
&block
|
197
202
|
)
|
198
203
|
end
|
199
204
|
|
200
|
-
# @param lock_name [String]
|
201
|
-
#
|
205
|
+
# @param lock_name [String] The lock name that should be released.
|
206
|
+
# @option logger [::Logger,#debug]
|
207
|
+
# @option instrumenter [#notify]
|
208
|
+
# @option instrument [NilClass,Any]
|
202
209
|
# @return [RedisQueuedLocks::Data, Hash<Symbol,Any>]
|
203
|
-
# Format: {
|
210
|
+
# Format: {
|
211
|
+
# ok: true/false,
|
212
|
+
# result: {
|
213
|
+
# rel_time: Integer, # <millisecnds>
|
214
|
+
# rel_key: String, # lock key
|
215
|
+
# rel_queue: String, # lock queue
|
216
|
+
# queue_res: Symbol, # :released or :nothing_to_release
|
217
|
+
# lock_res: Symbol # :released or :nothing_to_release
|
218
|
+
# }
|
219
|
+
# }
|
204
220
|
#
|
205
221
|
# @api public
|
206
222
|
# @since 0.1.0
|
207
|
-
def unlock(
|
223
|
+
def unlock(
|
224
|
+
lock_name,
|
225
|
+
logger: config[:logger],
|
226
|
+
instrumenter: config[:instrumenter],
|
227
|
+
instrument: nil
|
228
|
+
)
|
208
229
|
RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
|
209
230
|
redis_client,
|
210
231
|
lock_name,
|
@@ -249,33 +270,60 @@ class RedisQueuedLocks::Client
|
|
249
270
|
RedisQueuedLocks::Acquier::QueueInfo.queue_info(redis_client, lock_name)
|
250
271
|
end
|
251
272
|
|
273
|
+
# This method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension.
|
274
|
+
# So the methid is spliited into the two commands:
|
275
|
+
# (1) read current pttl
|
276
|
+
# (2) set new ttl that is calculated as "current pttl + additional milliseconds"
|
277
|
+
# What can happen during these steps
|
278
|
+
# - lock is expired between commands or before the first command;
|
279
|
+
# - lock is expired before the second command;
|
280
|
+
# - lock is expired AND newly acquired by another process (so you will extend the
|
281
|
+
# totally new lock with fresh PTTL);
|
282
|
+
# Use it at your own risk and consider async nature when calling this method.
|
283
|
+
#
|
252
284
|
# @param lock_name [String]
|
253
285
|
# @param milliseconds [Integer] How many milliseconds should be added.
|
254
|
-
# @return [
|
286
|
+
# @return [Hash<Symbol,Boolean|Symbol>]
|
287
|
+
# - { ok: true, result: :ttl_extended }
|
288
|
+
# - { ok: false, result: :async_expire_or_no_lock }
|
255
289
|
#
|
256
290
|
# @api public
|
257
291
|
# @since 0.1.0
|
258
|
-
def extend_lock_ttl(lock_name, milliseconds)
|
292
|
+
def extend_lock_ttl(lock_name, milliseconds, logger: config[:logger])
|
259
293
|
RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
|
260
294
|
redis_client,
|
261
295
|
lock_name,
|
262
296
|
milliseconds,
|
263
|
-
|
297
|
+
logger
|
264
298
|
)
|
265
299
|
end
|
266
300
|
|
301
|
+
# Releases all queues and locks.
|
302
|
+
# Returns:
|
303
|
+
# - :rel_time - (milliseconds) - time spent to release all locks and queues;
|
304
|
+
# - :rel_key_cnt - (integer) - the number of released redis keys (queus+locks);
|
305
|
+
#
|
267
306
|
# @option batch_size [Integer]
|
268
|
-
# @
|
269
|
-
#
|
307
|
+
# @option logger [::Logger,#debug]
|
308
|
+
# @option instrumenter [#notify]
|
309
|
+
# @option instrument [NilClass,Any]
|
310
|
+
# @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Numeric>>]
|
311
|
+
# Example: { ok: true, result { rel_key_cnt: 100, rel_time: 0.01 } }
|
270
312
|
#
|
271
313
|
# @api public
|
272
314
|
# @since 0.1.0
|
273
|
-
def clear_locks(
|
315
|
+
def clear_locks(
|
316
|
+
batch_size: config[:lock_release_batch_size],
|
317
|
+
logger: config[:logger],
|
318
|
+
instrumenter: config[:instrumenter],
|
319
|
+
instrument: nil
|
320
|
+
)
|
274
321
|
RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
|
275
322
|
redis_client,
|
276
323
|
batch_size,
|
277
|
-
|
278
|
-
|
324
|
+
logger,
|
325
|
+
instrumenter,
|
326
|
+
instrument
|
279
327
|
)
|
280
328
|
end
|
281
329
|
|
@@ -352,5 +400,36 @@ class RedisQueuedLocks::Client
|
|
352
400
|
def keys(scan_size: config[:key_extraction_batch_size])
|
353
401
|
RedisQueuedLocks::Acquier::Keys.keys(redis_client, scan_size:)
|
354
402
|
end
|
403
|
+
|
404
|
+
# @option dead_ttl [Integer]
|
405
|
+
# - the time period (in millsiecnds) after whcih the lock request is
|
406
|
+
# considered as dead;
|
407
|
+
# - `config[:dead_request_ttl]` is used by default;
|
408
|
+
# @option scan_size [Integer]
|
409
|
+
# The batch of scanned keys for Redis'es SCAN command.
|
410
|
+
# @option logger [::Logger,#debug]
|
411
|
+
# @option instrumenter [#notify]
|
412
|
+
# @option instrument [NilClass,Any]
|
413
|
+
# @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
|
414
|
+
# Format: { ok: true, result: { processed_queus: Set<String> } }
|
415
|
+
#
|
416
|
+
# @api public
|
417
|
+
# @since 0.1.0
|
418
|
+
def clear_dead_requests(
|
419
|
+
dead_ttl: config[:dead_request_ttl],
|
420
|
+
scan_size: config[:key_extraction_batch_size],
|
421
|
+
logger: config[:logger],
|
422
|
+
instrumenter: config[:instrumenter],
|
423
|
+
instrument: nil
|
424
|
+
)
|
425
|
+
RedisQueuedLocks::Acquier::ClearDeadRequests.clear_dead_requests(
|
426
|
+
redis_client,
|
427
|
+
scan_size,
|
428
|
+
dead_ttl,
|
429
|
+
logger,
|
430
|
+
instrumenter,
|
431
|
+
instrument
|
432
|
+
)
|
433
|
+
end
|
355
434
|
end
|
356
435
|
# rubocop:enable Metrics/ClassLength
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_queued_locks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.40
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rustam Ibragimov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -61,6 +61,7 @@ files:
|
|
61
61
|
- lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb
|
62
62
|
- lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb
|
63
63
|
- lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb
|
64
|
+
- lib/redis_queued_locks/acquier/clear_dead_requests.rb
|
64
65
|
- lib/redis_queued_locks/acquier/extend_lock_ttl.rb
|
65
66
|
- lib/redis_queued_locks/acquier/is_locked.rb
|
66
67
|
- lib/redis_queued_locks/acquier/is_queued.rb
|
@@ -107,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
108
|
- !ruby/object:Gem::Version
|
108
109
|
version: '0'
|
109
110
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
111
|
+
rubygems_version: 3.3.7
|
111
112
|
signing_key:
|
112
113
|
specification_version: 4
|
113
114
|
summary: Queued distributed locks based on Redis.
|