redis_queued_locks 0.0.7 → 0.0.9

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: 463b0613174c7a3806aef19ce962bbe5ed91d342796bec84ea47b2aa79cf61fb
4
+ data.tar.gz: 4d483db40896d77049e3e6d324a206606225c9c8e8b592072b1f501563fb7ec8
5
5
  SHA512:
6
- metadata.gz: f3df6ebf409fa4d97ebe51632832d6c9c64eb03a4102d68e6951def7a3d351c62e86e63014b8cc1d5c6ea4c5cd6bf9bdf5595ebdb2a3334550e7a27f2f6b0bcd
7
- data.tar.gz: 6e512298fb8be556e5370574e87529b936670bec1b13250233c6ce08d9feb4c8032b1b07e90a514ff4d4687e01391424be5b546a124119c7937492baa7ef87ee
6
+ metadata.gz: f829f8afc264f8a44d0edd1469c5ada76c7257f15ef6945c3f154fe2908b29d5ace4ce0c11365a30af1cdbb6d3b4aeec30227b84500da65b720f22247ed96053
7
+ data.tar.gz: 1eee0e4c2a665b57ee6d2c2c2d6488c065bbcdd586cfe06f2890a6b412a3a009675dd26e39383f2fad54434288d4c3c7660fba2faaa3013da98ab27c484b08b0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.9] - 2024-02-27
4
+ ### Changed
5
+ - The lock acquier identifier (`acq_id`) now includes the fiber id, the ractor id and an unique per-process
6
+ 10 byte string. It is added in order to prevent collisions between different processes/pods
7
+ that will have the same procjet id / thread id identifiers (cuz it is an object_id integers) that can lead
8
+ to the same position with the same `acq_id` for different processes/pods in the lock request queue.
9
+
10
+ ## [0.0.8] - 2024-02-27
11
+ ### Added
12
+ - `RedisQueuedLock::Client#locked?`
13
+ - `RedisQueuedLock::Client#queued?`
14
+ - `RedisQueuedLock::Client#lock_info`
15
+ - `RedisQueuedLock::Client#queue_info`
16
+
3
17
  ## [0.0.7] - 2024-02-27
4
18
  ### Changed
5
19
  - 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)
@@ -56,7 +60,7 @@ require 'redis_queued_locks'
56
60
  require 'redis_queued_locks'
57
61
 
58
62
  # Step 1: initialize RedisClient instance
59
- redis_clinet = RedisClient.config.new_pool # NOTE: provide your ounw RedisClient instance
63
+ redis_clinet = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance
60
64
 
61
65
  # Step 2: initialize RedisQueuedLock::Client instance
62
66
  rq_lock_client = RedisQueuedLocks::Client.new(redis_client) do |config|
@@ -115,6 +119,13 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
115
119
  # - payload: <hash> requried;
116
120
  # - disabled by default via VoidNotifier;
117
121
  config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport
122
+
123
+ # (default: -> { RedisQueuedLocks::Resource.calc_uniq_identity })
124
+ # - uniqude idenfitier that is uniq per process/pod;
125
+ # - prevents potential lock-acquirement collisions bettween different process/pods
126
+ # that have identical process_id/thread_id/fiber_id/ractor_id (identivcal acquier ids);
127
+ # - it is calculated once per `RedisQueudLocks::Client` instance;
128
+ config.uniq_identifier = -> { RedisQueuedLocks::Resource.calc_uniq_identity }
118
129
  end
119
130
  ```
120
131
 
@@ -124,6 +135,10 @@ end
124
135
 
125
136
  - [lock](#lock---obtain-a-lock)
126
137
  - [lock!](#lock---exeptional-lock-obtaining)
138
+ - [lock_info](#lock_info)
139
+ - [queue_info](#queue_info)
140
+ - [locked?](#locked)
141
+ - [queued?](#queued)
127
142
  - [unlock](#unlock---release-a-lock)
128
143
  - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
129
144
 
@@ -134,8 +149,6 @@ end
134
149
  ```ruby
