redis_queued_locks 1.6.0 → 1.8.0

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: dd9e0ee49d4c08d6ba572a825da792468926b2f676e872a2337b1ce2eff60836
4
- data.tar.gz: ae635ac450f966539581c4b5a53098b92ba29562bb0641049571ca0ceb1461e7
3
+ metadata.gz: 7f8dd117ee12f1f598da555d02516bb950a2d9640b075a048612726b7a88a6ad
4
+ data.tar.gz: c0a5ac4a4dcb6f3b448dc23ffa8d0e0e13a726d50bdd98fc40193704aa6f967d
5
5
  SHA512:
6
- metadata.gz: b678b3fbccd5273d17d07120c144b0eb1c6be32db73ff1aa2f9fb19f485d30499645f3c4592fad0cb4cdeab1ebcd37f437b62740e01a46dce999eaaa2814f146
7
- data.tar.gz: b8cb79c79767cd1d3b831992454c84c8399f6bf7ac313b8a2d0a7527711f555cb2196a172ae435103df2fc4c3e483b0a5b35775fba4baee7b7bad6234103cb0a
6
+ metadata.gz: b35d0ef98da0e1ec2c2368b36064eb0501b45d26aaa1b7204230692a41025583366ce181b85eec09cff2d6fe4e93ec146280db78e08f8cbb232a559b91f6d489
7
+ data.tar.gz: 4776dd89a7bf1e27476877465faf3240988929efe398090fa4b8b4f604a976a861558e6f606278b91c2c95530be6c5205f8245c887befe92a75d4efde31820dc
data/CHANGELOG.md CHANGED
@@ -1,8 +1,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.8.0] - 2024-06-13
4
+ ### Added
5
+ - A configurable option that enables the adding additional lock/queue data to "Acquirement Timeout"-related error messages for better debugging;
6
+ - Configurable option is used beacuse of the extra error data requires some additional Redis requests that can be costly for memory/cpu/etc resources;
7
+ - An ability to get the current acquirer id (`RedisQueuedLocks::Client#current_acquirer_id`);
8
+ ### Changed
9
+ - Added additional lock information to some exceptions that does not require extra Redis requests;
10
+
11
+ ## [1.7.0] - 2024-06-12
12
+ ### Added
13
+ - New feature: **Lock Access Strategy**: you can obtain a lock in different ways: `queued` (classic queued FIFO), `random` (get the lock immideatly if lock is free):
14
+ - `:queued` is used by default (classic `redis_queued_locks` behavior);
15
+ - `:random`: obtain a lock without checking the positions in the queue => if lock is free to obtain - it will be obtained;
16
+ ### Changed
17
+ - Some logging refactorings, some instrumentation refactorings: the code that uses them is more readable and supportable;
18
+
3
19
  ## [1.6.0] - 2024-05-25
4
20
  ### Added
5
- - New Feature: **Instrumentation Sampling** - configurable instrumentation sampling based on `weight` algorithm (where the weight is a percentage of RQL cases that should be logged);
21
+ - New Feature: **Instrumentation Sampling**: configurable instrumentation sampling based on `weight` algorithm (where the weight is a percentage of RQL cases that should be logged);
6
22
  - Missing instrumenter customization in public `RedisQueuedLocks::Client` methods;
7
23
  - Documentation updates;
8
24
 
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) (with requeue capabilities) which guarantees the request queue will never be stacked.
6
6
 
7
+ In addition to the classic `queued` (FIFO) strategy RQL supports `random` (RANDOM) lock obtaining strategy when any acquirer from the lock queue can obtain the lock regardless the position in the queue.
8
+
7
9
  Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), logging and instrumentation.
8
10
 
9
11
  ---
