redis_queued_locks 1.6.0 → 1.7.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: cd303479ce60c268e630d4ad770657cbe0223cffcb9990d31a9bb3de92d80843
4
+ data.tar.gz: 2a19bfed90da70fa8c49bf069a2acd110d3840eeb78f1f21767416c28e5bc6b7
5
5
  SHA512:
6
- metadata.gz: b678b3fbccd5273d17d07120c144b0eb1c6be32db73ff1aa2f9fb19f485d30499645f3c4592fad0cb4cdeab1ebcd37f437b62740e01a46dce999eaaa2814f146
7
- data.tar.gz: b8cb79c79767cd1d3b831992454c84c8399f6bf7ac313b8a2d0a7527711f555cb2196a172ae435103df2fc4c3e483b0a5b35775fba4baee7b7bad6234103cb0a
6
+ metadata.gz: 0c2d6c956bfc7227cd45c33c8b983ae759e13bf7149121c272821922d66a4be73b57b344e2042f86d0391acbe92de794013e0a67ce6157b402c6a0ca0a3a1740
7
+ data.tar.gz: e00ec46afd8e5d8ebef469c0a5d86a026562f8060e8abc95c4f3e89aa9351dc8961fe2d9d181aa4e8203056c74fb4aa8c4043f925b93a5b477c44541eec5ad9f
data/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.7.0] - 2024-06-12
4
+ ### Added
5
+ - 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), and soon: `lifo` and `barrier`;
6
+ - `:queued` is used by default (classic `redis_queued_locks` behavior);
7
+ - `:random`: obtain a lock without checking the positions in the queue => if lock is free to obtain - it will be obtained;
8
+ ### Changed
9
+ - Some logging refactorings, some instrumentation refactorings: the code that uses them is more readable and supportable;
10
+
3
11
  ## [1.6.0] - 2024-05-25
4
12
  ### 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);
13
+ - 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
14
  - Missing instrumenter customization in public `RedisQueuedLocks::Client` methods;
7
15
  - Documentation updates;
