redis_queued_locks 0.0.7 → 0.0.8
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 +7 -0
- data/README.md +93 -5
- data/lib/redis_queued_locks/acquier/try.rb +7 -1
- data/lib/redis_queued_locks/acquier.rb +115 -0
- data/lib/redis_queued_locks/client.rb +38 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4846c7a27c9ce2108903a9b9b9250a38aa18ae34be6da85b4055bfe9c20371bb
|
4
|
+
data.tar.gz: 0346d8a0c4d43490ea0fcf79c7048e70c480285b18f26f84b06ad24fe42dc9fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbf5bd49d6c3b912ff8e3b8061cae4e2b1eaea00f913c8f217058168e7008f3c4daaeef0888ca22bce22d318742db1890c8524293e20734b4dea506fc3eeae10
|
7
|
+
data.tar.gz: 744519a43e3f1eb98527cbcb5495700b16a6ea94de95732baf8027debc14df7e5fa0394f804a9360383eb9168d3975794b46c4a0a6c45d1b769fd0c94e1700b5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.8] - 2024-02-27
|
4
|
+
### Added
|
5
|
+
- `RedisQueuedLock::Client#locked?`
|
6
|
+
- `RedisQueuedLock::Client#queued?`
|
7
|
+
- `RedisQueuedLock::Client#lock_info`
|
8
|
+
- `RedisQueuedLock::Client#queue_info`
|
9
|
+
|
3
10
|
## [0.0.7] - 2024-02-27
|
4
11
|
### Changed
|
5
12
|
- Minor documentation updates;
|
data/README.md
CHANGED
@@ -15,11 +15,15 @@ Each lock request is put into a request queue and processed in order of their pr
|
|
15
15
|
- [Usage](#usage)
|
16
16
|
- [lock](#lock---obtain-a-lock)
|
17
17
|
- [lock!](#lock---exeptional-lock-obtaining)
|
18
|
+
- [lock_info](#lock_info)
|
19
|
+
- [queue_info](#queue_info)
|
20
|
+
- [locked?](#locked)
|
21
|
+
- [queued?](#queued)
|
18
22
|
- [unlock](#unlock---release-a-lock)
|
19
23
|
- [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
|
20
24
|
- [Instrumentation](#instrumentation)
|
21
25
|
- [Instrumentation Events](#instrumentation-events)
|
22
|
-
- [
|
26
|
+
- [Roadmap](#roadmap)
|
23
27
|
- [Contributing](#contributing)
|
24
28
|
- [License](#license)
|
25
29
|
- [Authors](#authors)
|
@@ -124,6 +128,10 @@ end
|
|
124
128
|
|
125
129
|
- [lock](#lock---obtain-a-lock)
|
126
130
|
- [lock!](#lock---exeptional-lock-obtaining)
|
131
|
+
- [lock_info](#lock_info)
|
132
|
+
- [queue_info](#queue_info)
|
133
|
+
- [locked?](#locked)
|
134
|
+
- [queued?](#queued)
|
127
135
|
- [unlock](#unlock---release-a-lock)
|
128
136
|
- [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
|
129
137
|
|
@@ -222,6 +230,83 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
|
|
222
230
|
|
223
231
|
---
|
224
232
|
|
233
|
+
#### #lock_info
|
234
|
+
|
235
|
+
- get the lock information;
|
236
|
+
- returns `nil` if lock does not exist;
|
237
|
+
- lock data (`Hash<Symbol,String|Integer>`):
|
238
|
+
- `lock_key` - `string` - lock key in redis;
|
239
|
+
- `acq_id` - `string` - acquier identifier (process_id/thread_id by default);
|
240
|
+
- `ts` - `integer`/`epoch` - the time lock was obtained;
|
241
|
+
- `init_ttl` - `integer` - (milliseconds) initial lock key ttl;
|
242
|
+
- `rem_ttl` - `integer` - (milliseconds) remaining lock key ttl;
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
rql.lock_info("your_lock_name")
|
246
|
+
|
247
|
+
# =>
|
248
|
+
{
|
249
|
+
lock_key: "rql:lock:your_lock_name",
|
250
|
+
acq_id: "rql:acq:123/456",
|
251
|
+
ts: 123456789,
|
252
|
+
ini_ttl: 123456789,
|
253
|
+
rem_ttl: 123456789
|
254
|
+
}
|
255
|
+
```
|
256
|
+
|
257
|
+
---
|
258
|
+
|
259
|
+
#### #queue_info
|
260
|
+
|
261
|
+
- get the lock queue information;
|
262
|
+
- queue represents the ordered set of lock key reqests:
|
263
|
+
- set is ordered by score in ASC manner (inside the Redis Set);
|
264
|
+
- score is represented as a timestamp when the lock request was made;
|
265
|
+
- represents the acquier identifier and their score as an array of hashes;
|
266
|
+
- returns `nil` if lock queue does not exist;
|
267
|
+
- lock queue data (`Hash<Symbol,String|Array<Hash<Symbol,String|Numeric>>`):
|
268
|
+
- `lock_queue` - `string` - lock queue key in redis;
|
269
|
+
- `queue` - `array` - an array of lock requests (array of hashes):
|
270
|
+
- `acq_id` - `string` - acquier identifier (process_id/thread_id by default);
|
271
|
+
- `score` - `float`/`epoch` - time when the lock request was made (epoch);
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
rql.queue_info("your_lock_name")
|
275
|
+
|
276
|
+
# =>
|
277
|
+
{
|
278
|
+
lock_queue: "rql:lock_queue:your_lock_name",
|
279
|
+
queue: [
|
280
|
+
{ acq_id: "rql:acq:123/456", score: 1},
|
281
|
+
{ acq_id: "rql:acq:123/567", score: 2},
|
282
|
+
{ acq_id: "rql:acq:555/329", score: 3},
|
283
|
+
# ...etc
|
284
|
+
]
|
285
|
+
}
|
286
|
+
```
|
287
|
+
|
288
|
+
---
|
289
|
+
|
290
|
+
#### #locked?
|
291
|
+
|
292
|
+
- is the lock obtaied or not?
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
rql.locked?("your_lock_name") # => true/false
|
296
|
+
```
|
297
|
+
|
298
|
+
---
|
299
|
+
|
300
|
+
#### #queued?
|
301
|
+
|
302
|
+
- is the lock queued for obtain / has requests for obtain?
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
rql.queued?("your_lock_name") # => true/false
|
306
|
+
```
|
307
|
+
|
308
|
+
---
|
309
|
+
|
225
310
|
#### #unlock - release a lock
|
226
311
|
|
227
312
|
- release the concrete lock with lock request queue;
|
@@ -341,11 +426,14 @@ Detalized event semantics and payload structure:
|
|
341
426
|
|
342
427
|
---
|
343
428
|
|
344
|
-
##
|
429
|
+
## Roadmap
|
345
430
|
|
346
|
-
-
|
347
|
-
-
|
348
|
-
-
|
431
|
+
- **Major**
|
432
|
+
- Semantic Error objects for unexpected Redis errors;
|
433
|
+
- `100%` test coverage;
|
434
|
+
- **Minor**
|
435
|
+
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
436
|
+
- better code stylization and interesting refactorings;
|
349
437
|
|
350
438
|
---
|
351
439
|
|
@@ -95,7 +95,13 @@ module RedisQueuedLocks::Acquier::Try
|
|
95
95
|
)
|
96
96
|
|
97
97
|
# Step 6.2: acquire a lock and store an info about the acquier
|
98
|
-
transact.call(
|
98
|
+
transact.call(
|
99
|
+
'HSET',
|
100
|
+
lock_key,
|
101
|
+
'acq_id', acquier_id,
|
102
|
+
'ts', (timestamp = Time.now.to_i),
|
103
|
+
'ini_ttl', ttl
|
104
|
+
)
|
99
105
|
|
100
106
|
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
101
107
|
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in seconds
|
@@ -271,6 +271,121 @@ module RedisQueuedLocks::Acquier
|
|
271
271
|
{ ok: true, result: { rel_key_cnt: result[:rel_keys], rel_time: rel_time } }
|
272
272
|
end
|
273
273
|
|
274
|
+
# @param redis_client [RedisClient]
|
275
|
+
# @param lock_name [String]
|
276
|
+
# @return [Boolean]
|
277
|
+
#
|
278
|
+
# @api private
|
279
|
+
# @since 0.1.0
|
280
|
+
def locked?(redis_client, lock_name)
|
281
|
+
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
282
|
+
redis_client.call('EXISTS', lock_key) == 1
|
283
|
+
end
|
284
|
+
|
285
|
+
# @param redis_client [RedisClient]
|
286
|
+
# @param lock_name [String]
|
287
|
+
# @return [Boolean]
|
288
|
+
#
|
289
|
+
# @api private
|
290
|
+
# @since 0.1.0
|
291
|
+
def queued?(redis_client, lock_name)
|
292
|
+
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
293
|
+
redis_client.call('EXISTS', lock_key_queue) == 1
|
294
|
+
end
|
295
|
+
|
296
|
+
# @param redis_client [RedisClient]
|
297
|
+
# @param lock_name [String]
|
298
|
+
# @return [Hash<Symbol,String|Numeric>,NilClass]
|
299
|
+
# - `nil` is returned when lock key does not exist or expired;
|
300
|
+
# - result format: {
|
301
|
+
# lock_key: "rql:lock:your_lockname", # acquired lock key
|
302
|
+
# acq_id: "rql:acq:process_id/thread_id", # lock acquier identifier
|
303
|
+
# ts: 123456789, # <locked at> time stamp (epoch)
|
304
|
+
# ini_ttl: 123456789, # initial lock key ttl (milliseconds),
|
305
|
+
# rem_ttl: 123456789, # remaining lock key ttl (milliseconds)
|
306
|
+
# }
|
307
|
+
#
|
308
|
+
# @api private
|
309
|
+
# @since 0.1.0
|
310
|
+
def lock_info(redis_client, lock_name)
|
311
|
+
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
312
|
+
|
313
|
+
result = redis_client.multi(watch: [lock_key]) do |transact|
|
314
|
+
transact.call('HGETALL', lock_key)
|
315
|
+
transact.call('PTTL', lock_key)
|
316
|
+
end
|
317
|
+
|
318
|
+
if result == nil
|
319
|
+
# NOTE:
|
320
|
+
# - nil result means that during transaction invocation the lock is changed (CAS):
|
321
|
+
# - lock is expired;
|
322
|
+
# - lock is released;
|
323
|
+
# - lock is expired + re-obtained;
|
324
|
+
nil
|
325
|
+
else
|
326
|
+
hget_cmd_res = result[0]
|
327
|
+
pttl_cmd_res = result[1]
|
328
|
+
|
329
|
+
if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
|
330
|
+
nil
|
331
|
+
else
|
332
|
+
# NOTE: the result of MULTI-command is an array of results of each internal command
|
333
|
+
# - result[0] (HGETALL) (Hash<String,String>)
|
334
|
+
# - result[1] (PTTL) (Integer)
|
335
|
+
{
|
336
|
+
lock_key: lock_key,
|
337
|
+
acq_id: hget_cmd_res['acq_id'],
|
338
|
+
ts: Integer(hget_cmd_res['ts']),
|
339
|
+
ini_ttl: Integer(hget_cmd_res['ini_ttl']),
|
340
|
+
rem_ttl: ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
|
341
|
+
}
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns an information about the required lock queue by the lock name. The result
|
347
|
+
# represnts the ordered lock request queue that is ordered by score (Redis sets) and shows
|
348
|
+
# lock acquirers and their position in queue. Async nature with redis communcation can lead
|
349
|
+
# the sitation when the queue becomes empty during the queue data extraction. So sometimes
|
350
|
+
# you can receive the lock queue info with empty queue.
|
351
|
+
#
|
352
|
+
# @param redis_client [RedisClient]
|
353
|
+
# @param lock_name [String]
|
354
|
+
# @return [Hash<Symbol,String|Array<Hash<Symbol,String|Float>>,NilClass]
|
355
|
+
# - `nil` is returned when lock queue does not exist;
|
356
|
+
# - result format: {
|
357
|
+
# lock_queue: "rql:lock_queue:your_lock_name", # lock queue key in redis,
|
358
|
+
# queue: [
|
359
|
+
# { acq_id: "rql:acq:process_id/thread_id", score: 123 },
|
360
|
+
# { acq_id: "rql:acq:process_id/thread_id", score: 456 },
|
361
|
+
# ] # ordered set (by score) with information about an acquier and their position in queue
|
362
|
+
# }
|
363
|
+
#
|
364
|
+
# @api private
|
365
|
+
# @since 0.1.0
|
366
|
+
def queue_info(redis_client, lock_name)
|
367
|
+
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
368
|
+
|
369
|
+
result = redis_client.pipelined do |pipeline|
|
370
|
+
pipeline.call('EXISTS', lock_key_queue)
|
371
|
+
pipeline.call('ZRANGE', lock_key_queue, '0', '-1', 'WITHSCORES')
|
372
|
+
end
|
373
|
+
|
374
|
+
exists_cmd_res = result[0]
|
375
|
+
zrange_cmd_res = result[1]
|
376
|
+
|
377
|
+
if exists_cmd_res == 1
|
378
|
+
# NOTE: queue existed during the piepline invocation
|
379
|
+
{
|
380
|
+
lock_queue: lock_key_queue,
|
381
|
+
queue: zrange_cmd_res.map { |val| { acq_id: val[0], score: val[1] } }
|
382
|
+
}
|
383
|
+
else
|
384
|
+
# NOTE: queue did not exist during the pipeline invocation
|
385
|
+
nil
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
274
389
|
private
|
275
390
|
|
276
391
|
# @param timeout [NilClass,Integer]
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# @api public
|
4
4
|
# @since 0.1.0
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
5
6
|
class RedisQueuedLocks::Client
|
6
7
|
# @since 0.1.0
|
7
8
|
include Qonfig::Configurable
|
@@ -154,6 +155,42 @@ class RedisQueuedLocks::Client
|
|
154
155
|
)
|
155
156
|
end
|
156
157
|
|
158
|
+
# @param lock_name [String]
|
159
|
+
# @return [Boolean]
|
160
|
+
#
|
161
|
+
# @api public
|
162
|
+
# @since 0.1.0
|
163
|
+
def locked?(lock_name)
|
164
|
+
RedisQueuedLocks::Acquier.locked?(redis_client, lock_name)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param lock_name [String]
|
168
|
+
# @return [Boolean]
|
169
|
+
#
|
170
|
+
# @api public
|
171
|
+
# @since 0.1.0
|
172
|
+
def queued?(lock_name)
|
173
|
+
RedisQueuedLocks::Acquier.queued?(redis_client, lock_name)
|
174
|
+
end
|
175
|
+
|
176
|
+
# @param lock_name [String]
|
177
|
+
# @return [Hash,NilClass]
|
178
|
+
#
|
179
|
+
# @api public
|
180
|
+
# @since 0.1.0
|
181
|
+
def lock_info(lock_name)
|
182
|
+
RedisQueuedLocks::Acquier.lock_info(redis_client, lock_name)
|
183
|
+
end
|
184
|
+
|
185
|
+
# @param lock_name [String]
|
186
|
+
# @return [Hash,NilClass]
|
187
|
+
#
|
188
|
+
# @api public
|
189
|
+
# @since 0.1.0
|
190
|
+
def queue_info(lock_name)
|
191
|
+
RedisQueuedLocks::Acquier.queue_info(redis_client, lock_name)
|
192
|
+
end
|
193
|
+
|
157
194
|
# @option batch_size [Integer]
|
158
195
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol/Hash }.
|
159
196
|
#
|
@@ -167,3 +204,4 @@ class RedisQueuedLocks::Client
|
|
167
204
|
)
|
168
205
|
end
|
169
206
|
end
|
207
|
+
# 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.8
|
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-02-
|
11
|
+
date: 2024-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|