@@ -32,6 +34,10 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
32
34
  - [locks_info](#locks_info---get-list-of-locks-with-their-info)
33
35
  - [queues_info](#queues_info---get-list-of-queues-with-their-info)
34
36
  - [clear_dead_requests](#clear_dead_requests)
37
+ - [current_acquirer_id](#current_acquirer_id)
38
+ - [Lock Access Strategies](#lock-access-strategies)
39
+ - [queued](#lock-access-strategies)
40
+ - [random](#lock-access-strategies)
35
41
  - [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks)
36
42
  - [Logging](#logging)
37
43
  - [Instrumentation](#instrumentation)
@@ -69,6 +75,8 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
69
75
 
70
76
  > Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
71
77
 
78
+ > In addition to the classic "queued" (FIFO) strategy RQL supports "random" (RANDOM) lock obtaining strategy when any acquirer from the lock queue can obtain the lock regardless the position in the queue.
79
+
72
80
  **Soon**: detailed explanation.
73
81
 
74
82
  ---
@@ -150,9 +158,43 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
150
158
  # - should be all blocks of code are timed by default;
151
159
  config.is_timed_by_default = false
152
160
 
161
+ # (boolean) (default: false)
162
+ # - When the lock acquirement try reached the acquirement time limit (:timeout option) the
163
+ # `RedisQueuedLocks::LockAcquirementTimeoutError` is raised (when `raise_errors` option
164
+ # of the #lock method is set to `true`). The error message contains the lock key name and
165
+ # the timeout value).
166
+ # - <true> option adds the additional details to the error message:
167
+ # - current lock queue state (you can see which acquirer blocks your request and
168
+ # how much acquirers are in queue);
169
+ # - current lock data stored inside (for example: you can check the current acquirer and
170
+ # the lock meta state if you store some additional data there);
171
+ # - Realized as an option because of the additional lock data requires two additional Redis
172
+ # queries: (1) get the current lock from redis and (2) fetch the lock queue state;
173
+ # - These two additional Redis queries has async nature so you can receive
174
+ # inconsistent data of the lock and of the lock queue in your error emssage because:
175
+ # - required lock can be released after the error moment and before the error message build;
176
+ # - required lock can be obtained by other process after the error moment and
177
+ # before the error message build;
178
+ # - required lock queue can reach a state when the blocking acquirer start to obtain the lock
179
+ # and moved from the lock queue after the error moment and before the error message build;
180
+ # - You should consider the async nature of this error message and should use received data
181
+ # from error message correspondingly;
182
+ config.detailed_acq_timeout_error = false
183
+
184
+ # (symbol) (default: :queued)
185
+ # - Defines the way in which the lock should be obitained;
186
+ # - By default it is configured to obtain a lock in classic `queued` way:
187
+ # you should wait your position in queue in order to obtain a lock;
188
+ # - Can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);
189
+ # - Supports different strategies:
190
+ # - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;
191
+ # - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist,
192
+ # retries, timeouts and so on). if lock is free to obtain - it will be obtained;
193
+ config.default_access_strategy = :queued
194
+
153
195
  # (symbol) (default: :wait_for_lock)
154
196
  # - Global default conflict strategy mode;
155
- # - Can be customized in methods `#lock` and `#lock` via `:conflict_strategy` attribute (see method signatures of #lock and #lock! methods);
197
+ # - Can be customized in methods `#lock` and `#lock!` via `:conflict_strategy` attribute (see method signatures of #lock and #lock! methods);
156
198
  # - Conflict strategy is a logical behavior for cases when the process that obtained the lock want to acquire this lock again;
157
199
  # - Realizes "reentrant locks" abstraction (same process conflict / same process deadlock);
158
200
  # - By default uses `:wait_for_lock` strategy (classic way);
@@ -202,15 +244,15 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
202
244
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
203
245
  # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
204
246
  # - at this moment the only debug logs are realised in following cases:
205
- # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id");
206
- # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id");
207
- # - "[redis_queued_locks.dead_score_reached__reset_acquier_position]" (logs "lock_key", "queue_ttl", "acq_id");
208
- # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time");
209
- # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time");
210
- # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time");
211
- # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id");
212
- # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id");
213
- # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id");
247
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
248
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
249
+ # - "[redis_queued_locks.dead_score_reached__reset_acquier_position]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
250
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time", "acs_strat");
251
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time", "acs_strat");
252
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time", "acs_strat");
253
+ # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
254
+ # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
255
+ # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "acs_strat");
214
256
  # - by default uses VoidLogger that does nothing;
215
257
  config.logger = RedisQueuedLocks::Logging::VoidLogger
216
258
 
@@ -218,19 +260,19 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
218
260
  # - adds additional debug logs;
219
261
  # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
220
262
  # - it adds following logs in addition to the existing:
221
- # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id");
222
- # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id");
223
- # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id");
224
- # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "spc_status");
225
- # - "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" (logs "lock_key", "queue_ttl", "acq_id", "spc_status", "last_ext_ttl", "last_ext_ts");
226
- # - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "spc_status", last_spc_ts);
227
- # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id)";
228
- # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id");
229
- # - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue");
230
- # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id");
231
- # - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "<current_lock_data>");
232
- # - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
233
- # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id");
263
+ # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
264
+ # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
265
+ # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
266
+ # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status");
267
+ # - "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", "last_ext_ttl", "last_ext_ts");
268
+ # - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", last_spc_ts);
269
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id, "acs_strat")";
270
+ # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
271
+ # - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue");
272
+ # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
273
+ # - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue", "<current_lock_data>");
274
+ # - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
275
+ # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
234
276
  config.log_lock_try = false
235
277
 
236
278
  # (default: false)
@@ -298,6 +340,7 @@ end
298
340
  - [locks_info](#locks_info---get-list-of-locks-with-their-info)
299
341
  - [queues_info](#queues_info---get-list-of-queues-with-their-info)
300
342
  - [clear_dead_requests](#clear_dead_requests)
343
+ - [current_acquirer_id](#current_acquirer_id)
301
344
 
302
345
  ---
303
346
 
@@ -327,8 +370,10 @@ def lock(
327
370
  raise_errors: false,
328
371
  fail_fast: false,
329
372
  conflict_strategy: config[:default_conflict_strategy],
373
+ access_strategy: config[:default_access_strategy],
330
374
  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
331
375
  meta: nil,
376
+ detailed_acq_timeout_error: config[:detailed_acq_timeout_error],
332
377
  instrument: nil,
333
378
  instrumenter: config[:instrumenter],
334
379
  logger: config[:logger],
@@ -378,12 +423,21 @@ def lock(
378
423
  - Raise errors on library-related limits (such as timeout or retry count limit) and on lock conflicts (such as same-process dead locks);
379
424
  - `false` by default;
380
425
  - `fail_fast` - (optional) `[Boolean]`
381
- - Should the required lock to be checked before the try and exit immidietly if lock is
382
- already obtained;
383
- - Should the logic exit immidietly after the first try if the lock was obtained
384
- by another process while the lock request queue was initially empty;
385
- - `false` by default;
386
- - `conflict_strategy` - (optional) - `[Symbol]``
426
+ - Should the required lock to be checked before the try and exit immidietly if lock is
427
+ already obtained;
428
+ - Should the logic exit immidietly after the first try if the lock was obtained
429
+ by another process while the lock request queue was initially empty;
430
+ - `false` by default;
431
+ - `access_strategy` - (optional) - `[Symbol]`
432
+ - Defines the way in which the lock should be obitained (in queued way, in random way and so on);
433
+ - By default it is configured to obtain a lock in classic `:queued` way: you should wait your position in queue in order to obtain a lock;
434
+ - Supports following strategies:
435
+ - `:queued` (FIFO): (default) the classic queued behavior, your lock will be obitaned if you are first in queue and the required lock is free;
436
+ - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist, retries, timeouts and so on).
437
+ if lock is free to obtain - it will be obtained;
438
+ - pre-configured in `config[:default_access_strategy]`;
439
+ - See [Lock Access Strategies](#lock-access-strategies) documentation section for details;
440
+ - `conflict_strategy` - (optional) - `[Symbol]`
387
441
  - The conflict strategy mode for cases when the process that obtained the lock
388
442
  want to acquire this lock again;
389
443
  - By default uses `:wait_for_lock` strategy;
@@ -393,7 +447,7 @@ def lock(
393
447
  - `:extendable_work_through` - continue working under the lock **with** lock's TTL extension;
394
448
  - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
395
449
  - `:dead_locking` - fail with deadlock exception;
396
- - See [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) readme section for details;
450
+ - See [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) documentation section for details;
397
451
  - `identity` - (optional) `[String]`
398
452
  - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
399
453
  collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
@@ -405,6 +459,25 @@ def lock(
405
459
  - A custom metadata wich will be passed to the lock data in addition to the existing data;
406
460
  - Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`);
407
461
  - `nil` by default (means "no metadata");
462
+ - `detailed_acq_timeout_error` - (optional) `[Boolean]`
463
+ - When the lock acquirement try reached the acquirement time limit (:timeout option) the
464
+ `RedisQueuedLocks::LockAcquirementTimeoutError` is raised (when `raise_errors` option
465
+ set to `true`). The error message contains the lock key name and the timeout value).
466
+ - <true> option adds the additional details to the error message:
467
+ - current lock queue state (you can see which acquirer blocks your request and how much acquirers are in queue);
468
+ - current lock data stored inside (for example: you can check the current acquirer and the lock meta state if you store some additional data there);
469
+ - Realized as an option because of the additional lock data requires two additional Redis
470
+ queries: (1) get the current lock from redis and (2) fetch the lock queue state;
471
+ - These two additional Redis queries has async nature so you can receive
472
+ inconsistent data of the lock and of the lock queue in your error emssage because:
473
+ - required lock can be released after the error moment and before the error message build;
474
+ - required lock can be obtained by other process after the error moment and
475
+ before the error message build;
476
+ - required lock queue can reach a state when the blocking acquirer start to obtain the lock
477
+ and moved from the lock queue after the error moment and before the error message build;
478
+ - You should consider the async nature of this error message and should use received data
479
+ from error message correspondingly;
480
+ - pre-configred in `config[:detailed_acq_timeout_error]`;
408
481
  - `logger` - (optional) `[::Logger,#debug]`
409
482
  - Logger object used for loggin internal mutation oeprations and opertioan results / process progress;
410
483
  - pre-configured in `config[:logger]` with void logger `RedisQueuedLocks::Logging::VoidLogger`;
@@ -624,7 +697,51 @@ rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
624
697
  "rql:acq:123/456/567/685/374dd74329", # some other waiting process
625
698
  "rql:acq:123/456/567/683/374dd74322", # <== we are here (moved to the end of the queue)
626
699
  ]
700
+ ```
701
+
702
+ - obtain a lock in `:random` way (with `:random` strategy): in `:random` strategy
703
+ any acquirer from the lcok queue can obtain the lock regardless of the position in the lock queue;
704
+
705
+ ```ruby
706
+ # Current Process (process#1)
707
+ rql.lock('my_lock', ttl: 2_000, access_strategy: :random)
708
+ # => holds the lock
709
+
710
+ # Another Process (process#2)
711
+ rql.lock('my_lock', retry_delay: 7000, ttl: 4000, access_strategy: :random)
712
+ # => the lock is not free, stay in a queue and retry...
713
+
714
+ # Another Process (process#3)
715
+ rql.lock('my_lock', retry_delay: 3000, ttl: 3000, access_strategy: :random)
716
+ # => the lock is not free, stay in a queue and retry...
627
717
 
718
+ # lock queue:
719
+ [
720
+ "rql:acq:123/456/567/677/374dd74322", # process#1 (holds the lock)
721
+ "rql:acq:123/456/567/679/374dd74321", # process#2 (waiting for the lock, in retry)
722
+ "rql:acq:123/456/567/683/374dd74322", # process#3 (waiting for the lock, in retry)
723
+ ]
724
+
725
+ # ... some period of time
726
+ # -> process#1 => released the lock;
727
+ # -> process#2 => delayed retry, waiting;
728
+ # -> process#3 => preparing for retry (the delay is over);
729
+ # lock queue:
730
+ [
731
+ "rql:acq:123/456/567/679/374dd74321", # process#2 (waiting for the lock, DELAYED)
732
+ "rql:acq:123/456/567/683/374dd74322", # process#3 (trying to obtain the lock, RETRYING now)
733
+ ]
734
+
735
+ # ... some period of time
736
+ # -> process#2 => didn't have time to obtain the lock, delayed retry;
737
+ # -> process#3 => holds the lock;
738
+ # lock queue:
739
+ [
740
+ "rql:acq:123/456/567/679/374dd74321", # process#2 (waiting for the lock, DELAYED)
741
+ "rql:acq:123/456/567/683/374dd74322", # process#3 (holds the lock)
742
+ ]
743
+
744
+ # `process#3` is the last in queue, but has acquired the lock because his lock request "randomly" came first;
628
745
  ```
629
746
 
630
747
  ---
@@ -653,10 +770,12 @@ def lock!(
653
770
  fail_fast: false,
654
771
  identity: uniq_identity,
655
772
  meta: nil,
773
+ detailed_acq_timeout_error: config[:detailed_acq_timeout_error]
656
774
  logger: config[:logger],
657
775
  log_lock_try: config[:log_lock_try],
658
776
  instrument: nil,
659
777
  instrumenter: config[:instrumenter],
778
+ access_strategy: config[:default_access_strategy],
660
779
  conflict_strategy: config[:default_conflict_strategy],
661
780
  log_sampling_enabled: config[:log_sampling_enabled],
662
781
  log_sampling_percent: config[:log_sampling_percent],
@@ -1246,6 +1365,59 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
1246
1365
 
1247
1366
  ---
1248
1367
 
1368
+ #### #current_acquirer_id
1369
+
1370
+ - get the current acquirer identifier in RQL notation that you can use for debugging purposes during the lock analyzation;
1371
+ - acquirer identifier format:
1372
+ ```ruby
1373
+ "rql:acq:#{process_id}/#{thread_id}/#{fiber_id}/#{ractor_id}/#{identity}"
1374
+ ```
1375
+ - because of the moment that `#lock`/`#lock!` gives you a possibility to customize `process_id`,
1376
+ `fiber_id`, `thread_id`, `ractor_id` and `unique identity` identifiers the `#current_acquirer_id` method provides this possibility too;
1377
+
1378
+ Accepts:
1379
+
1380
+ - `process_id:` - (optional) `[Integer,Any]`
1381
+ - `::Process.pid` by default;
1382
+ - `thread_id:` - (optional) `[Integer,Any]`;
1383
+ - `::Thread.current.object_id` by default;
1384
+ - `fiber_id:` - (optional) `[Integer,Any]`;
1385
+ - `::Fiber.current.object_id` by default;
1386
+ - `ractor_id:` - (optional) `[Integer,Any]`;
1387
+ - `::Ractor.current.object_id` by default;
1388
+ - `identity:` - (optional) `[String,Any]`;
1389
+ - this value is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`;
1390
+ - this value can be accessed from `RedisQueuedLock::Client#uniq_identity`;
1391
+ - [Configuration](#configuration) documentation: see `config[:uniq_identifier]`;
1392
+ - [#lock](#lock---obtain-a-lock) method documentation: see `uniq_identifier`;
1393
+
1394
+ ```ruby
1395
+ rql.current_acquirer_id
1396
+
1397
+ # =>
1398
+ "rql:acq:38529/4500/4520/4360/66093702f24a3129"
1399
+ ```
1400
+
1401
+ ---
1402
+
1403
+ ## Lock Access Strategies
1404
+
1405
+ <sup>\[[back to top](#table-of-contents)\]</sup>
1406
+
1407
+ - **this documentation section is in progress**;
1408
+ - (little details for a context of the current implementation and feautres):
1409
+ - defines the way in which the lock should be obitained;
1410
+ - by default it is configured to obtain a lock in classic `queued` way: you should wait your position in queue in order to obtain a lock;
1411
+ - can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);
1412
+ - supports different strategies:
1413
+ - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;
1414
+ - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist, retries, timeouts and so on). if lock is free to obtain - it will be obtained;
1415
+ - for current implementation detalis check:
1416
+ - [Configuration](#configuration) documentation: see `config.default_access_strategy` config docs;
1417
+ - [#lock](#lock---obtain-a-lock) method documentation: see `access_strategy` attribute docs;
1418
+
1419
+ ---
1420
+
1249
1421
  ## Dead locks and Reentrant locks
1250
1422
 
1251
1423
  <sup>\[[back to top](#table-of-contents)\]</sup>
@@ -1271,34 +1443,34 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
1271
1443
  - default logs (raised from `#lock`/`#lock!`):
1272
1444
 
1273
1445
  ```ruby
1274
- "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id");
1275
- "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id");
1276
- "[redis_queued_locks.dead_score_reached__reset_acquier_position]" # (logs "lock_key", "queue_ttl", "acq_id");
1446
+ "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1447
+ "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1448
+ "[redis_queued_locks.dead_score_reached__reset_acquier_position]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1277
1449
  "[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acq_time");
1278
- "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acq_time");
1279
- "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acq_time");
1280
- "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id");
1281
- "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id");
1282
- "[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id");
1450
+ "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "acq_time");
1451
+ "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "acq_time");
1452
+ "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1453
+ "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1454
+ "[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "acs_strat");
1283
1455
  ```
1284
1456
 
1285
1457
  - additional logs (raised from `#lock`/`#lock!` with `confg[:log_lock_try] == true`):
1286
1458
 
1287
1459
  ```ruby
1288
- "[redis_queued_locks.try_lock.start]" # (logs "lock_key", "queue_ttl", "acq_id");
1289
- "[redis_queued_locks.try_lock.rconn_fetched]" # (logs "lock_key", "queue_ttl", "acq_id");
1290
- "[redis_queued_locks.try_lock.same_process_conflict_detected]" # (logs "lock_key", "queue_ttl", "acq_id");
1291
- "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" # (logs "lock_key", "queue_ttl", "acq_id", "spc_status");
1292
- "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "spc_status", "last_ext_ttl", "last_ext_ts");
1293
- "[redis_queued_locks.try_lock.reentrant_lock__work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "spc_status", last_spc_ts);
1294
- "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "spc_status", "last_spc_ts");
1295
- "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id)";
1296
- "[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id");
1297
- "[redis_queued_locks.try_lock.get_first_from_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue");
1298
- "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id");
1299
- "[redis_queued_locks.try_lock.exit__no_first]" # (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "<current_lock_data>");
1300
- "[redis_queued_locks.try_lock.exit__lock_still_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
1301
- "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id");
1460
+ "[redis_queued_locks.try_lock.start]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1461
+ "[redis_queued_locks.try_lock.rconn_fetched]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1462
+ "[redis_queued_locks.try_lock.same_process_conflict_detected]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1463
+ "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status");
1464
+ "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", "last_ext_ttl", "last_ext_ts");
1465
+ "[redis_queued_locks.try_lock.reentrant_lock__work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", last_spc_ts);
1466
+ "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", "last_spc_ts");
1467
+ "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1468
+ "[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1469
+ "[redis_queued_locks.try_lock.get_first_from_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue");
1470
+ "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1471
+ "[redis_queued_locks.try_lock.exit__no_first]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue", "<current_lock_data>");
1472
+ "[redis_queued_locks.try_lock.exit__lock_still_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
1473
+ "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1302
1474
  ```
1303
1475
 
1304
1476
  ---
@@ -1424,7 +1596,6 @@ Detalized event semantics and payload structure:
1424
1596
  <sup>\[[back to top](#table-of-contents)\]</sup>
1425
1597
 
1426
1598
  - **Major**:
1427
- - support for Random Access strategy (non-queued behavior);
1428
1599
  - lock request prioritization;
1429
1600
  - **strict redlock algorithm support** (support for many `RedisClient` instances);
1430
1601
  - `#lock_series` - acquire a series of locks:
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.7.0
5
+ module RedisQueuedLocks::Acquier::AcquireLock::DequeueFromLockQueue::LogVisitor
6
+ extend self
7
+
8
+ # @param logger [::Logger,#debug]
9
+ # @param log_sampled [Boolean]
10
+ # @param lock_key [String]
11
+ # @param queue_ttl [Integer]
12
+ # @param acquier_id [String]
13
+ # @param access_strategy [Symbol]
14
+ # @return [void]
15
+ #
16
+ # @api private
17
+ # @since 1.7.0
18
+ def dequeue_from_lock_queue(
19
+ logger,
20
+ log_sampled,
21
+ lock_key,
22
+ queue_ttl,
23
+ acquier_id,
24
+ access_strategy
25
+ )
26
+ return unless log_sampled
27
+
28
+ logger.debug do
29
+ "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue] " \
30
+ "lock_key => '#{lock_key}' " \
31
+ "queue_ttl => #{queue_ttl} " \
32
+ "acq_id => '#{acquier_id}' " \
33
+ "acs_strat => '#{access_strategy}"
34
+ end rescue nil
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.7.0
5
+ module RedisQueuedLocks::Acquier::AcquireLock::DequeueFromLockQueue
6
+ require_relative 'dequeue_from_lock_queue/log_visitor'
7
+
8
+ # @param redis [RedisClient]
9
+ # @param logger [::Logger,#debug]
10
+ # @param lock_key [String]
11
+ # @param lock_key_queue [String]
12
+ # @param queue_ttl [Integer]
13
+ # @param acquier_id [String]
14
+ # @param access_strategy [Symbol]
15
+ # @param log_sampled [Boolean]
16
+ # @param instr_sampled [Boolean]
17
+ # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
18
+ #
19
+ # @api private
20
+ # @since 1.7.0
21
+ def dequeue_from_lock_queue(
22
+ redis,
23
+ logger,
24
+ lock_key,
25
+ lock_key_queue,
26
+ queue_ttl,
27
+ acquier_id,
28
+ access_strategy,
29
+ log_sampled,
30
+ instr_sampled
31
+ )
32
+ result = redis.call('ZREM', lock_key_queue, acquier_id)
33
+ LogVisitor.dequeue_from_lock_queue(
34
+ logger, log_sampled,
35
+ lock_key, queue_ttl, acquier_id, access_strategy
36
+ )
37
+ RedisQueuedLocks::Data[ok: true, result: result]
38
+ end
39
+ end