8
16
 
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,9 @@ 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
+ - [Lock Access Strategies](#lock-access-strategies)
38
+ - [queued](#lock-access-strategies)
39
+ - [random](#lock-access-strategies)
35
40
  - [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks)
36
41
  - [Logging](#logging)
37
42
  - [Instrumentation](#instrumentation)
@@ -69,6 +74,8 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
69
74
 
70
75
  > 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
76
 
77
+ > 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.
78
+
72
79
  **Soon**: detailed explanation.
73
80
 
74
81
  ---
@@ -150,9 +157,20 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
150
157
  # - should be all blocks of code are timed by default;
151
158
  config.is_timed_by_default = false
152
159
 
160
+ # (symbol) (default: :queued)
161
+ # - Defines the way in which the lock should be obitained;
162
+ # - By default it is configured to obtain a lock in classic `queued` way:
163
+ # you should wait your position in queue in order to obtain a lock;
164
+ # - Can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);
165
+ # - Supports different strategies:
166
+ # - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;
167
+ # - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist,
168
+ # retries, timeouts and so on). if lock is free to obtain - it will be obtained;
169
+ config.default_access_strategy = :queued
170
+
153
171
  # (symbol) (default: :wait_for_lock)
154
172
  # - 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);
173
+ # - Can be customized in methods `#lock` and `#lock!` via `:conflict_strategy` attribute (see method signatures of #lock and #lock! methods);
156
174
  # - Conflict strategy is a logical behavior for cases when the process that obtained the lock want to acquire this lock again;
157
175
  # - Realizes "reentrant locks" abstraction (same process conflict / same process deadlock);
158
176
  # - By default uses `:wait_for_lock` strategy (classic way);
@@ -202,15 +220,15 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
202
220
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
203
221
  # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
204
222
  # - 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");
223
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
224
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
225
+ # - "[redis_queued_locks.dead_score_reached__reset_acquier_position]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
226
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time", "acs_strat");
227
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time", "acs_strat");
228
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "acq_time", "acs_strat");
229
+ # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
230
+ # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
231
+ # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "acs_strat");
214
232
  # - by default uses VoidLogger that does nothing;
215
233
  config.logger = RedisQueuedLocks::Logging::VoidLogger
216
234
 
@@ -218,19 +236,19 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
218
236
  # - adds additional debug logs;
219
237
  # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
220
238
  # - 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");
239
+ # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
240
+ # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
241
+ # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
242
+ # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status");
243
+ # - "[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");
244
+ # - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", last_spc_ts);
245
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id, "acs_strat")";
246
+ # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
247
+ # - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue");
248
+ # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
249
+ # - "[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>");
250
+ # - "[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>");
251
+ # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
234
252
  config.log_lock_try = false
235
253
 
236
254
  # (default: false)
@@ -327,6 +345,7 @@ def lock(
327
345
  raise_errors: false,
328
346
  fail_fast: false,
329
347
  conflict_strategy: config[:default_conflict_strategy],
348
+ access_strategy: config[:default_access_strategy],
330
349
  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
331
350
  meta: nil,
332
351
  instrument: nil,
@@ -378,12 +397,21 @@ def lock(
378
397
  - Raise errors on library-related limits (such as timeout or retry count limit) and on lock conflicts (such as same-process dead locks);
379
398
  - `false` by default;
380
399
  - `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]``
400
+ - Should the required lock to be checked before the try and exit immidietly if lock is
401
+ already obtained;
402
+ - Should the logic exit immidietly after the first try if the lock was obtained
403
+ by another process while the lock request queue was initially empty;
404
+ - `false` by default;
405
+ - `access_strategy` - (optional) - `[Symbol]`
406
+ - Defines the way in which the lock should be obitained (in queued way, in random way and so on);
407
+ - 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;
408
+ - Supports following strategies:
409
+ - `:queued` (FIFO): (default) the classic queued behavior, your lock will be obitaned if you are first in queue and the required lock is free;
410
+ - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist, retries, timeouts and so on).
411
+ if lock is free to obtain - it will be obtained;
412
+ - pre-configured in `config[:default_access_strategy]`;
413
+ - See [Lock Access Strategies](#lock-access-strategies) documentation section for details;
414
+ - `conflict_strategy` - (optional) - `[Symbol]`
387
415
  - The conflict strategy mode for cases when the process that obtained the lock
388
416
  want to acquire this lock again;
389
417
  - By default uses `:wait_for_lock` strategy;
@@ -393,7 +421,7 @@ def lock(
393
421
  - `:extendable_work_through` - continue working under the lock **with** lock's TTL extension;
394
422
  - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
395
423
  - `:dead_locking` - fail with deadlock exception;
396
- - See [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) readme section for details;
424
+ - See [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) documentation section for details;
397
425
  - `identity` - (optional) `[String]`
398
426
  - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
399
427
  collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
@@ -624,7 +652,51 @@ rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
624
652
  "rql:acq:123/456/567/685/374dd74329", # some other waiting process
625
653
  "rql:acq:123/456/567/683/374dd74322", # <== we are here (moved to the end of the queue)
626
654
  ]
655
+ ```
656
+
657
+ - obtain a lock in `:random` way (with `:random` strategy): in `:random` strategy
658
+ any acquirer from the lcok queue can obtain the lock regardless of the position in the lock queue;
659
+
660
+ ```ruby
661
+ # Current Process (process#1)
662
+ rql.lock('my_lock', ttl: 2_000, access_strategy: :random)
663
+ # => holds the lock
664
+
665
+ # Another Process (process#2)
666
+ rql.lock('my_lock', retry_delay: 7000, ttl: 4000, access_strategy: :random)
667
+ # => the lock is not free, stay in a queue and retry...
668
+
669
+ # Another Process (process#3)
670
+ rql.lock('my_lock', retry_delay: 3000, ttl: 3000, access_strategy: :random)
671
+ # => the lock is not free, stay in a queue and retry...
672
+
673
+ # lock queue:
674
+ [
675
+ "rql:acq:123/456/567/677/374dd74322", # process#1 (holds the lock)
676
+ "rql:acq:123/456/567/679/374dd74321", # process#2 (waiting for the lock, in retry)
677
+ "rql:acq:123/456/567/683/374dd74322", # process#3 (waiting for the lock, in retry)
678
+ ]
627
679
 
680
+ # ... some period of time
681
+ # -> process#1 => released the lock;
682
+ # -> process#2 => delayed retry, waiting;
683
+ # -> process#3 => preparing for retry (the delay is over);
684
+ # lock queue:
685
+ [
686
+ "rql:acq:123/456/567/679/374dd74321", # process#2 (waiting for the lock, DELAYED)
687
+ "rql:acq:123/456/567/683/374dd74322", # process#3 (trying to obtain the lock, RETRYING now)
688
+ ]
689
+
690
+ # ... some period of time
691
+ # -> process#2 => didn't have time to obtain the lock, delayed retry;
692
+ # -> process#3 => holds the lock;
693
+ # lock queue:
694
+ [
695
+ "rql:acq:123/456/567/679/374dd74321", # process#2 (waiting for the lock, DELAYED)
696
+ "rql:acq:123/456/567/683/374dd74322", # process#3 (holds the lock)
697
+ ]
698
+
699
+ # `process#3` is the last in queue, but has acquired the lock because his lock request "randomly" came first;
628
700
  ```
629
701
 
630
702
  ---
@@ -657,6 +729,7 @@ def lock!(
657
729
  log_lock_try: config[:log_lock_try],
658
730
  instrument: nil,
659
731
  instrumenter: config[:instrumenter],
732
+ access_strategy: config[:default_access_strategy],
660
733
  conflict_strategy: config[:default_conflict_strategy],
661
734
  log_sampling_enabled: config[:log_sampling_enabled],
662
735
  log_sampling_percent: config[:log_sampling_percent],
@@ -1246,6 +1319,22 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
1246
1319
 
1247
1320
  ---
1248
1321
 
1322
+ ## Lock Access Strategies
1323
+
1324
+ - **this documentation section is in progress**;
1325
+ - (little details for a context of the current implementation and feautres):
1326
+ - defines the way in which the lock should be obitained;
1327
+ - 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;
1328
+ - can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);
1329
+ - supports different strategies:
1330
+ - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;
1331
+ - `: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;
1332
+ - for current implementation detalis check:
1333
+ - [Configuration](#configuration) documentation: see `config.default_access_strategy` config docs;
1334
+ - [#lock](#lock---obtain-a-lock) method documentation: see `access_strategy` attribute docs;
1335
+
1336
+ ---
1337
+
1249
1338
  ## Dead locks and Reentrant locks
1250
1339
 
1251
1340
  <sup>\[[back to top](#table-of-contents)\]</sup>
@@ -1271,34 +1360,34 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
1271
1360
  - default logs (raised from `#lock`/`#lock!`):
1272
1361
 
1273
1362
  ```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");
1363
+ "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1364
+ "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1365
+ "[redis_queued_locks.dead_score_reached__reset_acquier_position]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1277
1366
  "[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");
1367
+ "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "acq_time");
1368
+ "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "acq_time");
1369
+ "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1370
+ "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1371
+ "[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "acs_strat");
1283
1372
  ```
1284
1373
 
1285
1374
  - additional logs (raised from `#lock`/`#lock!` with `confg[:log_lock_try] == true`):
1286
1375
 
1287
1376
  ```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");
1377
+ "[redis_queued_locks.try_lock.start]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1378
+ "[redis_queued_locks.try_lock.rconn_fetched]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1379
+ "[redis_queued_locks.try_lock.same_process_conflict_detected]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1380
+ "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status");
1381
+ "[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");
1382
+ "[redis_queued_locks.try_lock.reentrant_lock__work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "spc_status", last_spc_ts);
1383
+ "[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");
1384
+ "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1385
+ "[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1386
+ "[redis_queued_locks.try_lock.get_first_from_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat", "first_acq_id_in_queue");
1387
+ "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1388
+ "[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>");
1389
+ "[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>");
1390
+ "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "acs_strat");
1302
1391
  ```
1303
1392
 
1304
1393
  ---
@@ -1424,7 +1513,6 @@ Detalized event semantics and payload structure:
1424
1513
  <sup>\[[back to top](#table-of-contents)\]</sup>
1425
1514
 
1426
1515
  - **Major**:
1427
- - support for Random Access strategy (non-queued behavior);
1428
1516
  - lock request prioritization;
1429
1517
  - **strict redlock algorithm support** (support for many `RedisClient` instances);
1430
1518
  - `#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
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.7.0
5
+ module RedisQueuedLocks::Acquier::AcquireLock::InstrVisitor
6
+ extend self
7
+
8
+ # @param instrumenter [#notify]
9
+ # @param instr_sampled [Boolean]
10
+ # @param lock_key [String]
11
+ # @param ttl [Integer, NilClass]
12
+ # @param acq_id [String]
13
+ # @param ts [Numeric]
14
+ # @param acq_time [Numeric]
15
+ # @param instrument [NilClass,Any]
16
+ # @return [void]
17
+ #
18
+ # @api private
19
+ # @since 1.7.0
20
+ def extendable_reentrant_lock_obtained(
21
+ instrumenter,
22
+ instr_sampled,
23
+ lock_key,
24
+ ttl,
25
+ acq_id,
26
+ ts,
27
+ acq_time,
28
+ instrument
29
+ )
30
+ return unless instr_sampled
31
+ instrumenter.notify('redis_queued_locks.extendable_reentrant_lock_obtained', {
32
+ lock_key:, ttl:, acq_id:, ts:, acq_time:, instrument:
33
+ }) rescue nil
34
+ end
35
+
36
+ # @param instrumenter [#notify]
37
+ # @param instr_sampled [Boolean]
38
+ # @param lock_key [String]
39
+ # @param ttl [Integer, NilClass]
40
+ # @param acq_id [String]
41
+ # @param ts [Numeric]
42
+ # @param acq_time [Numeric]
43
+ # @param instrument [NilClass,Any]
44
+ # @return [void]
45
+ #
46
+ # @api private
47
+ # @since 1.7.0
48
+ def reentrant_lock_obtained(
49
+ instrumenter,
50
+ instr_sampled,
51
+ lock_key,
52
+ ttl,
53
+ acq_id,
54
+ ts,
55
+ acq_time,
56
+ instrument
57
+ )
58
+ return unless instr_sampled
59
+ instrumenter.notify('redis_queued_locks.reentrant_lock_obtained', {
60
+ lock_key:, ttl:, acq_id:, ts:, acq_time:, instrument:
61
+ }) rescue nil
62
+ end
63
+
64
+ # @param instrumenter [#notify]
65
+ # @param instr_sampled [Boolean]
66
+ # @param lock_key [String]
67
+ # @param ttl [Integer, NilClass]
68
+ # @param acq_id [String]
69
+ # @param ts [Numeric]
70
+ # @param acq_time [Numeric]
71
+ # @param instrument [NilClass,Any]
72
+ # @return [void]
73
+ #
74
+ # @api private
75
+ # @since 1.7.0
76
+ def lock_obtained(
77
+ instrumenter,
78
+ instr_sampled,
79
+ lock_key,
80
+ ttl,
81
+ acq_id,
82
+ ts,
83
+ acq_time,
84
+ instrument
85
+ )
86
+ return unless instr_sampled
87
+ instrumenter.notify('redis_queued_locks.lock_obtained', {
88
+ lock_key:, ttl:, acq_id:, ts:, acq_time:, instrument:
89
+ }) rescue nil
90
+ end
91
+
92
+ # @param instrumenter [#notify]
93
+ # @param instr_sampled [Boolean]
94
+ # @param lock_key [String]
95
+ # @param ttl [Integer, NilClass]
96
+ # @param acq_id [String]
97
+ # @param ts [Numeric]
98
+ # @param acq_time [Numeric]
99
+ # @param hold_time [Numeric]
100
+ # @param instrument [NilClass,Any]
101
+ # @return [void]
102
+ #
103
+ # @api private
104
+ # @since 1.7.0
105
+ def reentrant_lock_hold_completes(
106
+ instrumenter,
107
+ instr_sampled,
108
+ lock_key,
109
+ ttl,
110
+ acq_id,
111
+ ts,
112
+ acq_time,
113
+ hold_time,
114
+ instrument
115
+ )
116
+ return unless instr_sampled
117
+ instrumenter.notify('redis_queued_locks.reentrant_lock_hold_completes', {
118
+ hold_time:, ttl:, acq_id:, ts:, lock_key:, acq_time:, instrument:
119
+ }) rescue nil
120
+ end
121
+
122
+ # @param instrumenter [#notify]
123
+ # @param instr_sampled [Boolean]
124
+ # @param lock_key [String]
125
+ # @param ttl [Integer, NilClass]
126
+ # @param acq_id [String]
127
+ # @param ts [Numeric]
128
+ # @param acq_time [Numeric]
129
+ # @param hold_time [Numeric]
130
+ # @param instrument [NilClass,Any]
131
+ # @return [void]
132
+ #
133
+ # @api private
134
+ # @since 1.7.0
135
+ def lock_hold_and_release(
136
+ instrumenter,
137
+ instr_sampled,
138
+ lock_key,
139
+ ttl,
140
+ acq_id,
141
+ ts,
142
+ acq_time,
143
+ hold_time,
144
+ instrument
145
+ )
146
+ return unless instr_sampled
147
+ instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
148
+ hold_time:, ttl:, acq_id:, ts:, lock_key:, acq_time:, instrument:
149
+ }) rescue nil
150
+ end
151
+ end