135
150
  def lock(
136
151
  lock_name,
137
- process_id: RedisQueuedLocks::Resource.get_process_id,
138
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
139
152
  ttl: config[:default_lock_ttl],
140
153
  queue_ttl: config[:default_queue_ttl],
141
154
  timeout: config[:try_to_lock_timeout],
@@ -143,16 +156,13 @@ def lock(
143
156
  retry_delay: config[:retry_delay],
144
157
  retry_jitter: config[:retry_jitter],
145
158
  raise_errors: false,
159
+ identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
146
160
  &block
147
161
  )
148
162
  ```
149
163
 
150
164
  - `lock_name` - `[String]`
151
165
  - Lock name to be obtained.
152
- - `process_id` - `[Integer,String]`
153
- - The process that want to acquire the lock.
154
- - `thread_id` - `[Integer,String]`
155
- - The process's thread that want to acquire the lock.
156
166
  - `ttl` [Integer]
157
167
  - Lock's time to live (in milliseconds).
158
168
  - `queue_ttl` - `[Integer]`
@@ -169,6 +179,12 @@ def lock(
169
179
  - See RedisQueuedLocks::Instrument::ActiveSupport for example.
170
180
  - `raise_errors` - `[Boolean]`
171
181
  - Raise errors on library-related limits such as timeout or retry count limit.
182
+ - `identity` - `[String]`
183
+ - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
184
+ collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
185
+ pods or/and nodes of your application;
186
+ - It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
187
+ ivar (accessed via `uniq_dentity` accessor method);
172
188
  - `block` - `[Block]`
173
189
  - A block of code that should be executed after the successfully acquired lock.
174
190
  - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
@@ -206,14 +222,13 @@ Return value:
206
222
  ```ruby
207
223
  def lock!(
208
224
  lock_name,
209
- process_id: RedisQueuedLocks::Resource.get_process_id,
210
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
211
225
  ttl: config[:default_lock_ttl],
212
226
  queue_ttl: config[:default_queue_ttl],
213
227
  timeout: config[:default_timeout],
214
228
  retry_count: config[:retry_count],
215
229
  retry_delay: config[:retry_delay],
216
230
  retry_jitter: config[:retry_jitter],
231
+ identity: uniq_identity,
217
232
  &block
218
233
  )
219
234
  ```
@@ -222,6 +237,83 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
222
237
 
223
238
  ---
224
239
 
240
+ #### #lock_info
241
+
242
+ - get the lock information;
243
+ - returns `nil` if lock does not exist;
244
+ - lock data (`Hash<Symbol,String|Integer>`):
245
+ - `lock_key` - `string` - lock key in redis;
246
+ - `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
247
+ - `ts` - `integer`/`epoch` - the time lock was obtained;
248
+ - `init_ttl` - `integer` - (milliseconds) initial lock key ttl;
249
+ - `rem_ttl` - `integer` - (milliseconds) remaining lock key ttl;
250
+
251
+ ```ruby
252
+ rql.lock_info("your_lock_name")
253
+
254
+ # =>
255
+ {
256
+ lock_key: "rql:lock:your_lock_name",
257
+ acq_id: "rql:acq:123/456",
258
+ ts: 123456789,
259
+ ini_ttl: 123456789,
260
+ rem_ttl: 123456789
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ #### #queue_info
267
+
268
+ - get the lock queue information;
269
+ - queue represents the ordered set of lock key reqests:
270
+ - set is ordered by score in ASC manner (inside the Redis Set);
271
+ - score is represented as a timestamp when the lock request was made;
272
+ - represents the acquier identifier and their score as an array of hashes;
273
+ - returns `nil` if lock queue does not exist;
274
+ - lock queue data (`Hash<Symbol,String|Array<Hash<Symbol,String|Numeric>>`):
275
+ - `lock_queue` - `string` - lock queue key in redis;
276
+ - `queue` - `array` - an array of lock requests (array of hashes):
277
+ - `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
278
+ - `score` - `float`/`epoch` - time when the lock request was made (epoch);
279
+
280
+ ```ruby
281
+ rql.queue_info("your_lock_name")
282
+
283
+ # =>
284
+ {
285
+ lock_queue: "rql:lock_queue:your_lock_name",
286
+ queue: [
287
+ { acq_id: "rql:acq:123/456/567/678/fa76df9cc2", score: 1},
288
+ { acq_id: "rql:acq:123/567/456/679/c7bfcaf4f9", score: 2},
289
+ { acq_id: "rql:acq:555/329/523/127/7329553b11", score: 3},
290
+ # ...etc
291
+ ]
292
+ }
293
+ ```
294
+
295
+ ---
296
+
297
+ #### #locked?
298
+
299
+ - is the lock obtaied or not?
300
+
301
+ ```ruby
302
+ rql.locked?("your_lock_name") # => true/false
303
+ ```
304
+
305
+ ---
306
+
307
+ #### #queued?
308
+
309
+ - is the lock queued for obtain / has requests for obtain?
310
+
311
+ ```ruby
312
+ rql.queued?("your_lock_name") # => true/false
313
+ ```
314
+
315
+ ---
316
+
225
317
  #### #unlock - release a lock
226
318
 
227
319
  - release the concrete lock with lock request queue;
@@ -341,11 +433,14 @@ Detalized event semantics and payload structure:
341
433
 
342
434
  ---
343
435
 
344
- ## Todo
436
+ ## Roadmap
345
437
 
346
- - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
347
- - `100%` test coverage;
348
- - better code stylization and interesting refactorings;
438
+ - **Major**
439
+ - Semantic Error objects for unexpected Redis errors;
440
+ - `100%` test coverage;
441
+ - **Minor**
442
+ - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
443
+ - better code stylization and interesting refactorings;
349
444
 
350
445
  ---
351
446
 
@@ -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
@@ -34,9 +34,13 @@ module RedisQueuedLocks::Acquier
34
34
  # @param lock_name [String]
35
35
  # Lock name to be acquier.
36
36
  # @option process_id [Integer,String]
37
- # The process that want to acquire the lock.
37
+ # The process that want to acquire a lock.
38
38
  # @option thread_id [Integer,String]
39
- # The process's thread that want to acquire the lock.
39
+ # The process's thread that want to acquire a lock.
40
+ # @option fiber_id [Integer,String]
41
+ # A current fiber that want to acquire a lock.
42
+ # @option ractor_id [Integer,String]
43
+ # The current ractor that want to acquire a lock.
40
44
  # @option ttl [Integer,NilClass]
41
45
  # Lock's time to live (in milliseconds). Nil means "without timeout".
42
46
  # @option queue_ttl [Integer]
@@ -53,6 +57,10 @@ module RedisQueuedLocks::Acquier
53
57
  # Raise errors on exceptional cases.
54
58
  # @option instrumenter [#notify]
55
59
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
60
+ # @option identity [String]
61
+ # Unique acquire identifier that is also should be unique between processes and pods
62
+ # on different machines. By default the uniq identity string is
63
+ # represented as 10 bytes hexstr.
56
64
  # @param [Block]
57
65
  # A block of code that should be executed after the successfully acquired lock.
58
66
  # @return [Hash<Symbol,Any>]
@@ -66,6 +74,8 @@ module RedisQueuedLocks::Acquier
66
74
  lock_name,
67
75
  process_id:,
68
76
  thread_id:,
77
+ fiber_id:,
78
+ ractor_id:,
69
79
  ttl:,
70
80
  queue_ttl:,
71
81
  timeout:,
@@ -74,10 +84,17 @@ module RedisQueuedLocks::Acquier
74
84
  retry_jitter:,
75
85
  raise_errors:,
76
86
  instrumenter:,
87
+ identity:,
77
88
  &block
78
89
  )
