redis_queued_locks 0.0.30 → 0.0.33
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +73 -35
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +10 -5
- data/lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb +5 -3
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +5 -3
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +51 -16
- data/lib/redis_queued_locks/acquier/lock_info.rb +12 -13
- data/lib/redis_queued_locks/acquier/queue_info.rb +6 -6
- data/lib/redis_queued_locks/client.rb +16 -8
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43854a2842911dcf40e95329b2631bc6c76cdaff0cd204fb57eff55c0ee461aa
|
4
|
+
data.tar.gz: c361d55a71ac4c71d307fca4a7182069d055e302ec7b65274bcbc1a4ede6f768
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6887f12891fce49f878427b501980695872e304c1429331d23b251d2f66f2db1f07a0994952f0b4ad6d9ec9b81ea769eaefe09ab1bef0c1abcadc9770ef38344
|
7
|
+
data.tar.gz: aa2f6d2cfbe72dd6bf5c637f5538ce1df7c8415579505f765b3016a4c243abc52cd1db246f51abba7efa51abef123d577e26bfee992a5484208cb1416ca00f50
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.33] - 2024-03-26
|
4
|
+
### Added
|
5
|
+
- Logging: added current lock data info to the detailed `#try_to_lock` log to the cases when lock is still obtained. It is suitable
|
6
|
+
when you pass a custom metadata with lock obtainer (for example: the current string of code) and want to see this information
|
7
|
+
in logs when you can not acquire the concrete lock long time;
|
8
|
+
|
9
|
+
## [0.0.32] - 2024-03-26
|
10
|
+
### Added
|
11
|
+
- Support for custom metadata that merged to the lock data. This data also returned from `RedisQueudLocks::Client#lock_info` method;
|
12
|
+
- Custom metadata shou;d be represented as a `key => value` `Hash` data (or `NilClass` instead);
|
13
|
+
- Custom metadata values is returned as raw data from Redis (commonly as strings);
|
14
|
+
- Custom metadata can not contain reserved lock data keys;
|
15
|
+
- Reduced some memory consuption;
|
16
|
+
### Changed
|
17
|
+
- `RedisQueuedLocks::Client#lock_info` - has keys is changed from `Symbol` type to `String` type;
|
18
|
+
- `RedisQueuedLocks::Client#queue_info` - hash keys is changed from `Symbol` type to `String` type;
|
19
|
+
|
20
|
+
## [0.0.31] - 2024-03-25
|
21
|
+
### Changed
|
22
|
+
- `:metadata` renamed to `:instrument` in order to reflect it's domain area;
|
23
|
+
- `:metadata` is renamed to `:meta` and reserved for the future updates;
|
24
|
+
|
3
25
|
## [0.0.30] - 2024-03-23
|
4
26
|
### Fixed
|
5
27
|
- Re-enqueue problem: fixed a problem when the expired lock requests were infinitly re-added to the lock queue
|
data/README.md
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
Distributed locks with "lock acquisition queue" capabilities based on the Redis Database.
|
4
4
|
|
5
|
-
|
5
|
+
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttls, queue ttls, fast failing, etc), logging and instrumentation.
|
6
|
+
|
7
|
+
Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees the request queue will never be stacked.
|
6
8
|
|
7
9
|
---
|
8
10
|
|
@@ -14,7 +16,7 @@ Each lock request is put into the request queue and processed in order of their
|
|
14
16
|
- [Configuration](#configuration)
|
15
17
|
- [Usage](#usage)
|
16
18
|
- [lock](#lock---obtain-a-lock)
|
17
|
-
- [lock!](#lock---
|
19
|
+
- [lock!](#lock---exceptional-lock-obtaining)
|
18
20
|
- [lock_info](#lock_info)
|
19
21
|
- [queue_info](#queue_info)
|
20
22
|
- [locked?](#locked)
|
@@ -36,7 +38,7 @@ Each lock request is put into the request queue and processed in order of their
|
|
36
38
|
|
37
39
|
### Algorithm
|
38
40
|
|
39
|
-
> Each lock request is put into the request queue and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
|
41
|
+
> Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
|
40
42
|
|
41
43
|
**Soon**: detailed explanation.
|
42
44
|
|
@@ -138,18 +140,27 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
138
140
|
# (default: RedisQueuedLocks::Logging::VoidLogger)
|
139
141
|
# - the logger object;
|
140
142
|
# - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
|
141
|
-
# - at this moment the only debug logs are realised in
|
142
|
-
# -
|
143
|
-
# -
|
144
|
-
# -
|
143
|
+
# - at this moment the only debug logs are realised in following cases:
|
144
|
+
# - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id");
|
145
|
+
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id");
|
146
|
+
# - "[redis_queued_locks.dead_score_reached__reset_acquier_position]" (logs "lock_key", "queue_ttl", "acq_id");
|
147
|
+
# - "[redis_queued_locks.lock_obtained]" (logs "lockkey", "queue_ttl", "acq_id", "acq_time");
|
145
148
|
# - by default uses VoidLogger that does nothing;
|
146
149
|
config.logger = RedisQueuedLocks::Logging::VoidLogger
|
147
150
|
|
148
151
|
# (default: false)
|
149
|
-
# -
|
150
|
-
# -
|
151
|
-
#
|
152
|
-
# -
|
152
|
+
# - adds additional debug logs;
|
153
|
+
# - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
|
154
|
+
# - it adds following logs in addition to the existing:
|
155
|
+
# - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id");
|
156
|
+
# - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id");
|
157
|
+
# - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id)";
|
158
|
+
# - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id");
|
159
|
+
# - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue");
|
160
|
+
# - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id");
|
161
|
+
# - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "<current_lock_data>");
|
162
|
+
# - "[redis_queued_locks.try_lock.exit__still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
|
163
|
+
# - "[redis_queued_locks.try_lock.run__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id");
|
153
164
|
# - if logger is not configured this option does not lead to any effect;
|
154
165
|
config.log_lock_try = false
|
155
166
|
end
|
@@ -194,7 +205,8 @@ def lock(
|
|
194
205
|
raise_errors: false,
|
195
206
|
fail_fast: false,
|
196
207
|
identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
|
197
|
-
|
208
|
+
meta: nil,
|
209
|
+
instrument: nil,
|
198
210
|
logger: config[:logger],
|
199
211
|
log_lock_try: config[:log_lock_try],
|
200
212
|
&block
|
@@ -232,8 +244,11 @@ def lock(
|
|
232
244
|
pods or/and nodes of your application;
|
233
245
|
- It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
|
234
246
|
ivar (accessed via `uniq_dentity` accessor method);
|
235
|
-
- `
|
236
|
-
- A custom metadata wich will be passed to the
|
247
|
+
- `meta` - `[NilClass,Hash<String|Symbol,Any>]`
|
248
|
+
- A custom metadata wich will be passed to the lock data in addition to the existing data;
|
249
|
+
- Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`);
|
250
|
+
- `instrument` - `[NilClass,Any]`
|
251
|
+
- Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
237
252
|
- `logger` - `[::Logger,#debug]`
|
238
253
|
- Logger object used from the configuration layer (see config[:logger]);
|
239
254
|
- See `RedisQueuedLocks::Logging::VoidLogger` for example;
|
@@ -293,7 +308,8 @@ def lock!(
|
|
293
308
|
retry_jitter: config[:retry_jitter],
|
294
309
|
identity: uniq_identity,
|
295
310
|
fail_fast: false,
|
296
|
-
|
311
|
+
meta: nil,
|
312
|
+
instrument: nil,
|
297
313
|
logger: config[:logger],
|
298
314
|
log_lock_try: config[:log_lock_try],
|
299
315
|
&block
|
@@ -309,22 +325,42 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
|
|
309
325
|
- get the lock information;
|
310
326
|
- returns `nil` if lock does not exist;
|
311
327
|
- lock data (`Hash<Symbol,String|Integer>`):
|
312
|
-
- `lock_key` - `string` - lock key in redis;
|
313
|
-
- `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity);
|
314
|
-
- `ts` - `integer`/`epoch` - the time lock was obtained;
|
315
|
-
- `init_ttl` - `integer` - (milliseconds) initial lock key ttl;
|
316
|
-
- `rem_ttl` - `integer` - (milliseconds) remaining lock key ttl;
|
328
|
+
- `"lock_key"` - `string` - lock key in redis;
|
329
|
+
- `"acq_id"` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity);
|
330
|
+
- `"ts"` - `integer`/`epoch` - the time lock was obtained;
|
331
|
+
- `"init_ttl"` - `integer` - (milliseconds) initial lock key ttl;
|
332
|
+
- `"rem_ttl"` - `integer` - (milliseconds) remaining lock key ttl;
|
333
|
+
- custom metadata keys - `String` - custom metadata passed to the `lock`/`lock!`
|
334
|
+
methods via `meta:` keyword argument (see [lock]((#lock---obtain-a-lock)) method documentation);
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
# without custom metadata
|
338
|
+
rql.lock_info("your_lock_name")
|
339
|
+
|
340
|
+
# =>
|
341
|
+
{
|
342
|
+
"lock_key" => "rql:lock:your_lock_name",
|
343
|
+
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
344
|
+
"ts" => 123456789,
|
345
|
+
"ini_ttl" => 123456789,
|
346
|
+
"rem_ttl" => 123456789
|
347
|
+
}
|
348
|
+
```
|
317
349
|
|
318
350
|
```ruby
|
351
|
+
# with custom metadata
|
352
|
+
rql.lock("your_lock_name", meta: { "kek" => "pek", "bum" => 123 })
|
319
353
|
rql.lock_info("your_lock_name")
|
320
354
|
|
321
355
|
# =>
|
322
356
|
{
|
323
|
-
lock_key
|
324
|
-
acq_id
|
325
|
-
ts
|
326
|
-
ini_ttl
|
327
|
-
rem_ttl
|
357
|
+
"lock_key" => "rql:lock:your_lock_name",
|
358
|
+
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
359
|
+
"ts" => 123456789,
|
360
|
+
"ini_ttl" => 123456789,
|
361
|
+
"rem_ttl" => 123456789,
|
362
|
+
"kek" => "pek",
|
363
|
+
"bum" => "123" # NOTE: returned as a raw string directly from Redis
|
328
364
|
}
|
329
365
|
```
|
330
366
|
|
@@ -339,10 +375,10 @@ rql.lock_info("your_lock_name")
|
|
339
375
|
- represents the acquier identifier and their score as an array of hashes;
|
340
376
|
- returns `nil` if lock queue does not exist;
|
341
377
|
- lock queue data (`Hash<Symbol,String|Array<Hash<Symbol,String|Numeric>>`):
|
342
|
-
- `lock_queue` - `string` - lock queue key in redis;
|
343
|
-
- `queue` - `array` - an array of lock requests (array of hashes):
|
344
|
-
- `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
|
345
|
-
- `score` - `float`/`epoch` - time when the lock request was made (epoch);
|
378
|
+
- `"lock_queue"` - `string` - lock queue key in redis;
|
379
|
+
- `"queue"` - `array` - an array of lock requests (array of hashes):
|
380
|
+
- `"acq_id"` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
|
381
|
+
- `"score"` - `float`/`epoch` - time when the lock request was made (epoch);
|
346
382
|
|
347
383
|
```
|
348
384
|
| Returns an information about the required lock queue by the lock name. The result
|
@@ -357,11 +393,11 @@ rql.queue_info("your_lock_name")
|
|
357
393
|
|
358
394
|
# =>
|
359
395
|
{
|
360
|
-
lock_queue
|
361
|
-
queue
|
362
|
-
{ acq_id
|
363
|
-
{ acq_id
|
364
|
-
{ acq_id
|
396
|
+
"lock_queue" => "rql:lock_queue:your_lock_name",
|
397
|
+
"queue" => [
|
398
|
+
{ "acq_id" => "rql:acq:123/456/567/678/fa76df9cc2", "score" => 1},
|
399
|
+
{ "acq_id" => "rql:acq:123/567/456/679/c7bfcaf4f9", "score" => 2},
|
400
|
+
{ "acq_id" => "rql:acq:555/329/523/127/7329553b11", "score" => 3},
|
365
401
|
# ...etc
|
366
402
|
]
|
367
403
|
}
|
@@ -604,7 +640,8 @@ Detalized event semantics and payload structure:
|
|
604
640
|
- `100%` test coverage;
|
605
641
|
- per-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
|
606
642
|
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
607
|
-
whose ttl may expire before the block execution completes)
|
643
|
+
whose ttl may expire before the block execution completes). It only makes sens for non-`timed` locks
|
644
|
+
(for those locks where otaned with `timed: false` option);
|
608
645
|
- an ability to add custom metadata to the lock and an ability to read this data;
|
609
646
|
- lock prioritization;
|
610
647
|
- support for LIFO strategy;
|
@@ -613,6 +650,7 @@ Detalized event semantics and payload structure:
|
|
613
650
|
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
614
651
|
- better code stylization and interesting refactorings;
|
615
652
|
- dead queue cleanup;
|
653
|
+
- statistics with UI;
|
616
654
|
- support for `Dragonfly` DB backend;
|
617
655
|
- support for `Garnet` DB backend;
|
618
656
|
|
@@ -17,6 +17,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
17
17
|
# @param ttl [Integer]
|
18
18
|
# @param queue_ttl [Integer]
|
19
19
|
# @param fail_fast [Boolean]
|
20
|
+
# @param meta [NilClass,Hash<String|Symbol,Any>]
|
20
21
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol|Hash<Symbol,Any> }
|
21
22
|
#
|
22
23
|
# @api private
|
@@ -32,7 +33,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
32
33
|
acquier_position,
|
33
34
|
ttl,
|
34
35
|
queue_ttl,
|
35
|
-
fail_fast
|
36
|
+
fail_fast,
|
37
|
+
meta
|
36
38
|
)
|
37
39
|
# Step X: intermediate invocation results
|
38
40
|
inter_result = nil
|
@@ -44,7 +46,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
44
46
|
"[redis_queued_locks.try_lock.start] " \
|
45
47
|
"lock_key => '#{lock_key}' " \
|
46
48
|
"queue_ttl => #{queue_ttl} " \
|
47
|
-
"acq_id => '#{acquier_id}'
|
49
|
+
"acq_id => '#{acquier_id}'"
|
48
50
|
)
|
49
51
|
end
|
50
52
|
end
|
@@ -161,7 +163,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
161
163
|
"lock_key => '#{lock_key}' " \
|
162
164
|
"queue_ttl => #{queue_ttl} " \
|
163
165
|
"acq_id => '#{acquier_id}' " \
|
164
|
-
"first_acq_id_in_queue => '#{waiting_acquier}'"
|
166
|
+
"first_acq_id_in_queue => '#{waiting_acquier}' " \
|
167
|
+
"<current_lock_data> => <<#{rconn.call('HGETALL', lock_key).to_h}>>"
|
165
168
|
)
|
166
169
|
end
|
167
170
|
end
|
@@ -197,7 +200,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
197
200
|
"queue_ttl => #{queue_ttl} " \
|
198
201
|
"acq_id => '#{acquier_id}' " \
|
199
202
|
"first_acq_id_in_queue => '#{waiting_acquier}' " \
|
200
|
-
"locked_by_acq_id => '#{locked_by_acquier}'"
|
203
|
+
"locked_by_acq_id => '#{locked_by_acquier}' " \
|
204
|
+
"<current_lock_data> => <<#{rconn.call('HGETALL', lock_key).to_h}>>"
|
201
205
|
)
|
202
206
|
end
|
203
207
|
end
|
@@ -230,7 +234,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
230
234
|
lock_key,
|
231
235
|
'acq_id', acquier_id,
|
232
236
|
'ts', (timestamp = Time.now.to_f),
|
233
|
-
'ini_ttl', ttl
|
237
|
+
'ini_ttl', ttl,
|
238
|
+
*(meta.to_a if meta != nil)
|
234
239
|
)
|
235
240
|
|
236
241
|
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
@@ -21,9 +21,11 @@ module RedisQueuedLocks::Acquier::AcquireLock::WithAcqTimeout
|
|
21
21
|
on_timeout.call unless on_timeout == nil
|
22
22
|
|
23
23
|
if raise_errors
|
24
|
-
raise(
|
25
|
-
|
26
|
-
|
24
|
+
raise(
|
25
|
+
RedisQueuedLocks::LockAcquiermentTimeoutError,
|
26
|
+
"Failed to acquire the lock \"#{lock_key}\" " \
|
27
|
+
"for the given timeout (#{timeout} seconds)."
|
28
|
+
)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -63,8 +63,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
63
63
|
def yield_with_timeout(timeout, lock_key, lock_ttl, &block)
|
64
64
|
::Timeout.timeout(timeout, &block)
|
65
65
|
rescue ::Timeout::Error
|
66
|
-
raise(
|
67
|
-
|
68
|
-
|
66
|
+
raise(
|
67
|
+
RedisQueuedLocks::TimedLockTimeoutError,
|
68
|
+
"Passed <timed> block of code exceeded " \
|
69
|
+
"the lock TTL (lock: \"#{lock_key}\", ttl: #{lock_ttl})"
|
70
|
+
)
|
69
71
|
end
|
70
72
|
end
|
@@ -67,8 +67,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
67
67
|
# @option fail_fast [Boolean]
|
68
68
|
# Should the required lock to be checked before the try and exit immidetly if lock is
|
69
69
|
# already obtained.
|
70
|
-
# @option
|
71
|
-
# - A custom metadata wich will be passed to the
|
70
|
+
# @option meta [NilClass,Hash<String|Symbol,Any>]
|
71
|
+
# - A custom metadata wich will be passed to the lock data in addition to the existing data;
|
72
|
+
# - Metadata can not contain reserved lock data keys;
|
72
73
|
# @option logger [::Logger,#debug]
|
73
74
|
# - Logger object used from the configuration layer (see config[:logger]);
|
74
75
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
@@ -76,6 +77,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
76
77
|
# - should be logged the each try of lock acquiring (a lot of logs can be generated depending
|
77
78
|
# on your retry configurations);
|
78
79
|
# - see `config[:log_lock_try]`;
|
80
|
+
# @option instrument [NilClass,Any]
|
81
|
+
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
82
|
+
# with :instrument key;
|
79
83
|
# @param [Block]
|
80
84
|
# A block of code that should be executed after the successfully acquired lock.
|
81
85
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -102,11 +106,38 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
102
106
|
instrumenter:,
|
103
107
|
identity:,
|
104
108
|
fail_fast:,
|
105
|
-
|
109
|
+
meta:,
|
110
|
+
instrument:,
|
106
111
|
logger:,
|
107
112
|
log_lock_try:,
|
108
113
|
&block
|
109
114
|
)
|
115
|
+
# Step 0: Prevent argument type incompatabilities
|
116
|
+
# Step 0.1: prevent :meta incompatabiltiies (type)
|
117
|
+
case meta # NOTE: do not ask why case/when is used here
|
118
|
+
when Hash, NilClass then nil
|
119
|
+
else
|
120
|
+
raise(
|
121
|
+
RedisQueuedLocks::ArgumentError,
|
122
|
+
"`:meta` argument should be a type of NilClass or Hash, got #{meta.class}."
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Step 0.2: prevent :meta incompatabiltiies (structure)
|
127
|
+
if meta == ::Hash && (meta.keys.any? do |key|
|
128
|
+
key == 'acq_id' ||
|
129
|
+
key == 'ts' ||
|
130
|
+
key == 'ini_ttl' ||
|
131
|
+
key == 'lock_key' ||
|
132
|
+
key == 'rem_ttl'
|
133
|
+
end)
|
134
|
+
raise(
|
135
|
+
RedisQueuedLocks::ArgumentError,
|
136
|
+
'`:meta` keys can not overlap reserved lock data keys' \
|
137
|
+
'"acq_id", "ts", "ini_ttl", "lock_key", "rem_ttl"'
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
110
141
|
# Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
|
111
142
|
acquier_id = RedisQueuedLocks::Resource.acquier_identifier(
|
112
143
|
process_id,
|
@@ -185,7 +216,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
185
216
|
acquier_position,
|
186
217
|
lock_ttl,
|
187
218
|
queue_ttl,
|
188
|
-
fail_fast
|
219
|
+
fail_fast,
|
220
|
+
meta
|
189
221
|
) => { ok:, result: }
|
190
222
|
|
191
223
|
acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
@@ -215,7 +247,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
215
247
|
acq_id: result[:acq_id],
|
216
248
|
ts: result[:ts],
|
217
249
|
acq_time: acq_time,
|
218
|
-
|
250
|
+
instrument:
|
219
251
|
})
|
220
252
|
end
|
221
253
|
|
@@ -233,9 +265,10 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
233
265
|
elsif fail_fast && acq_process[:result] == :fail_fast_no_try
|
234
266
|
acq_process[:should_try] = false
|
235
267
|
if raise_errors
|
236
|
-
raise(
|
237
|
-
|
238
|
-
|
268
|
+
raise(
|
269
|
+
RedisQueuedLocks::LockAlreadyObtainedError,
|
270
|
+
"Lock \"#{lock_key}\" is already obtained."
|
271
|
+
)
|
239
272
|
end
|
240
273
|
else
|
241
274
|
# Step 2.1.b: failed acquirement => retry
|
@@ -255,14 +288,16 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
255
288
|
|
256
289
|
# NOTE: check and raise an error
|
257
290
|
if fail_fast && raise_errors
|
258
|
-
raise(
|
259
|
-
|
260
|
-
|
291
|
+
raise(
|
292
|
+
RedisQueuedLocks::LockAlreadyObtainedError,
|
293
|
+
"Lock \"#{lock_key}\" is already obtained."
|
294
|
+
)
|
261
295
|
elsif raise_errors
|
262
|
-
raise(
|
263
|
-
|
264
|
-
|
265
|
-
|
296
|
+
raise(
|
297
|
+
RedisQueuedLocks::LockAcquiermentRetryLimitError,
|
298
|
+
"Failed to acquire the lock \"#{lock_key}\" " \
|
299
|
+
"for the given retry_count limit (#{retry_count} times)."
|
300
|
+
)
|
266
301
|
end
|
267
302
|
else
|
268
303
|
# NOTE:
|
@@ -307,7 +342,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
307
342
|
ts: acq_process[:lock_info][:ts],
|
308
343
|
lock_key: acq_process[:lock_info][:lock_key],
|
309
344
|
acq_time: acq_process[:acq_time],
|
310
|
-
|
345
|
+
instrument:
|
311
346
|
})
|
312
347
|
end
|
313
348
|
end
|
@@ -6,14 +6,14 @@ module RedisQueuedLocks::Acquier::LockInfo
|
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
8
8
|
# @param lock_name [String]
|
9
|
-
# @return [Hash<
|
9
|
+
# @return [Hash<String,String|Numeric>,NilClass]
|
10
10
|
# - `nil` is returned when lock key does not exist or expired;
|
11
11
|
# - result format: {
|
12
|
-
# lock_key
|
13
|
-
# acq_id
|
14
|
-
# ts
|
15
|
-
# ini_ttl
|
16
|
-
# rem_ttl
|
12
|
+
# 'lock_key' => "rql:lock:your_lockname", # acquired lock key
|
13
|
+
# 'acq_id' => "rql:acq:process_id/thread_id", # lock acquier identifier
|
14
|
+
# 'ts' => 123456789.2649841, # <locked at> time stamp (epoch, seconds.microseconds)
|
15
|
+
# 'ini_ttl' => 123456789, # initial lock key ttl (milliseconds),
|
16
|
+
# 'rem_ttl' => 123456789, # remaining lock key ttl (milliseconds)
|
17
17
|
# }
|
18
18
|
#
|
19
19
|
# @api private
|
@@ -43,13 +43,12 @@ module RedisQueuedLocks::Acquier::LockInfo
|
|
43
43
|
# NOTE: the result of MULTI-command is an array of results of each internal command
|
44
44
|
# - result[0] (HGETALL) (Hash<String,String>)
|
45
45
|
# - result[1] (PTTL) (Integer)
|
46
|
-
|
47
|
-
lock_key
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
}
|
46
|
+
hget_cmd_res.tap do |lock_data|
|
47
|
+
lock_data['lock_key'] = lock_key
|
48
|
+
lock_data['ts'] = Float(lock_data['ts'])
|
49
|
+
lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
|
50
|
+
lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
|
51
|
+
end
|
53
52
|
end
|
54
53
|
end
|
55
54
|
end
|
@@ -12,13 +12,13 @@ module RedisQueuedLocks::Acquier::QueueInfo
|
|
12
12
|
#
|
13
13
|
# @param redis_client [RedisClient]
|
14
14
|
# @param lock_name [String]
|
15
|
-
# @return [Hash<
|
15
|
+
# @return [Hash<String|Array<Hash<String,String|Numeric>>,NilClass]
|
16
16
|
# - `nil` is returned when lock queue does not exist;
|
17
17
|
# - result format: {
|
18
|
-
# lock_queue
|
18
|
+
# "lock_queue" => "rql:lock_queue:your_lock_name", # lock queue key in redis,
|
19
19
|
# queue: [
|
20
|
-
# { acq_id
|
21
|
-
# { acq_id
|
20
|
+
# { "acq_id" => "rql:acq:process_id/thread_id", "score" => 123 },
|
21
|
+
# { "acq_id" => "rql:acq:process_id/thread_id", "score" => 456 },
|
22
22
|
# ] # ordered set (by score) with information about an acquier and their position in queue
|
23
23
|
# }
|
24
24
|
#
|
@@ -38,8 +38,8 @@ module RedisQueuedLocks::Acquier::QueueInfo
|
|
38
38
|
if exists_cmd_res == 1
|
39
39
|
# NOTE: queue existed during the piepline invocation
|
40
40
|
{
|
41
|
-
lock_queue
|
42
|
-
queue
|
41
|
+
'lock_queue' => lock_key_queue,
|
42
|
+
'queue' => zrange_cmd_res.map { |val| { 'acq_id' => val[0], 'score' => val[1] } }
|
43
43
|
}
|
44
44
|
else
|
45
45
|
# NOTE: queue did not exist during the pipeline invocation
|
@@ -93,8 +93,9 @@ class RedisQueuedLocks::Client
|
|
93
93
|
# already obtained;
|
94
94
|
# - Should the logic exit immidietly after the first try if the lock was obtained
|
95
95
|
# by another process while the lock request queue was initially empty;
|
96
|
-
# @option
|
97
|
-
# - A custom metadata wich will be passed to the
|
96
|
+
# @option meta [NilClass,Hash<String|Symbol,Any>]
|
97
|
+
# - A custom metadata wich will be passed to the lock data in addition to the existing data;
|
98
|
+
# - Metadata can not contain reserved lock data keys;
|
98
99
|
# @option logger [::Logger,#debug]
|
99
100
|
# - Logger object used from the configuration layer (see config[:logger]);
|
100
101
|
# - See `RedisQueuedLocks::Logging::VoidLogger` for example;
|
@@ -102,6 +103,9 @@ class RedisQueuedLocks::Client
|
|
102
103
|
# - should be logged the each try of lock acquiring (a lot of logs can
|
103
104
|
# be generated depending on your retry configurations);
|
104
105
|
# - see `config[:log_lock_try]`;
|
106
|
+
# @option instrument [NilClass,Any]
|
107
|
+
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
108
|
+
# with :instrument key;
|
105
109
|
# @param block [Block]
|
106
110
|
# A block of code that should be executed after the successfully acquired lock.
|
107
111
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -122,9 +126,10 @@ class RedisQueuedLocks::Client
|
|
122
126
|
raise_errors: false,
|
123
127
|
fail_fast: false,
|
124
128
|
identity: uniq_identity,
|
125
|
-
|
129
|
+
meta: nil,
|
126
130
|
logger: config[:logger],
|
127
131
|
log_lock_try: config[:log_lock_try],
|
132
|
+
instrument: nil,
|
128
133
|
&block
|
129
134
|
)
|
130
135
|
RedisQueuedLocks::Acquier::AcquireLock.acquire_lock(
|
@@ -145,9 +150,10 @@ class RedisQueuedLocks::Client
|
|
145
150
|
instrumenter: config[:instrumenter],
|
146
151
|
identity:,
|
147
152
|
fail_fast:,
|
148
|
-
|
153
|
+
meta:,
|
149
154
|
logger: config[:logger],
|
150
155
|
log_lock_try: config[:log_lock_try],
|
156
|
+
instrument:,
|
151
157
|
&block
|
152
158
|
)
|
153
159
|
end
|
@@ -167,9 +173,10 @@ class RedisQueuedLocks::Client
|
|
167
173
|
retry_jitter: config[:retry_jitter],
|
168
174
|
fail_fast: false,
|
169
175
|
identity: uniq_identity,
|
170
|
-
|
176
|
+
meta: nil,
|
171
177
|
logger: config[:logger],
|
172
178
|
log_lock_try: config[:log_lock_try],
|
179
|
+
instrument: nil,
|
173
180
|
&block
|
174
181
|
)
|
175
182
|
lock(
|
@@ -184,7 +191,8 @@ class RedisQueuedLocks::Client
|
|
184
191
|
raise_errors: true,
|
185
192
|
identity:,
|
186
193
|
fail_fast:,
|
187
|
-
|
194
|
+
meta:,
|
195
|
+
instrument:,
|
188
196
|
&block
|
189
197
|
)
|
190
198
|
end
|
@@ -224,7 +232,7 @@ class RedisQueuedLocks::Client
|
|
224
232
|
end
|
225
233
|
|
226
234
|
# @param lock_name [String]
|
227
|
-
# @return [Hash,NilClass]
|
235
|
+
# @return [Hash<String,String|Numeric>,NilClass]
|
228
236
|
#
|
229
237
|
# @api public
|
230
238
|
# @since 0.1.0
|
@@ -233,7 +241,7 @@ class RedisQueuedLocks::Client
|
|
233
241
|
end
|
234
242
|
|
235
243
|
# @param lock_name [String]
|
236
|
-
# @return [Hash,NilClass]
|
244
|
+
# @return [Hash<String|Array<Hash<String,String|Numeric>>,NilClass]
|
237
245
|
#
|
238
246
|
# @api public
|
239
247
|
# @since 0.1.0
|
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.33
|
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-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -107,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
107
|
- !ruby/object:Gem::Version
|
108
108
|
version: '0'
|
109
109
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
110
|
+
rubygems_version: 3.5.1
|
111
111
|
signing_key:
|
112
112
|
specification_version: 4
|
113
113
|
summary: Queued distributed locks based on Redis.
|