redis_queued_locks 0.0.7 → 0.0.9

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