79
90
  # Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
80
- acquier_id = RedisQueuedLocks::Resource.acquier_identifier(process_id, thread_id)
91
+ acquier_id = RedisQueuedLocks::Resource.acquier_identifier(
92
+ process_id,
93
+ thread_id,
94
+ fiber_id,
95
+ ractor_id,
96
+ identity
97
+ )
81
98
  lock_ttl = ttl + REDIS_EXPIRATION_DEVIATION
82
99
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
83
100
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
@@ -271,6 +288,121 @@ module RedisQueuedLocks::Acquier
271
288
  { ok: true, result: { rel_key_cnt: result[:rel_keys], rel_time: rel_time } }
272
289
  end
273
290
 
291
+ # @param redis_client [RedisClient]
292
+ # @param lock_name [String]
293
+ # @return [Boolean]
294
+ #
295
+ # @api private
296
+ # @since 0.1.0
297
+ def locked?(redis_client, lock_name)
298
+ lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
299
+ redis_client.call('EXISTS', lock_key) == 1
300
+ end
301
+
302
+ # @param redis_client [RedisClient]
303
+ # @param lock_name [String]
304
+ # @return [Boolean]
305
+ #
306
+ # @api private
307
+ # @since 0.1.0
308
+ def queued?(redis_client, lock_name)
309
+ lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
310
+ redis_client.call('EXISTS', lock_key_queue) == 1
311
+ end
312
+
313
+ # @param redis_client [RedisClient]
314
+ # @param lock_name [String]
315
+ # @return [Hash<Symbol,String|Numeric>,NilClass]
316
+ # - `nil` is returned when lock key does not exist or expired;
317
+ # - result format: {
318
+ # lock_key: "rql:lock:your_lockname", # acquired lock key
319
+ # acq_id: "rql:acq:process_id/thread_id", # lock acquier identifier
320
+ # ts: 123456789, # <locked at> time stamp (epoch)
321
+ # ini_ttl: 123456789, # initial lock key ttl (milliseconds),
322
+ # rem_ttl: 123456789, # remaining lock key ttl (milliseconds)
323
+ # }
324
+ #
325
+ # @api private
326
+ # @since 0.1.0
327
+ def lock_info(redis_client, lock_name)
328
+ lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
329
+
330
+ result = redis_client.multi(watch: [lock_key]) do |transact|
331
+ transact.call('HGETALL', lock_key)
332
+ transact.call('PTTL', lock_key)
333
+ end
334
+
335
+ if result == nil
336
+ # NOTE:
337
+ # - nil result means that during transaction invocation the lock is changed (CAS):
338
+ # - lock is expired;
339
+ # - lock is released;
340
+ # - lock is expired + re-obtained;
341
+ nil
342
+ else
343
+ hget_cmd_res = result[0]
344
+ pttl_cmd_res = result[1]
345
+
346
+ if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
347
+ nil
348
+ else
349
+ # NOTE: the result of MULTI-command is an array of results of each internal command
350
+ # - result[0] (HGETALL) (Hash<String,String>)
351
+ # - result[1] (PTTL) (Integer)
352
+ {
353
+ lock_key: lock_key,
354
+ acq_id: hget_cmd_res['acq_id'],
355
+ ts: Integer(hget_cmd_res['ts']),
356
+ ini_ttl: Integer(hget_cmd_res['ini_ttl']),
357
+ rem_ttl: ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
358
+ }
359
+ end
360
+ end
361
+ end
362
+
363
+ # Returns an information about the required lock queue by the lock name. The result
364
+ # represnts the ordered lock request queue that is ordered by score (Redis sets) and shows
365
+ # lock acquirers and their position in queue. Async nature with redis communcation can lead
366
+ # the sitation when the queue becomes empty during the queue data extraction. So sometimes
367
+ # you can receive the lock queue info with empty queue.
368
+ #
369
+ # @param redis_client [RedisClient]
370
+ # @param lock_name [String]
371
+ # @return [Hash<Symbol,String|Array<Hash<Symbol,String|Float>>,NilClass]
372
+ # - `nil` is returned when lock queue does not exist;
373
+ # - result format: {
374
+ # lock_queue: "rql:lock_queue:your_lock_name", # lock queue key in redis,
375
+ # queue: [
376
+ # { acq_id: "rql:acq:process_id/thread_id", score: 123 },
377
+ # { acq_id: "rql:acq:process_id/thread_id", score: 456 },
378
+ # ] # ordered set (by score) with information about an acquier and their position in queue
379
+ # }
380
+ #
381
+ # @api private
382
+ # @since 0.1.0
383
+ def queue_info(redis_client, lock_name)
384
+ lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
385
+
386
+ result = redis_client.pipelined do |pipeline|
387
+ pipeline.call('EXISTS', lock_key_queue)
388
+ pipeline.call('ZRANGE', lock_key_queue, '0', '-1', 'WITHSCORES')
389
+ end
390
+
391
+ exists_cmd_res = result[0]
392
+ zrange_cmd_res = result[1]
393
+
394
+ if exists_cmd_res == 1
395
+ # NOTE: queue existed during the piepline invocation
396
+ {
397
+ lock_queue: lock_key_queue,
398
+ queue: zrange_cmd_res.map { |val| { acq_id: val[0], score: val[1] } }
399
+ }
400
+ else
401
+ # NOTE: queue did not exist during the pipeline invocation
402
+ nil
403
+ end
404
+ end
405
+
274
406
  private
