redis_queued_locks 1.16.1 → 1.16.2
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 -1
- data/LICENSE.txt +1 -1
- data/README.md +19 -11
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +62 -51
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d49a1559431c569e92cfc1a9b56bd04d9942002aa6d79efcb533370f58b0c184
|
|
4
|
+
data.tar.gz: bc8fd235df24386a92275273de91d34804452eb085bbb5a2b7118eb4508319ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e7d0944406f13a7a6bac3e59153998fca9731e184ef5da83d8d415af7185f61a4b260166895d3858c501f4ae93cf160fb6b4c5c33e248d2aa82c9bac2265216
|
|
7
|
+
data.tar.gz: 8aff1a1fb28cbc510edc30efbab0eed49bbb088feff6235959b314b24d705e0058c6d1a347356874fa2a4b930f7aaba62e7f6b48bebf2622e11600998055f719
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.16.2] - 2026-02-06
|
|
4
|
+
### Fixed
|
|
5
|
+
- `lock_series` PoC:
|
|
6
|
+
- acquired locks are not released when the block of code invoked under the lock series fails with
|
|
7
|
+
an exception
|
|
8
|
+
- `raise_errors: true` option incorrectly suppresses the lock releasing when the lock series
|
|
9
|
+
reaches their obtaning limits
|
|
10
|
+
|
|
3
11
|
## [1.16.1] - 2026-01-28
|
|
4
12
|
### Changed
|
|
5
13
|
- `lock_series` PoC:
|
|
@@ -30,7 +38,7 @@
|
|
|
30
38
|
- lock series of lock simultainously (and release them all at the finish of your the invocation of block of code under the obtained locks);
|
|
31
39
|
- exmaple: `client.lock_series("a", "b", "c")` (or in exceptional-based way `client.lock_series!("a", "b", "c")`;
|
|
32
40
|
### Changed
|
|
33
|
-
- YARDOC documentation updates in order to
|
|
41
|
+
- YARDOC documentation updates in order to prepare to the 100% yardoc-coverage;
|
|
34
42
|
- updated dev deps;
|
|
35
43
|
### Fixed
|
|
36
44
|
- fixed some yardoc documentation typos;
|
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024-
|
|
3
|
+
Copyright (c) 2024-2026 Rustam Ibragimov
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Each lock request is put into the request queue (each lock is hosted by its own
|
|
|
8
8
|
|
|
9
9
|
In addition to the classic `queued` (FIFO) strategy RQL supports `random` (RANDOM) lock obtaining strategy when any acquirer from the lock queue can obtain the lock regardless the position in the queue.
|
|
10
10
|
|
|
11
|
-
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), logging and instrumentation.
|
|
11
|
+
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), **zombie locks elimination**, support for **reentrant locks**, logging and instrumentation (and much more).
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -23,8 +23,8 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
23
23
|
- [Usage](#usage)
|
|
24
24
|
- [lock](#lock---obtain-a-lock)
|
|
25
25
|
- [lock!](#lock---exceptional-lock-obtaining)
|
|
26
|
-
- [lock_series
|
|
27
|
-
- [lock_series!
|
|
26
|
+
- [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
|
|
27
|
+
- [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
|
|
28
28
|
- [lock_info](#lock_info)
|
|
29
29
|
- [queue_info](#queue_info)
|
|
30
30
|
- [locked?](#locked)
|
|
@@ -86,10 +86,10 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
86
86
|
|
|
87
87
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
88
88
|
|
|
89
|
-
- Redis Version: `~>
|
|
89
|
+
- Redis Version: `>= 7`, `~> 8.x`;
|
|
90
90
|
- Redis Protocol: `RESP3`;
|
|
91
91
|
- gem `redis-client`: `~> 0.20`;
|
|
92
|
-
- Ruby: `>= 3.
|
|
92
|
+
- Ruby: `>= 3.3`;
|
|
93
93
|
|
|
94
94
|
---
|
|
95
95
|
|
|
@@ -854,13 +854,14 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
|
|
|
854
854
|
|
|
855
855
|
#### #lock_series - (PoC) acquire a series of locks
|
|
856
856
|
|
|
857
|
-
|
|
857
|
+
<sup>\[[back to top](#usage)\]</sup>
|
|
858
|
+
|
|
859
|
+
> (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future.
|
|
858
860
|
|
|
859
861
|
`lock_series` - acquire a series of locks simultaniously and hold them all while your block of code is executing
|
|
860
862
|
or (if block is not passed) while all locks will not expire.
|
|
861
863
|
|
|
862
|
-
If it is not possible to obtain all locks at the same time (taking into queue ttl, retry attempts count for each lock, etc)
|
|
863
|
-
- no any lock will be acquired.
|
|
864
|
+
If it is not possible to obtain all locks at the same time (taking into queue ttl, retry attempts count for each lock, etc) no any lock will be acquired.
|
|
864
865
|
|
|
865
866
|
Method options is the same as for `lock` method: `retry_count`, `retry_jitter`, `ttl`, `queue_ttl`, etc. Each option will be
|
|
866
867
|
applied with the same value to the each lock acquirement process (for each lock in a series separately).
|
|
@@ -880,7 +881,7 @@ applied with the same value to the each lock acquirement process (for each lock
|
|
|
880
881
|
|
|
881
882
|
##### How to use:
|
|
882
883
|
|
|
883
|
-
- method signature is the same as the
|
|
884
|
+
- method signature is the same as the `#lock`;
|
|
884
885
|
|
|
885
886
|
```ruby
|
|
886
887
|
# typical logic-oriented way
|
|
@@ -937,7 +938,7 @@ client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
|
|
|
937
938
|
}
|
|
938
939
|
```
|
|
939
940
|
|
|
940
|
-
|
|
941
|
+
###### New instrumentaiton events:
|
|
941
942
|
|
|
942
943
|
```ruby
|
|
943
944
|
# NEW instrumentation events (examples)
|
|
@@ -945,6 +946,8 @@ client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
|
|
|
945
946
|
"redis_queued_locks.lock_series_hold_and_release" => # {lock_keys: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], hold_time: 0.29, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, instrument: nil}
|
|
946
947
|
```
|
|
947
948
|
|
|
949
|
+
###### New logs:
|
|
950
|
+
|
|
948
951
|
```shell
|
|
949
952
|
# NEW logs (examples)
|
|
950
953
|
[redis_queued_locks.start_lock_series_obtaining] lock_keys => '["rql:lock:a", "rql:lock:b"]'queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued'
|
|
@@ -956,6 +959,8 @@ client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
|
|
|
956
959
|
|
|
957
960
|
#### #lock_series! - (PoC) exceptional `lock_series`
|
|
958
961
|
|
|
962
|
+
<sup>\[[back to top](#usage)\]</sup>
|
|
963
|
+
|
|
959
964
|
> (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future (very-very soon)
|
|
960
965
|
|
|
961
966
|
For more details see `lock_series` [docs](#lock_series---poc-acquire-a-series-of-locks)
|
|
@@ -2430,7 +2435,8 @@ Detalized event semantics and payload structure:
|
|
|
2430
2435
|
some GVL-related things and problem situations when the global watcher thread is "dead";
|
|
2431
2436
|
- lock request prioritization;
|
|
2432
2437
|
- **strict redlock algorithm support** (support for many `RedisClient` instances that are fully independent (distributed redis instances));
|
|
2433
|
-
-
|
|
2438
|
+
- #lock_series` - acquire a series of locks:
|
|
2439
|
+
- (NOTE: "Proof of Concept" implementation is ready to use);
|
|
2434
2440
|
```ruby
|
|
2435
2441
|
rql.lock_series('lock_a', 'lock_b', 'lock_c') { puts 'locked' }
|
|
2436
2442
|
```
|
|
@@ -2477,6 +2483,8 @@ Detalized event semantics and payload structure:
|
|
|
2477
2483
|
- **Research**: support for `Garnet` database backend (https://microsoft.github.io/) (https://github.com/microsoft/garnet);
|
|
2478
2484
|
- add a library-level exception, when RQL-related key in Redis (required for its logic) has incompatible type (means: some other program uses our key with their own type and logic and RQL can't work properly);
|
|
2479
2485
|
- yardoc docs with CI check (full doc coverage check);
|
|
2486
|
+
- split *exception* inheritance to the groups: `lock obtaining errors`, `block invocation errors`, `swarm errors`, and other groups (research possible groups):
|
|
2487
|
+
- in some cases we need to intercept "Lock Obtaining Process Erros", in other cases: "Block Invocation Errors", and so on (in `Lock Serirs PoC` for example);
|
|
2480
2488
|
|
|
2481
2489
|
---
|
|
2482
2490
|
|
|
@@ -15,7 +15,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
15
15
|
class << self
|
|
16
16
|
# @api private
|
|
17
17
|
# @since 1.16.0
|
|
18
|
-
# @version 1.16.
|
|
18
|
+
# @version 1.16.2
|
|
19
19
|
def lock_series_poc( # steep:ignore
|
|
20
20
|
redis,
|
|
21
21
|
lock_names,
|
|
@@ -175,8 +175,12 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
175
175
|
instr_sampler:,
|
|
176
176
|
instr_sample_this:
|
|
177
177
|
)
|
|
178
|
-
rescue
|
|
179
|
-
|
|
178
|
+
rescue RedisQueuedLocks::LockAlreadyObtainedError,
|
|
179
|
+
RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
180
|
+
RedisQueuedLocks::LockAcquirementTimeoutError,
|
|
181
|
+
RedisQueuedLocks::LockAcquirementRetryLimitError,
|
|
182
|
+
RedisQueuedLocks::ConflictLockObtainError => error
|
|
183
|
+
if successfully_acquired_locks.any?
|
|
180
184
|
# NOTE: release all previously acquired locks if any next lock is already locked
|
|
181
185
|
successfully_acquired_locks.each do |operation_result|
|
|
182
186
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(operation_result[:lock_name])
|
|
@@ -187,7 +191,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
187
191
|
end
|
|
188
192
|
end
|
|
189
193
|
end
|
|
190
|
-
|
|
194
|
+
|
|
195
|
+
raise(error) if raise_errors
|
|
191
196
|
end
|
|
192
197
|
|
|
193
198
|
if result[:ok]
|
|
@@ -225,61 +230,67 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
225
230
|
RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
|
|
226
231
|
).ceil(2)
|
|
227
232
|
|
|
228
|
-
yield_result =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
lock_keys_for_instrumentation.last,
|
|
232
|
-
acquirer_id_for_instrumentation,
|
|
233
|
-
host_id_for_instrumentation,
|
|
234
|
-
access_strategy,
|
|
235
|
-
timed,
|
|
236
|
-
ttl_shift,
|
|
237
|
-
ttl,
|
|
238
|
-
queue_ttl,
|
|
239
|
-
meta,
|
|
240
|
-
log_sampled,
|
|
241
|
-
instr_sampled,
|
|
242
|
-
false, # should_expire (expire manually)
|
|
243
|
-
false, # should_decrease (expire manually)
|
|
244
|
-
&block
|
|
245
|
-
)
|
|
233
|
+
yield_result = nil
|
|
234
|
+
is_lock_manually_released = nil
|
|
235
|
+
hold_time = nil
|
|
246
236
|
|
|
247
|
-
|
|
237
|
+
begin
|
|
238
|
+
yield_result = yield_expire( # steep:ignore
|
|
239
|
+
redis,
|
|
240
|
+
logger,
|
|
241
|
+
lock_keys_for_instrumentation.last,
|
|
242
|
+
acquirer_id_for_instrumentation,
|
|
243
|
+
host_id_for_instrumentation,
|
|
244
|
+
access_strategy,
|
|
245
|
+
timed,
|
|
246
|
+
ttl_shift,
|
|
247
|
+
ttl,
|
|
248
|
+
queue_ttl,
|
|
249
|
+
meta,
|
|
250
|
+
log_sampled,
|
|
251
|
+
instr_sampled,
|
|
252
|
+
false, # should_expire (expire manually)
|
|
253
|
+
false, # should_decrease (expire manually)
|
|
254
|
+
&block
|
|
255
|
+
)
|
|
256
|
+
ensure
|
|
257
|
+
is_lock_manually_released = false
|
|
248
258
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
259
|
+
# expire locks manually
|
|
260
|
+
if block_given?
|
|
261
|
+
redis.with do |conn|
|
|
262
|
+
# use transaction in order to exclude any cross-locking during the group expiration
|
|
263
|
+
conn.multi(watch: lock_keys_for_instrumentation) do |transaction|
|
|
264
|
+
lock_keys_for_instrumentation.each do |lock_key|
|
|
265
|
+
transaction.call('EXPIRE', lock_key, '0')
|
|
266
|
+
end
|
|
256
267
|
end
|
|
257
268
|
end
|
|
269
|
+
is_lock_manually_released = true
|
|
258
270
|
end
|
|
259
|
-
is_lock_manually_released = true
|
|
260
|
-
end
|
|
261
271
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
rel_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
273
|
+
hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
|
|
274
|
+
ts = Time.now.to_f
|
|
265
275
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
276
|
+
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.expire_lock_series( # steep:ignore
|
|
277
|
+
logger, log_sampled, lock_keys_for_instrumentation,
|
|
278
|
+
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
|
|
279
|
+
) if is_lock_manually_released
|
|
270
280
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_hold_and_release( # steep:ignore
|
|
282
|
+
instrumenter,
|
|
283
|
+
instr_sampled,
|
|
284
|
+
lock_keys_for_instrumentation,
|
|
285
|
+
ttl,
|
|
286
|
+
acquirer_id_for_instrumentation,
|
|
287
|
+
host_id_for_instrumentation,
|
|
288
|
+
ts,
|
|
289
|
+
acq_time,
|
|
290
|
+
hold_time,
|
|
291
|
+
instrument
|
|
292
|
+
) if is_lock_manually_released
|
|
293
|
+
end
|
|
283
294
|
|
|
284
295
|
if detailed_result
|
|
285
296
|
{
|