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
data/README.md
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
# RedisQueuedLocks
|
1
|
+
# RedisQueuedLocks · [![Gem Version](https://badge.fury.io/rb/redis_queued_locks.svg)](https://badge.fury.io/rb/redis_queued_locks)
|
2
2
|
|
3
|
-
Distributed locks with "lock acquisition queue" capabilities based on the Redis Database.
|
3
|
+
<a href="https://redis.io/docs/manual/patterns/distributed-locks/">Distributed locks</a> 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
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.
|
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) (with requeue capabilities) which guarantees the request queue will never be stacked.
|
8
8
|
|
9
9
|
---
|
10
10
|
|
11
11
|
## Table of Contents
|
12
12
|
|
13
13
|
- [Requirements](#requirements)
|
14
|
+
- [Experience](#experience)
|
14
15
|
- [Algorithm](#algorithm)
|
15
16
|
- [Installation](#installation)
|
16
17
|
- [Setup](#setup)
|
@@ -30,6 +31,8 @@ Each lock request is put into the request queue (each lock is hosted by it's own
|
|
30
31
|
- [keys](#keys---get-list-of-taken-locks-and-queues)
|
31
32
|
- [locks_info](#locks_info---get-list-of-locks-with-their-info)
|
32
33
|
- [queues_info](#queues_info---get-list-of-queues-with-their-info)
|
34
|
+
- [clear_dead_requests](#clear_dead_requests)
|
35
|
+
- [Logging](#logging)
|
33
36
|
- [Instrumentation](#instrumentation)
|
34
37
|
- [Instrumentation Events](#instrumentation-events)
|
35
38
|
- [Roadmap](#roadmap)
|
@@ -41,13 +44,28 @@ Each lock request is put into the request queue (each lock is hosted by it's own
|
|
41
44
|
|
42
45
|
### Requirements
|
43
46
|
|
47
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
48
|
+
|
44
49
|
- Redis Version: `~> 7.x`;
|
45
50
|
- Redis Protocol: `RESP3`;
|
51
|
+
- gem `redis-client`: `~> 0.20`;
|
52
|
+
- Ruby: `>= 3.1`;
|
53
|
+
|
54
|
+
---
|
55
|
+
|
56
|
+
### Experience
|
57
|
+
|
58
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
59
|
+
|
60
|
+
- Battle-tested on huge ruby projects in production: `~1500` locks-per-second are obtained and released on an ongoing basis;
|
61
|
+
- Works well with `hiredis` driver enabled (it is enabled by default on our projects where `redis_queued_locks` are used);
|
46
62
|
|
47
63
|
---
|
48
64
|
|
49
65
|
### Algorithm
|
50
66
|
|
67
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
68
|
+
|
51
69
|
> 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.
|
52
70
|
|
53
71
|
**Soon**: detailed explanation.
|
@@ -56,6 +74,8 @@ Each lock request is put into the request queue (each lock is hosted by it's own
|
|
56
74
|
|
57
75
|
### Installation
|
58
76
|
|
77
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
78
|
+
|
59
79
|
```ruby
|
60
80
|
gem 'redis_queued_locks'
|
61
81
|
```
|
@@ -74,6 +94,8 @@ require 'redis_queued_locks'
|
|
74
94
|
|
75
95
|
### Setup
|
76
96
|
|
97
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
98
|
+
|
77
99
|
```ruby
|
78
100
|
require 'redis_queued_locks'
|
79
101
|
|
@@ -95,6 +117,8 @@ rq_lock_client.lock("some-lock") { puts "Hello, lock!" }
|
|
95
117
|
|
96
118
|
### Configuration
|
97
119
|
|
120
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
121
|
+
|
98
122
|
```ruby
|
99
123
|
redis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance
|
100
124
|
|
@@ -121,6 +145,10 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
121
145
|
# - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the queue);
|
122
146
|
config.default_queue_ttl = 15
|
123
147
|
|
148
|
+
# (boolean) (default: false)
|
149
|
+
# - should be all blocks of code are timed by default;
|
150
|
+
config.is_timed_by_default = false
|
151
|
+
|
124
152
|
# (default: 100)
|
125
153
|
# - how many items will be released at a time in RedisQueuedLocks::Client#clear_locks logic (uses SCAN);
|
126
154
|
# - affects the performancs of your Redis and Ruby Application (configure thoughtfully);
|
@@ -156,6 +184,8 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
156
184
|
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id");
|
157
185
|
# - "[redis_queued_locks.dead_score_reached__reset_acquier_position]" (logs "lock_key", "queue_ttl", "acq_id");
|
158
186
|
# - "[redis_queued_locks.lock_obtained]" (logs "lockkey", "queue_ttl", "acq_id", "acq_time");
|
187
|
+
# - "[redis_queued_locks.fail_fast_or_limits_reached__dequeue] (logs "lock_key", "queue_ttl", "acq_id");
|
188
|
+
# - "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id");
|
159
189
|
# - by default uses VoidLogger that does nothing;
|
160
190
|
config.logger = RedisQueuedLocks::Logging::VoidLogger
|
161
191
|
|
@@ -170,8 +200,8 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
170
200
|
# - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue");
|
171
201
|
# - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id");
|
172
202
|
# - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "<current_lock_data>");
|
173
|
-
# - "[redis_queued_locks.try_lock.
|
174
|
-
# - "[redis_queued_locks.try_lock.
|
203
|
+
# - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
|
204
|
+
# - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id");
|
175
205
|
config.log_lock_try = false
|
176
206
|
end
|
177
207
|
```
|
@@ -180,6 +210,8 @@ end
|
|
180
210
|
|
181
211
|
### Usage
|
182
212
|
|
213
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
214
|
+
|
183
215
|
- [lock](#lock---obtain-a-lock)
|
184
216
|
- [lock!](#lock---exeptional-lock-obtaining)
|
185
217
|
- [lock_info](#lock_info)
|
@@ -194,11 +226,14 @@ end
|
|
194
226
|
- [keys](#keys---get-list-of-taken-locks-and-queues)
|
195
227
|
- [locks_info](#locks_info---get-list-of-locks-with-their-info)
|
196
228
|
- [queues_info](#queues_info---get-list-of-queues-with-their-info)
|
229
|
+
- [clear_dead_requests](#clear_dead_requests)
|
197
230
|
|
198
231
|
---
|
199
232
|
|
200
233
|
#### #lock - obtain a lock
|
201
234
|
|
235
|
+
<sup>\[[back to top](#usage)\]</sup>
|
236
|
+
|
202
237
|
- If block is passed the obtained lock will be released after the block execution or the lock's ttl (what will happen first);
|
203
238
|
- If block is not passed the obtained lock will be released after lock's ttl;
|
204
239
|
- If block is passed the block's yield result will be returned;
|
@@ -225,48 +260,64 @@ def lock(
|
|
225
260
|
)
|
226
261
|
```
|
227
262
|
|
228
|
-
- `lock_name` - `[String]`
|
263
|
+
- `lock_name` - (required) `[String]`
|
229
264
|
- Lock name to be obtained.
|
230
|
-
- `ttl` [Integer]
|
231
|
-
- Lock's time to live (in milliseconds)
|
232
|
-
-
|
265
|
+
- `ttl` - (optional) - [Integer]
|
266
|
+
- Lock's time to live (in milliseconds);
|
267
|
+
- pre-configured in `config[:default_lock_ttl]`;
|
268
|
+
- `queue_ttl` - (optional) `[Integer]`
|
233
269
|
- Lifetime of the acuier's lock request. In seconds.
|
234
|
-
-
|
235
|
-
|
236
|
-
-
|
270
|
+
- pre-configured in `config[:default_queue_ttl]`;
|
271
|
+
- `timeout` - (optional) `[Integer,NilClass]`
|
272
|
+
- Time period a client should try to acquire the lock (in seconds). Nil means "without timeout".
|
273
|
+
- pre-configured in `config[:try_to_lock_timeout]`;
|
274
|
+
- `timed` - (optiona) `[Boolean]`
|
237
275
|
- Limit the invocation time period of the passed block of code by the lock's TTL.
|
238
|
-
-
|
276
|
+
- pre-configured in `config[:is_timed_by_default]`;
|
277
|
+
- `false` by default;
|
278
|
+
- `retry_count` - (optional) `[Integer,NilClass]`
|
239
279
|
- How many times we should try to acquire a lock. Nil means "infinite retries".
|
240
|
-
-
|
280
|
+
- pre-configured in `config[:retry_count]`;
|
281
|
+
- `retry_delay` - (optional) `[Integer]`
|
241
282
|
- A time-interval between the each retry (in milliseconds).
|
242
|
-
-
|
243
|
-
|
244
|
-
-
|
245
|
-
-
|
246
|
-
- `
|
247
|
-
-
|
248
|
-
-
|
283
|
+
- pre-configured in `config[:retry_delay]`;
|
284
|
+
- `retry_jitter` - (optional) `[Integer]`
|
285
|
+
- Time-shift range for retry-delay (in milliseconds);
|
286
|
+
- pre-configured in `config[:retry_jitter]`;
|
287
|
+
- `instrumenter` - (optional) `[#notify]`
|
288
|
+
- See RedisQueuedLocks::Instrument::ActiveSupport for example;
|
289
|
+
- See [Instrumentation](#instrumentation) section of docs;
|
290
|
+
- pre-configured in `config[:isntrumenter]` with void notifier (`RedisQueuedLocks::Instrumenter::VoidNotifier`);
|
291
|
+
- `raise_errors` - (optional) `[Boolean]`
|
292
|
+
- Raise errors on library-related limits such as timeout or retry count limit;
|
293
|
+
- `false` by default;
|
294
|
+
- `fail_fast` - (optional) `[Boolean]`
|
249
295
|
- Should the required lock to be checked before the try and exit immidietly if lock is
|
250
296
|
already obtained;
|
251
297
|
- Should the logic exit immidietly after the first try if the lock was obtained
|
252
298
|
by another process while the lock request queue was initially empty;
|
253
|
-
- `
|
299
|
+
- `false` by default;
|
300
|
+
- `identity` - (optional) `[String]`
|
254
301
|
- An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
|
255
302
|
collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
|
256
303
|
pods or/and nodes of your application;
|
257
304
|
- It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
|
258
305
|
ivar (accessed via `uniq_dentity` accessor method);
|
259
|
-
-
|
306
|
+
- Identity calculator is pre-configured in `config[:uniq_identifier]`;
|
307
|
+
- `meta` - (optional) `[NilClass,Hash<String|Symbol,Any>]`
|
260
308
|
- A custom metadata wich will be passed to the lock data in addition to the existing data;
|
261
309
|
- Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`);
|
262
|
-
- `
|
310
|
+
- `nil` by default (means "no metadata");
|
311
|
+
- `instrument` - (optional) `[NilClass,Any]`
|
263
312
|
- Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
264
|
-
- `
|
265
|
-
|
266
|
-
-
|
267
|
-
-
|
313
|
+
- `nil` by default (means "no custom instrumentation data");
|
314
|
+
- `logger` - (optional) `[::Logger,#debug]`
|
315
|
+
- Logger object used for loggin internal mutation oeprations and opertioan results / process progress;
|
316
|
+
- pre-configured in `config[:logger]` with void logger `RedisQueuedLocks::Logging::VoidLogger`;
|
317
|
+
- `log_lock_try` - (optional) `[Boolean]`
|
268
318
|
- should be logged the each try of lock acquiring (a lot of logs can be generated depending on your retry configurations);
|
269
|
-
-
|
319
|
+
- pre-configured in `config[:log_lock_try]`;
|
320
|
+
- `false` by default;
|
270
321
|
- `block` - `[Block]`
|
271
322
|
- A block of code that should be executed after the successfully acquired lock.
|
272
323
|
- If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
|
@@ -274,8 +325,25 @@ def lock(
|
|
274
325
|
|
275
326
|
Return value:
|
276
327
|
|
277
|
-
- If block is passed the block's yield result will be returned
|
278
|
-
|
328
|
+
- If block is passed the block's yield result will be returned:
|
329
|
+
```ruby
|
330
|
+
result = rql.lock("my_lock") { 1 + 1 }
|
331
|
+
result # => 2
|
332
|
+
```
|
333
|
+
- If block is not passed the lock information will be returned:
|
334
|
+
```ruby
|
335
|
+
result = rql.lock("my_lock")
|
336
|
+
result # =>
|
337
|
+
{
|
338
|
+
ok: true,
|
339
|
+
result: {
|
340
|
+
lock_key: "rql:lock:my_lock",
|
341
|
+
acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
|
342
|
+
ts: 1711909612.653696,
|
343
|
+
ttl: 10000
|
344
|
+
}
|
345
|
+
}
|
346
|
+
```
|
279
347
|
- Lock information result:
|
280
348
|
- Signature: `[yield, Hash<Symbol,Boolean|Hash<Symbol,Numeric|String>>]`
|
281
349
|
- Format: `{ ok: true/false, result: <Symbol|Hash<Symbol,Hash>> }`;
|
@@ -286,11 +354,23 @@ Return value:
|
|
286
354
|
result: {
|
287
355
|
lock_key: String, # acquierd lock key ("rql:lock:your_lock_name")
|
288
356
|
acq_id: String, # acquier identifier ("process_id/thread_id/fiber_id/ractor_id/identity")
|
289
|
-
ts:
|
357
|
+
ts: Float, # time (epoch) when lock was obtained (float, Time#to_f)
|
290
358
|
ttl: Integer # lock's time to live in milliseconds (integer)
|
291
359
|
}
|
292
360
|
}
|
293
361
|
```
|
362
|
+
```ruby
|
363
|
+
# example:
|
364
|
+
{
|
365
|
+
ok: true,
|
366
|
+
result: {
|
367
|
+
lock_key: "rql:lock:my_lock",
|
368
|
+
acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
|
369
|
+
ts: 1711909612.653696,
|
370
|
+
ttl: 10000
|
371
|
+
}
|
372
|
+
}
|
373
|
+
```
|
294
374
|
- for failed lock obtaining:
|
295
375
|
```ruby
|
296
376
|
{ ok: false, result: :timeout_reached }
|
@@ -300,10 +380,61 @@ Return value:
|
|
300
380
|
{ ok: false, result: :unknown }
|
301
381
|
```
|
302
382
|
|
383
|
+
Examples:
|
384
|
+
|
385
|
+
- obtain a lock:
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
rql.lock("my_lock") { print "Hello!" }
|
389
|
+
```
|
390
|
+
|
391
|
+
- obtain a lock with custom lock TTL:
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
rql.lock("my_lock", ttl: 5_000) { print "Hello!" } # for 5 seconds
|
395
|
+
```
|
396
|
+
|
397
|
+
- obtain a lock and limit the passed block of code TTL with lock's TTL:
|
398
|
+
|
399
|
+
```ruby
|
400
|
+
rql.lock("my_lock", ttl: 5_000, timed: true) { sleep(4) }
|
401
|
+
# => OK
|
402
|
+
|
403
|
+
rql.lock("my_lock", ttl: 5_000, timed: true) { sleep(6) }
|
404
|
+
# => fails with RedisQueuedLocks::TimedLockTimeoutError
|
405
|
+
```
|
406
|
+
|
407
|
+
- infinite lock obtaining (no retry limit, no timeout limit):
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
rql.lock("my_lock", retry_count: nil, timeout: nil)
|
411
|
+
```
|
412
|
+
|
413
|
+
- try to obtain with a custom waiting timeout:
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
# First Ruby Process:
|
417
|
+
rql.lock("my_lock", ttl: 5_000) { sleep(4) } # acquire a long living lock
|
418
|
+
|
419
|
+
# Another Ruby Process:
|
420
|
+
rql.lock("my_lock", timeout: 2) # try to acquire but wait for a 2 seconds maximum
|
421
|
+
# =>
|
422
|
+
{ ok: false, result: :timeout_reached }
|
423
|
+
```
|
424
|
+
|
425
|
+
- obtain a lock and immediatly continue working (the lock will live in the background in Redis with the passed ttl)
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
rql.lock("my_lock", ttl: 6_500) # blocks execution until the lock is obtained
|
429
|
+
puts "Let's go" # will be called immediately after the lock is obtained
|
430
|
+
```
|
431
|
+
|
303
432
|
---
|
304
433
|
|
305
434
|
#### #lock! - exceptional lock obtaining
|
306
435
|
|
436
|
+
<sup>\[[back to top](#usage)\]</sup>
|
437
|
+
|
307
438
|
- fails when (and with):
|
308
439
|
- (`RedisQueuedLocks::LockAlreadyObtainedError`) when `fail_fast` is `true` and lock is already obtained;
|
309
440
|
- (`RedisQueuedLocks::LockAcquiermentTimeoutError`) `timeout` limit reached before lock is obtained;
|
@@ -334,9 +465,11 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
|
|
334
465
|
|
335
466
|
#### #lock_info
|
336
467
|
|
468
|
+
<sup>\[[back to top](#usage)\]</sup>
|
469
|
+
|
337
470
|
- get the lock information;
|
338
471
|
- returns `nil` if lock does not exist;
|
339
|
-
- lock data (`Hash<
|
472
|
+
- lock data (`Hash<String,String|Integer>`):
|
340
473
|
- `"lock_key"` - `string` - lock key in redis;
|
341
474
|
- `"acq_id"` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity);
|
342
475
|
- `"ts"` - `integer`/`epoch` - the time lock was obtained;
|
@@ -379,26 +512,26 @@ rql.lock_info("your_lock_name")
|
|
379
512
|
|
380
513
|
#### #queue_info
|
381
514
|
|
515
|
+
<sup>\[[back to top](#usage)\]</sup>
|
516
|
+
|
517
|
+
Returns an information about the required lock queue by the lock name. The result
|
518
|
+
represnts the ordered lock request queue that is ordered by score (Redis Sets) and shows
|
519
|
+
lock acquirers and their position in queue. Async nature with redis communcation can lead
|
520
|
+
the situation when the queue becomes empty during the queue data extraction. So sometimes
|
521
|
+
you can receive the lock queue info with empty queue value (an empty array).
|
522
|
+
|
382
523
|
- get the lock queue information;
|
383
524
|
- queue represents the ordered set of lock key reqests:
|
384
525
|
- set is ordered by score in ASC manner (inside the Redis Set);
|
385
526
|
- score is represented as a timestamp when the lock request was made;
|
386
527
|
- represents the acquier identifier and their score as an array of hashes;
|
387
528
|
- returns `nil` if lock queue does not exist;
|
388
|
-
- lock queue data (`Hash<
|
529
|
+
- lock queue data (`Hash<String,String|Array<Hash<String|Numeric>>`):
|
389
530
|
- `"lock_queue"` - `string` - lock queue key in redis;
|
390
531
|
- `"queue"` - `array` - an array of lock requests (array of hashes):
|
391
532
|
- `"acq_id"` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
|
392
533
|
- `"score"` - `float`/`epoch` - time when the lock request was made (epoch);
|
393
534
|
|
394
|
-
```
|
395
|
-
| Returns an information about the required lock queue by the lock name. The result
|
396
|
-
| represnts the ordered lock request queue that is ordered by score (Redis sets) and shows
|
397
|
-
| lock acquirers and their position in queue. Async nature with redis communcation can lead
|
398
|
-
| the situation when the queue becomes empty during the queue data extraction. So sometimes
|
399
|
-
| you can receive the lock queue info with empty queue value (an empty array).
|
400
|
-
```
|
401
|
-
|
402
535
|
```ruby
|
403
536
|
rql.queue_info("your_lock_name")
|
404
537
|
|
@@ -418,6 +551,8 @@ rql.queue_info("your_lock_name")
|
|
418
551
|
|
419
552
|
#### #locked?
|
420
553
|
|
554
|
+
<sup>\[[back to top](#usage)\]</sup>
|
555
|
+
|
421
556
|
- is the lock obtaied or not?
|
422
557
|
|
423
558
|
```ruby
|
@@ -438,26 +573,47 @@ rql.queued?("your_lock_name") # => true/false
|
|
438
573
|
|
439
574
|
#### #unlock - release a lock
|
440
575
|
|
576
|
+
<sup>\[[back to top](#usage)\]</sup>
|
577
|
+
|
441
578
|
- release the concrete lock with lock request queue;
|
442
579
|
- queue will be relased first;
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
- `
|
449
|
-
|
580
|
+
- accepts:
|
581
|
+
- `lock_name` - (required) `[String]` - the lock name that should be released.
|
582
|
+
- `:logger` - (optional) `[::Logger,#debug]`
|
583
|
+
- custom logger object;
|
584
|
+
- pre-configured in `config[:logger]`;
|
585
|
+
- `:instrumenter` - (optional) `[#notify]`
|
586
|
+
- custom instrumenter object;
|
587
|
+
- pre-configured in `config[:instrumetner]`;
|
588
|
+
- `:instrument` - (optional) `[NilClass,Any]`;
|
589
|
+
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
590
|
+
- `nil` by default (no additional data);
|
591
|
+
- if you try to unlock non-existent lock you will receive `ok: true` result with operation timings
|
592
|
+
and `:nothing_to_release` result factor inside;
|
450
593
|
|
451
594
|
Return:
|
452
|
-
- `[Hash<Symbol,Numeric|String
|
595
|
+
- `[Hash<Symbol,Boolean|Hash<Symbol,Numeric|String|Symbol>>]` (`{ ok: true/false, result: Hasn }`);
|
596
|
+
- `:result` format;
|
597
|
+
- `:rel_time` - `Float` - time spent to process redis commands (in seconds);
|
598
|
+
- `:rel_key` - `String` - released lock key (RedisQueudLocks-internal lock key name from Redis);
|
599
|
+
- `:rel_queue` - `String` - released lock queue key (RedisQueuedLocks-internal queue key name from Redis);
|
600
|
+
- `:queue_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required queue does not exist);
|
601
|
+
- `:lock_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required lock does not exist);
|
602
|
+
|
603
|
+
Consider that `lock_res` and `queue_res` can have different value because of the async nature of invoked Redis'es commands.
|
453
604
|
|
454
605
|
```ruby
|
606
|
+
rql.unlock("your_lock_name")
|
607
|
+
|
608
|
+
# =>
|
455
609
|
{
|
456
610
|
ok: true,
|
457
611
|
result: {
|
458
612
|
rel_time: 0.02, # time spent to lock release (in seconds)
|
459
613
|
rel_key: "rql:lock:your_lock_name", # released lock key
|
460
614
|
rel_queue: "rql:lock_queue:your_lock_name" # released lock key queue
|
615
|
+
queue_res: :released, # or :nothing_to_release
|
616
|
+
lock_res: :released # or :nothing_to_release
|
461
617
|
}
|
462
618
|
}
|
463
619
|
```
|
@@ -466,25 +622,38 @@ Return:
|
|
466
622
|
|
467
623
|
#### #clear_locks - release all locks and lock queues
|
468
624
|
|
625
|
+
<sup>\[[back to top](#usage)\]</sup>
|
626
|
+
|
469
627
|
- release all obtained locks and related lock request queues;
|
470
628
|
- queues will be released first;
|
629
|
+
- accepts:
|
630
|
+
- `:batch_size` - (optional) `[Integer]`
|
631
|
+
- the size of batch of locks and lock queus that should be cleared under the one pipelined redis command at once;
|
632
|
+
- pre-configured in `config[:lock_release_batch_size]`;
|
633
|
+
- `:logger` - (optional) `[::Logger,#debug]`
|
634
|
+
- custom logger object;
|
635
|
+
- has a preconfigured value in `config[:logger]`;
|
636
|
+
- `:instrumenter` - (optional) `[#notify]`
|
637
|
+
- custom instrumenter object;
|
638
|
+
- has a preconfigured value in `config[:isntrumenter]`;
|
639
|
+
- `:instrument` - (optional) `[NilClass,Any]`
|
640
|
+
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
471
641
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
- `
|
477
|
-
- the size of batch of locks and lock queus that should be cleared under the one pipelined redis command at once;
|
478
|
-
|
479
|
-
Return:
|
480
|
-
- `[Hash<Symbol,Numeric>]` - Format: `{ ok: true/false, result: Hash<Symbol,Numeric> }`;
|
642
|
+
- returns:
|
643
|
+
- `[Hash<Symbol,Numeric>]` - Format: `{ ok: true, result: Hash<Symbol,Numeric> }`;
|
644
|
+
- result data:
|
645
|
+
- `:rel_time` - `Numeric` - time spent to release all locks and related queus;
|
646
|
+
- `:rel_key_cnt` - `Integer` - the number of released Redis keys (queues+locks);
|
481
647
|
|
482
648
|
```ruby
|
649
|
+
rql.clear_locks
|
650
|
+
|
651
|
+
# =>
|
483
652
|
{
|
484
653
|
ok: true,
|
485
654
|
result: {
|
486
|
-
rel_time: 3.07,
|
487
|
-
rel_key_cnt:
|
655
|
+
rel_time: 3.07,
|
656
|
+
rel_key_cnt: 1234
|
488
657
|
}
|
489
658
|
}
|
490
659
|
```
|
@@ -493,19 +662,56 @@ Return:
|
|
493
662
|
|
494
663
|
#### #extend_lock_ttl
|
495
664
|
|
496
|
-
|
665
|
+
<sup>\[[back to top](#usage)\]</sup>
|
666
|
+
|
667
|
+
- extends lock ttl by the required number of milliseconds;
|
668
|
+
- expects the lock name and the number of milliseconds;
|
669
|
+
- accepts:
|
670
|
+
- `lock_name` - (required) `[String]`
|
671
|
+
- the lock name which ttl should be extended;
|
672
|
+
- `milliseconds` - (required) `[Integer]`
|
673
|
+
- how many milliseconds should be added to the lock's TTL;
|
674
|
+
- `:logger` - (optional) `[::Logger,#debug]`
|
675
|
+
- custom logger object;
|
676
|
+
- pre-configured in `config[:logger]`;
|
677
|
+
- returns `{ ok: true, result: :ttl_extended }` when ttl is extended;
|
678
|
+
- returns `{ ok: false, result: :async_expire_or_no_lock }` when lock not found or lock is expired during
|
679
|
+
some steps of invocation (see **Important** section below);
|
680
|
+
- **Important**:
|
681
|
+
- the method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension;
|
682
|
+
- the method consists of two commands:
|
683
|
+
- (1) read current pttl;
|
684
|
+
- (2) set new ttl that is calculated as "current pttl + additional milliseconds";
|
685
|
+
- what can happen during these steps:
|
686
|
+
- lock is expired between commands or before the first command;
|
687
|
+
- lock is expired before the second command;
|
688
|
+
- lock is expired AND newly acquired by another process (so you will extend the
|
689
|
+
totally new lock with fresh PTTL);
|
690
|
+
- use it at your own risk and consider the async nature when calling this method;
|
691
|
+
|
692
|
+
```ruby
|
693
|
+
rql.extend_lock_ttl("my_lock", 5_000) # NOTE: add 5_000 milliseconds
|
694
|
+
|
695
|
+
# => `ok` case
|
696
|
+
{ ok: true, result: :ttl_extended }
|
697
|
+
|
698
|
+
# => `failed` case
|
699
|
+
{ ok: false, result: :async_expire_or_no_lock }
|
700
|
+
```
|
497
701
|
|
498
702
|
---
|
499
703
|
|
500
704
|
#### #locks - get list of obtained locks
|
501
705
|
|
706
|
+
<sup>\[[back to top](#usage)\]</sup>
|
707
|
+
|
502
708
|
- uses redis `SCAN` under the hood;
|
503
709
|
- accepts:
|
504
710
|
- `:scan_size` - `Integer` - (`config[:key_extraction_batch_size]` by default);
|
505
711
|
- `:with_info` - `Boolean` - `false` by default (for details see [#locks_info](#locks_info---get-list-of-locks-with-their-info));
|
506
712
|
- returns:
|
507
713
|
- `Set<String>` (for `with_info: false`);
|
508
|
-
- `Set<Hash<Symbol,Any>>` (for `with_info: true`). See
|
714
|
+
- `Set<Hash<Symbol,Any>>` (for `with_info: true`). See [#locks_info](#locks_info---get-list-of-locks-with-their-info) for details;
|
509
715
|
|
510
716
|
```ruby
|
511
717
|
rql.locks # or rql.locks(scan_size: 123)
|
@@ -529,13 +735,15 @@ rql.locks # or rql.locks(scan_size: 123)
|
|
529
735
|
|
530
736
|
#### #queues - get list of lock request queues
|
531
737
|
|
738
|
+
<sup>\[[back to top](#usage)\]</sup>
|
739
|
+
|
532
740
|
- uses redis `SCAN` under the hood;
|
533
741
|
- accepts
|
534
742
|
- `:scan_size` - `Integer` - (`config[:key_extraction_batch_size]` by default);
|
535
|
-
- `:with_info` - `Boolean` - `false` by default (for details see [queues_info](#queues_info---get-list-of-queues-with-their-info));
|
743
|
+
- `:with_info` - `Boolean` - `false` by default (for details see [#queues_info](#queues_info---get-list-of-queues-with-their-info));
|
536
744
|
- returns:
|
537
745
|
- `Set<String>` (for `with_info: false`);
|
538
|
-
- `Set<Hash<Symbol,Any>>` (for `with_info: true`). See
|
746
|
+
- `Set<Hash<Symbol,Any>>` (for `with_info: true`). See [#locks_info](#locks_info---get-list-of-locks-with-their-info) for details;
|
539
747
|
|
540
748
|
```ruby
|
541
749
|
rql.queues # or rql.queues(scan_size: 123)
|
@@ -559,6 +767,8 @@ rql.queues # or rql.queues(scan_size: 123)
|
|
559
767
|
|
560
768
|
#### #keys - get list of taken locks and queues
|
561
769
|
|
770
|
+
<sup>\[[back to top](#usage)\]</sup>
|
771
|
+
|
562
772
|
- uses redis `SCAN` under the hood;
|
563
773
|
- accepts:
|
564
774
|
`:scan_size` - `Integer` - (`config[:key_extraction_batch_size]` by default);
|
@@ -590,6 +800,8 @@ rql.keys # or rql.keys(scan_size: 123)
|
|
590
800
|
|
591
801
|
#### #locks_info - get list of locks with their info
|
592
802
|
|
803
|
+
<sup>\[[back to top](#usage)\]</sup>
|
804
|
+
|
593
805
|
- uses redis `SCAN` under the hod;
|
594
806
|
- accepts `scan_size:`/`Integer` option (`config[:key_extraction_batch_size]` by default);
|
595
807
|
- returns `Set<Hash<Symbol,Any>>` (see [#lock_info](#lock_info) and examples below for details).
|
@@ -622,6 +834,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
|
|
622
834
|
|
623
835
|
#### #queues_info - get list of queues with their info
|
624
836
|
|
837
|
+
<sup>\[[back to top](#usage)\]</sup>
|
838
|
+
|
625
839
|
- uses redis `SCAN` under the hod;
|
626
840
|
- accepts `scan_size:`/`Integer` option (`config[:key_extraction_batch_size]` by default);
|
627
841
|
- returns `Set<Hash<Symbol,Any>>` (see [#queue_info](#queue_info) and examples below for details).
|
@@ -645,11 +859,97 @@ rql.queues_info # or rql.qeuues_info(scan_size: 123)
|
|
645
859
|
{"acq_id"=>"rql:acq:38529/4460/4480/4360/66093702f24a3129", "score"=>1711606640.540808}]},
|
646
860
|
...}>
|
647
861
|
```
|
862
|
+
---
|
863
|
+
|
864
|
+
#### #clear_dead_requests
|
865
|
+
|
866
|
+
<sup>\[[back to top](#usage)\]</sup>
|
867
|
+
|
868
|
+
In some cases your lock requests may become "dead". It can happen when your processs
|
869
|
+
that are enqueeud to the lock queue is failed unexpectedly (for some reason) before the lock acquire moment
|
870
|
+
and when no any other process does not need this lock anymore. For this case your lock will be cleared only when any process
|
871
|
+
will try to acquire this lock again (cuz lock acquirement triggers the removement of expired requests).
|
872
|
+
|
873
|
+
In order to help with these dead requests you may periodically call `#clear_dead_requests`
|
874
|
+
with corresponding `dead_ttl` option, that is pre-configured by default via `config[:dead_request_ttl]`.
|
875
|
+
|
876
|
+
An option is required because of it is no any **fast** way to understand which request
|
877
|
+
is dead now and is it really dead cuz each request queue can host their requests with
|
878
|
+
a custom queue ttl for each request differently.
|
879
|
+
|
880
|
+
Accepts:
|
881
|
+
- `:dead_ttl` - (optional) `[Integer]`
|
882
|
+
- lock request ttl after which a lock request is considered dead;
|
883
|
+
- has a preconfigured value in `config[:dead_request_ttl]` (1 day by default);
|
884
|
+
- `:sacn_size` - (optional) `[Integer]`
|
885
|
+
- the batch of scanned keys for Redis'es SCAN command;
|
886
|
+
- has a preconfigured valie in `config[:key_extraction_batch_size]`;
|
887
|
+
- `:logger` - (optional) `[::Logger,#debug]`
|
888
|
+
- custom logger object;
|
889
|
+
- pre-configured in `config[:logger]`;
|
890
|
+
- `:instrumenter` - (optional) `[#notify]`
|
891
|
+
- custom instrumenter object;
|
892
|
+
- pre-configured in `config[:isntrumenter]`;
|
893
|
+
- `:instrument` - (optional) `[NilClass,Any]`
|
894
|
+
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
895
|
+
- `nil` by default (no additional data);
|
896
|
+
|
897
|
+
Returns: `{ ok: true, processed_queues: Set<String> }` returns the list of processed lock queues;
|
898
|
+
|
899
|
+
```ruby
|
900
|
+
rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
|
901
|
+
|
902
|
+
# =>
|
903
|
+
{
|
904
|
+
ok: true,
|
905
|
+
processed_queues: [
|
906
|
+
"rql:lock_queue:some-lock-123",
|
907
|
+
"rql:lock_queue:some-lock-456",
|
908
|
+
"rql:lock_queue:your-other-lock",
|
909
|
+
...
|
910
|
+
]
|
911
|
+
}
|
912
|
+
```
|
913
|
+
|
914
|
+
---
|
915
|
+
|
916
|
+
## Logging
|
917
|
+
|
918
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
919
|
+
|
920
|
+
- default logs (raised from `#lock`/`#lock!`):
|
921
|
+
|
922
|
+
```ruby
|
923
|
+
"[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id");
|
924
|
+
"[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id");
|
925
|
+
"[redis_queued_locks.dead_score_reached__reset_acquier_position]" # (logs "lock_key", "queue_ttl", "acq_id");
|
926
|
+
"[redis_queued_locks.lock_obtained]" # (logs "lockkey", "queue_ttl", "acq_id", "acq_time");
|
927
|
+
"[redis_queued_locks.fail_fast_or_limits_reached__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id");
|
928
|
+
"[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id");
|
929
|
+
```
|
930
|
+
|
931
|
+
- additional logs (raised from `#lock`/`#lock!` with `confg[:log_lock_try] == true`):
|
932
|
+
|
933
|
+
```ruby
|
934
|
+
"[redis_queued_locks.try_lock.start]" # (logs "lock_key", "queue_ttl", "acq_id");
|
935
|
+
"[redis_queued_locks.try_lock.rconn_fetched]" # (logs "lock_key", "queue_ttl", "acq_id");
|
936
|
+
"[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id)";
|
937
|
+
"[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id");
|
938
|
+
"[redis_queued_locks.try_lock.get_first_from_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue");
|
939
|
+
"[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id");
|
940
|
+
"[redis_queued_locks.try_lock.exit__no_first]" # (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "<current_lock_data>");
|
941
|
+
"[redis_queued_locks.try_lock.exit__lock_still_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
|
942
|
+
"[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id");
|
943
|
+
```
|
648
944
|
|
649
945
|
---
|
650
946
|
|
651
947
|
## Instrumentation
|
652
948
|
|
949
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
950
|
+
|
951
|
+
- [Instrumentation Events](#instrumentation-events)
|
952
|
+
|
653
953
|
An instrumentation layer is incapsulated in `instrumenter` object stored in [config](#configuration) (`RedisQueuedLocks::Client#config[:instrumenter]`).
|
654
954
|
|
655
955
|
Instrumenter object should provide `notify(event, payload)` method with the following signarue:
|
@@ -659,8 +959,8 @@ Instrumenter object should provide `notify(event, payload)` method with the foll
|
|
659
959
|
|
660
960
|
`redis_queued_locks` provides two instrumenters:
|
661
961
|
|
662
|
-
- `RedisQueuedLocks::Instrument::ActiveSupport` -
|
663
|
-
that instrument events via
|
962
|
+
- `RedisQueuedLocks::Instrument::ActiveSupport` - **ActiveSupport::Notifications** instrumenter
|
963
|
+
that instrument events via **ActiveSupport::Notifications** API;
|
664
964
|
- `RedisQueuedLocks::Instrument::VoidNotifier` - instrumenter that does nothing;
|
665
965
|
|
666
966
|
By default `RedisQueuedLocks::Client` is configured with the void notifier (which means "instrumentation is disabled").
|
@@ -669,17 +969,20 @@ By default `RedisQueuedLocks::Client` is configured with the void notifier (whic
|
|
669
969
|
|
670
970
|
### Instrumentation Events
|
671
971
|
|
972
|
+
<sup>\[[back to top](#instrumentation-events)\]</sup>
|
973
|
+
|
672
974
|
List of instrumentation events
|
673
975
|
|
674
|
-
- `redis_queued_locks.lock_obtained
|
675
|
-
- `redis_queued_locks.lock_hold_and_release
|
676
|
-
- `redis_queued_locks.explicit_lock_release
|
677
|
-
- `redis_queued_locks.explicit_all_locks_release
|
976
|
+
- `redis_queued_locks.lock_obtained`;
|
977
|
+
- `redis_queued_locks.lock_hold_and_release`;
|
978
|
+
- `redis_queued_locks.explicit_lock_release`;
|
979
|
+
- `redis_queued_locks.explicit_all_locks_release`;
|
678
980
|
|
679
981
|
Detalized event semantics and payload structure:
|
680
982
|
|
681
983
|
- `"redis_queued_locks.lock_obtained"`
|
682
984
|
- a moment when the lock was obtained;
|
985
|
+
- raised from `#lock`/`#lock!`;
|
683
986
|
- payload:
|
684
987
|
- `:ttl` - `integer`/`milliseconds` - lock ttl;
|
685
988
|
- `:acq_id` - `string` - lock acquier identifier;
|
@@ -687,9 +990,10 @@ Detalized event semantics and payload structure:
|
|
687
990
|
- `:ts` - `integer`/`epoch` - the time when the lock was obtaiend;
|
688
991
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
689
992
|
- `:instrument` - `nil`/`Any` - custom data passed to the `lock`/`lock!` method as `:instrument` attribute;
|
993
|
+
|
690
994
|
- `"redis_queued_locks.lock_hold_and_release"`
|
691
|
-
- an event signalizes about the "hold+and+release" process
|
692
|
-
|
995
|
+
- an event signalizes about the "hold+and+release" process is finished;
|
996
|
+
- raised from `#lock`/`#lock!` when invoked with a block of code;
|
693
997
|
- payload:
|
694
998
|
- `:hold_time` - `float`/`milliseconds` - lock hold time;
|
695
999
|
- `:ttl` - `integer`/`milliseconds` - lock ttl;
|
@@ -698,26 +1002,32 @@ Detalized event semantics and payload structure:
|
|
698
1002
|
- `:ts` - `integer`/`epoch` - the time when lock was obtained;
|
699
1003
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
700
1004
|
- `:instrument` - `nil`/`Any` - custom data passed to the `lock`/`lock!` method as `:instrument` attribute;
|
1005
|
+
|
701
1006
|
- `"redis_queued_locks.explicit_lock_release"`
|
702
1007
|
- an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
|
1008
|
+
- raised from `#unlock`;
|
703
1009
|
- payload:
|
704
|
-
- `:at` - `
|
1010
|
+
- `:at` - `float`/`epoch` - the time when the lock was released;
|
705
1011
|
- `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
|
706
1012
|
- `:lock_key` - `string` - released lock (lock name);
|
707
1013
|
- `:lock_key_queue` - `string` - released lock queue (lock queue name);
|
1014
|
+
|
708
1015
|
- `"redis_queued_locks.explicit_all_locks_release"`
|
709
1016
|
- an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
|
1017
|
+
- raised from `#clear_locks`;
|
710
1018
|
- payload:
|
711
1019
|
- `:rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
|
712
|
-
- `:at` - `
|
1020
|
+
- `:at` - `float`/`epoch` - the time when the operation has ended;
|
713
1021
|
- `:rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
|
714
1022
|
|
715
1023
|
---
|
716
1024
|
|
717
1025
|
## Roadmap
|
718
1026
|
|
1027
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
1028
|
+
|
719
1029
|
- Semantic Error objects for unexpected Redis errors;
|
720
|
-
-
|
1030
|
+
- better specs with 100% test coverage;
|
721
1031
|
- per-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
|
722
1032
|
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
723
1033
|
whose ttl may expire before the block execution completes). It only makes sense for non-`timed` locks;
|
@@ -726,14 +1036,15 @@ Detalized event semantics and payload structure:
|
|
726
1036
|
- structured logging (separated docs);
|
727
1037
|
- GitHub Actions CI;
|
728
1038
|
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
729
|
-
- better code stylization
|
730
|
-
- dead queue keys cleanup (empty queues);
|
1039
|
+
- better code stylization (+ some refactorings);
|
731
1040
|
- statistics with UI;
|
732
1041
|
|
733
1042
|
---
|
734
1043
|
|
735
1044
|
## Contributing
|
736
1045
|
|
1046
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
1047
|
+
|
737
1048
|
- Fork it ( https://github.com/0exp/redis_queued_locks )
|
738
1049
|
- Create your feature branch (`git checkout -b feature/my-new-feature`)
|
739
1050
|
- Commit your changes (`git commit -am '[feature_context] Add some feature'`)
|
@@ -742,8 +1053,12 @@ Detalized event semantics and payload structure:
|
|
742
1053
|
|
743
1054
|
## License
|
744
1055
|
|
1056
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
1057
|
+
|
745
1058
|
Released under MIT License.
|
746
1059
|
|
747
1060
|
## Authors
|
748
1061
|
|
1062
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
1063
|
+
|
749
1064
|
[Rustam Ibragimov](https://github.com/0exp)
|