275
407
 
276
408
  # @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
@@ -16,6 +17,7 @@ class RedisQueuedLocks::Client
16
17
  setting :default_queue_ttl, 15 # NOTE: seconds
17
18
  setting :lock_release_batch_size, 100
18
19
  setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
20
+ setting :uniq_identifier, -> { RedisQueuedLocks::Resource.calc_uniq_identity }
19
21
 
20
22
  # TODO: setting :logger, Logger.new(IO::NULL)
21
23
  # TODO: setting :debug, true/false
@@ -29,6 +31,7 @@ class RedisQueuedLocks::Client
29
31
  validate('default_queue_ttl', :integer)
30
32
  validate('lock_release_batch_size', :integer)
31
33
  validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
34
+ validate('uniq_identifier', :proc)
32
35
  end
33
36
 
34
37
  # @return [RedisClient]
@@ -37,6 +40,12 @@ class RedisQueuedLocks::Client
37
40
  # @since 0.1.0
38
41
  attr_reader :redis_client
39
42
 
43
+ # @return [String]
44
+ #
45
+ # @api private
46
+ # @since 0.1.0
47
+ attr_accessor :uniq_identity
48
+
40
49
  # @param redis_client [RedisClient]
41
50
  # Redis connection manager, which will be used for the lock acquierment and distribution.
