redis_queued_locks 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56120a88eb6ffa4f377c014b8619cdcd227ac0efaab1c6ec6495042c74739a63
4
- data.tar.gz: d1e7240b5ca0c8a18842cd609b1e9280813a0a51a62d2ed9bcacdb66d707801f
3
+ metadata.gz: 4846c7a27c9ce2108903a9b9b9250a38aa18ae34be6da85b4055bfe9c20371bb
4
+ data.tar.gz: 0346d8a0c4d43490ea0fcf79c7048e70c480285b18f26f84b06ad24fe42dc9fc
5
5
  SHA512:
6
- metadata.gz: f3df6ebf409fa4d97ebe51632832d6c9c64eb03a4102d68e6951def7a3d351c62e86e63014b8cc1d5c6ea4c5cd6bf9bdf5595ebdb2a3334550e7a27f2f6b0bcd
7
- data.tar.gz: 6e512298fb8be556e5370574e87529b936670bec1b13250233c6ce08d9feb4c8032b1b07e90a514ff4d4687e01391424be5b546a124119c7937492baa7ef87ee
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
- - [Todo](#todo)
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
- ## Todo
429
+ ## Roadmap
345
430
 
346
- - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
347
- - `100%` test coverage;
348
- - better code stylization and interesting refactorings;
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('HSET', lock_key, 'acq_id', acquier_id, 'ts', (timestamp = Time.now.to_i))
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
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.7
9
- VERSION = '0.0.7'
8
+ # @version 0.0.8
9
+ VERSION = '0.0.8'
10
10
  end
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.7
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-26 00:00:00.000000000 Z
11
+ date: 2024-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client