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 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