42
51
  # It should be an instance of RedisClient.
@@ -48,15 +57,12 @@ class RedisQueuedLocks::Client
48
57
  # @since 0.1.0
49
58
  def initialize(redis_client, &configs)
50
59
  configure(&configs)
60
+ @uniq_identity = config[:uniq_identifier].call
51
61
  @redis_client = redis_client
52
62
  end
53
63
 
54
64
  # @param lock_name [String]
55
65
  # Lock name to be obtained.
56
- # @option process_id [Integer,String]
57
- # The process that want to acquire the lock.
58
- # @option thread_id [Integer,String]
59
- # The process's thread that want to acquire the lock.
60
66
  # @option ttl [Integer]
61
67
  # Lock's time to live (in milliseconds).
62
68
  # @option queue_ttl [Integer]
@@ -73,17 +79,19 @@ class RedisQueuedLocks::Client
73
79
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
74
80
  # @option raise_errors [Boolean]
75
81
  # Raise errors on library-related limits such as timeout or failed lock obtain.
82
+ # @option identity [String]
83
+ # Unique acquire identifier that is also should be unique between processes and pods
84
+ # on different machines. By default the uniq identity string is
85
+ # represented as 10 bytes hexstr.
76
86
  # @param block [Block]
77
87
  # A block of code that should be executed after the successfully acquired lock.
78
- # @return [Hash<Symbol,Any>]
88
+ # @return [Hash<Symbol,Any>,yield]
79
89
  # Format: { ok: true/false, result: Symbol/Hash }.
80
90
  #
81
91
  # @api public
82
92
  # @since 0.1.0
83
93
  def lock(
84
94
  lock_name,
85
- process_id: RedisQueuedLocks::Resource.get_process_id,
86
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
87
95
  ttl: config[:default_lock_ttl],
88
96
  queue_ttl: config[:default_queue_ttl],
89
97
  timeout: config[:try_to_lock_timeout],
@@ -91,13 +99,16 @@ class RedisQueuedLocks::Client
91
99
  retry_delay: config[:retry_delay],
92
100
  retry_jitter: config[:retry_jitter],
93
101
  raise_errors: false,
102
+ identity: uniq_identity,
94
103
  &block
95
104
  )
96
105
  RedisQueuedLocks::Acquier.acquire_lock!(
97
106
  redis_client,
98
107
  lock_name,
99
- process_id:,
100
- thread_id:,
108
+ process_id: RedisQueuedLocks::Resource.get_process_id,
109
+ thread_id: RedisQueuedLocks::Resource.get_thread_id,
110
+ fiber_id: RedisQueuedLocks::Resource.get_fiber_id,
111
+ ractor_id: RedisQueuedLocks::Resource.get_ractor_id,
101
112
  ttl:,
102
113
  queue_ttl:,
103
114
  timeout:,
@@ -106,6 +117,7 @@ class RedisQueuedLocks::Client
106
117
  retry_jitter:,
107
118
  raise_errors:,
108
119
  instrumenter: config[:instrumenter],
120
+ identity:,
109
121
  &block
110
122
  )
111
123
  end
@@ -116,20 +128,17 @@ class RedisQueuedLocks::Client
116
128
  # @since 0.1.0
117
129
  def lock!(
118
130
  lock_name,
119
- process_id: RedisQueuedLocks::Resource.get_process_id,
120
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
121
131
  ttl: config[:default_lock_ttl],
122
132
  queue_ttl: config[:default_queue_ttl],
123
133
  timeout: config[:default_timeout],
124
134
  retry_count: config[:retry_count],
125
135
  retry_delay: config[:retry_delay],
126
136
  retry_jitter: config[:retry_jitter],
137
+ identity: uniq_identity,
127
138
  &block
128
139
  )
129
140
  lock(
130
141
  lock_name,
131
- process_id:,
132
- thread_id:,
133
142
  ttl:,
134
143
  queue_ttl:,
135
144
  timeout:,
@@ -137,6 +146,7 @@ class RedisQueuedLocks::Client
137
146
  retry_delay:,
138
147
  retry_jitter:,
139
148
  raise_errors: true,
149
+ identity:,
140
150
  &block
141
151
  )
142
152
  end
@@ -147,11 +157,43 @@ class RedisQueuedLocks::Client
147
157
  # @api public
148
158
  # @since 0.1.0
149
159
  def unlock(lock_name)
150
- RedisQueuedLocks::Acquier.release_lock!(
151
- redis_client,
152
- lock_name,
153
- config[:instrumenter]
154
- )
160
+ RedisQueuedLocks::Acquier.release_lock!(redis_client, lock_name, config[:instrumenter])
161
+ end
162
+
163
+ # @param lock_name [String]
164
+ # @return [Boolean]
165
+ #
166
+ # @api public
167
+ # @since 0.1.0
168
+ def locked?(lock_name)
169
+ RedisQueuedLocks::Acquier.locked?(redis_client, lock_name)
170
+ end
171
+
172
+ # @param lock_name [String]
173
+ # @return [Boolean]
174
+ #
175
+ # @api public
176
+ # @since 0.1.0
177
+ def queued?(lock_name)
178
+ RedisQueuedLocks::Acquier.queued?(redis_client, lock_name)
179
+ end
180
+
181
+ # @param lock_name [String]
182
+ # @return [Hash,NilClass]
183
+ #
184
+ # @api public
185
+ # @since 0.1.0
186
+ def lock_info(lock_name)
187
+ RedisQueuedLocks::Acquier.lock_info(redis_client, lock_name)
188
+ end
189
+
190
+ # @param lock_name [String]
191
+ # @return [Hash,NilClass]
192
+ #
193
+ # @api public
194
+ # @since 0.1.0
195
+ def queue_info(lock_name)
196
+ RedisQueuedLocks::Acquier.queue_info(redis_client, lock_name)
155
197
  end
156
198
 
157
199
  # @option batch_size [Integer]
@@ -160,10 +202,7 @@ class RedisQueuedLocks::Client
160
202
  # @api public
161
203
  # @since 0.1.0
162
204
  def clear_locks(batch_size: config[:lock_release_batch_size])
163
- RedisQueuedLocks::Acquier.release_all_locks!(
164
- redis_client,
165
- batch_size,
166
- config[:instrumenter]
167
- )
205
+ RedisQueuedLocks::Acquier.release_all_locks!(redis_client, batch_size, config[:instrumenter])
168
206
  end
169
207
  end
208
+ # rubocop:enable Metrics/ClassLength
@@ -16,14 +16,29 @@ module RedisQueuedLocks::Resource
16
16
  LOCK_QUEUE_PATTERN = 'rql:lock_queue:*'
17
17
 
18
18
  class << self
19
+ # Returns 10-byte unique identifier. It is used for uniquely
20
+ # identify current process between different nodes/pods of your application
21
+ # during the lock obtaining and self-identifying in the lock queue.
22
+ #
23
+ # @return [String]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ def calc_uniq_identity
28
+ SecureRandom.hex(5)
29
+ end
30
+
19
31
  # @param process_id [Integer,String]
20
32
  # @param thread_id [Integer,String]
33
+ # @param fiber_id [Integer,String]
34
+ # @param ractor_id [Integer,String]
35
+ # @param identity [String]
21
36
  # @return [String]
22
37
  #
23
38
  # @api private
24
39
  # @since 0.1.0
25
- def acquier_identifier(process_id = get_process_id, thread_id = get_thread_id)
26
- "rql:acq:#{process_id}/#{thread_id}"
40
+ def acquier_identifier(process_id, thread_id, fiber_id, ractor_id, identity)
41
+ "rql:acq:#{process_id}/#{thread_id}/#{fiber_id}/#{ractor_id}/#{identity}"
27
42
  end
28
43
 
29
44
  # @param lock_name [String]
@@ -79,6 +94,22 @@ module RedisQueuedLocks::Resource
79
94
  ::Thread.current.object_id
80
95
  end
81
96
 
97
+ # @return [Integer]
98
+ #
99
+ # @api private
100
+ # @since 0.1.0
101
+ def get_fiber_id
102
+ ::Fiber.current.object_id
103
+ end
104
+
105
+ # @return [Integer]
106
+ #
107
+ # @api private
108
+ # @since 0.1.0
109
+ def get_ractor_id
110
+ ::Ractor.current.object_id
111
+ end
112
+
82
113
  # @return [Integer]
83
114
  #
84
115
  # @api private
@@ -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.9
9
+ VERSION = '0.0.9'
10
10
  end
@@ -3,6 +3,7 @@
3
3
  require 'redis-client'
4
4
  require 'qonfig'
5
5
  require 'timeout'
6
+ require 'securerandom'
6
7
 
7
8
  # @api public
8
9
  # @since 0.1.0
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.9
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