redis_queued_locks 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +88 -36
  3. data/.claude/rules/acquirer.md +11 -7
  4. data/.claude/rules/arguments.md +2 -2
  5. data/.claude/rules/logic.md +5 -4
  6. data/.claude/rules/tests.md +4 -4
  7. data/.claude/rules/type-checking.md +2 -3
  8. data/.claude/rules/visitors.md +8 -6
  9. data/.claude/skills/rql-code-review/SKILL.md +25 -20
  10. data/.ruby-version +1 -1
  11. data/CHANGELOG.md +24 -1
  12. data/CLAUDE.md +5 -4
  13. data/README.md +247 -96
  14. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
  15. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +582 -276
  16. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +26 -26
  17. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +13 -13
  18. data/lib/redis_queued_locks/acquirer/lock_series/instr_visitor.rb +243 -0
  19. data/lib/redis_queued_locks/acquirer/lock_series/log_visitor.rb +472 -0
  20. data/lib/redis_queued_locks/acquirer/lock_series/release_lock_series.rb +181 -0
  21. data/lib/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rb +1051 -0
  22. data/lib/redis_queued_locks/acquirer/lock_series.rb +948 -0
  23. data/lib/redis_queued_locks/acquirer.rb +1 -1
  24. data/lib/redis_queued_locks/client.rb +55 -25
  25. data/lib/redis_queued_locks/version.rb +2 -2
  26. data/redis_queued_locks.gemspec +21 -14
  27. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +1 -1
  28. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +106 -5
  29. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +1 -1
  30. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
  31. data/sig/redis_queued_locks/acquirer/lock_series/instr_visitor.rbs +102 -0
  32. data/sig/redis_queued_locks/acquirer/lock_series/log_visitor.rbs +154 -0
  33. data/sig/redis_queued_locks/acquirer/lock_series/release_lock_series.rbs +37 -0
  34. data/sig/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rbs +198 -0
  35. data/sig/redis_queued_locks/acquirer/lock_series.rbs +106 -0
  36. data/sig/redis_queued_locks/client.rbs +70 -3
  37. metadata +32 -11
  38. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -61
  39. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -104
  40. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -513
data/README.md CHANGED
@@ -29,8 +29,8 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
29
29
  - [Usage](#usage)
30
30
  - [lock](#lock---obtain-a-lock)
31
31
  - [lock!](#lock---exceptional-lock-obtaining)
32
- - [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
33
- - [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
32
+ - [lock_series](#lock_series---acquire-a-series-of-locks)
33
+ - [lock_series!](#lock_series---exceptional-lock_series)
34
34
  - [lock_info](#lock_info)
35
35
  - [queue_info](#queue_info)
36
36
  - [locked?](#locked)
@@ -85,8 +85,12 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
85
85
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
86
86
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
87
87
  - ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
88
+ - ["redis_queued_locks.lock_series_intermediate_lock_obtained"](#redis_queued_lockslock_series_intermediate_lock_obtained)
89
+ - ["redis_queued_locks.lock_series_lock_obtained"](#redis_queued_lockslock_series_lock_obtained)
90
+ - ["redis_queued_locks.lock_series_lock_obtaining_failed"](#redis_queued_lockslock_series_lock_obtaining_failed)
88
91
  - ["redis_queued_locks.lock_series_obtained"](#redis_queued_lockslock_series_obtained)
89
92
  - ["redis_queued_locks.lock_series_hold_and_release"](#redis_queued_lockslock_series_hold_and_release)
93
+ - ["redis_queued_locks.lock_series_obtaining_failed"](#redis_queued_lockslock_series_obtaining_failed)
90
94
  - ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
91
95
  - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
92
96
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
@@ -376,8 +380,16 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
376
380
  # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
377
381
  # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
378
382
  # - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
379
- # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
383
+ # - "[redis_queued_locks.start_try_to_lock_series_cycle]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
384
+ # - "[redis_queued_locks.dead_score_reached__reset_series_position]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
385
+ # - "[redis_queued_locks.lock_series_intermediate_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
386
+ # - "[redis_queued_locks.lock_series_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
387
+ # - "[redis_queued_locks.lock_series_lock_obtaining_failed]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
388
+ # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "reentrant_lock_keys");
389
+ # - "[redis_queued_locks.lock_series_obtaining_failed]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result", "failed_lock_key");
390
+ # - "[redis_queued_locks.dequeue_from_lock_series_queues]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
380
391
  # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
392
+ # - "[redis_queued_locks.decrease_lock_series]" (logs "lock_keys", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
381
393
  # - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
382
394
  # - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
383
395
  # - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
@@ -468,8 +480,8 @@ end
468
480
 
469
481
  - [lock](#lock---obtain-a-lock)
470
482
  - [lock!](#lock---exceptional-lock-obtaining)
471
- - [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
472
- - [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
483
+ - [lock_series](#lock_series---acquire-a-series-of-locks)
484
+ - [lock_series!](#lock_series---exceptional-lock_series)
473
485
  - [lock_info](#lock_info)
474
486
  - [queue_info](#queue_info)
475
487
  - [locked?](#locked)
@@ -528,7 +540,7 @@ def lock(
528
540
  raise_errors: false,
529
541
  fail_fast: false,
530
542
  conflict_strategy: config['default_conflict_strategy'],
531
- read_write_mode: :write,
543
+ rw_mode: :write,
532
544
  access_strategy: config['default_access_strategy'],
533
545
  priority: config['default_lock_priority'],
534
546
  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config['uniq_identifier'] proc;
@@ -637,7 +649,7 @@ def lock(
637
649
  - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
638
650
  - `:dead_locking` - fail with deadlock exception;
639
651
  - See [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) documentation section for details;
640
- - `read_write_mode` - (optional) - `[Symbol]`
652
+ - `rw_mode` - (optional) - `[Symbol]`
641
653
  - The lock mode (read/write lock semantics);
642
654
  - `:write` by default;
643
655
  - Modes:
@@ -799,7 +811,7 @@ Return value:
799
811
  { ok: false, result: :timeout_reached } # see <timeout> option
800
812
  { ok: false, result: :retry_limit_reached } # see <retry_count> option
801
813
  { ok: false, result: :conflict_dead_lock } # see <conflict_strategy> option for details (:dead_locking strategy)
802
- { ok: false, result: :conflict_lock_upgrade } # read-to-write lock upgrade (see <read_write_mode> and <conflict_strategy> options)
814
+ { ok: false, result: :conflict_lock_upgrade } # read-to-write lock upgrade (see <rw_mode> and <conflict_strategy> options)
803
815
  { ok: false, result: :fail_fast_no_try } # see <fail_fast> option
804
816
  { ok: false, result: :fail_fast_after_try } # see <fail_fast> option
805
817
  ```
@@ -989,7 +1001,7 @@ def lock!(
989
1001
  access_strategy: config['default_access_strategy'],
990
1002
  priority: config['default_lock_priority'],
991
1003
  conflict_strategy: config['default_conflict_strategy'],
992
- read_write_mode: :write,
1004
+ rw_mode: :write,
993
1005
  log_sampling_enabled: config['log_sampling_enabled'],
994
1006
  log_sampling_percent: config['log_sampling_percent'],
995
1007
  log_sampler: config['log_sampler'],
@@ -1006,39 +1018,56 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
1006
1018
 
1007
1019
  ---
1008
1020
 
1009
- #### #lock_series - (PoC) acquire a series of locks
1021
+ #### #lock_series - acquire a series of locks
1010
1022
 
1011
1023
  <sup>\[[back to top](#usage)\]</sup>
1012
1024
 
1013
- > (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future.
1014
-
1015
- `lock_series` - acquire a series of locks simultaniously and hold them all while your block of code is executing
1016
- or (if block is not passed) while all locks will not expire.
1017
-
1018
- If it is not possible to obtain all locks at the same time (taking into queue ttl, retry attempts count for each lock, etc) no any lock will be acquired.
1019
-
1020
- Method options is the same as for `lock` method: `retry_count`, `retry_jitter`, `ttl`, `queue_ttl`, etc. Each option will be
1021
- applied with the same value to the each lock acquirement process (for each lock in a series separately).
1022
-
1023
- **IMPORTANT DETAILS AND LIMITATIONS**:
1024
- - (this behavior will be reworked with another algorithm in future);
1025
- - each lock will be released with the next formula:
1026
- - `lock position with reverse numbering` * `ttl`
1027
- - (positions exmaple: ["a", "b", "c"] => "a" position is 3, "b" position is 2, "c" position is 1);
1028
- - this means that in case of 3 locks (`lock_series("x", "y", "z", ttl: 5_000`) with `5 seconds TTL` your locks will be with the next TTL:
1029
- - `"x"` - 15 seconds
1030
- - `"y"` - 10 seconds
1031
- - `"z"` - 5 seconds
1032
- - when your block of code has completed their execution - all locks will be released immediatly;
1033
- - when the series fails (any lock can not be obtained, with or without an exception) - the locks that are already
1034
- obtained by the series are released immediatly (`acquired_locks` of the failure result lists them);
1025
+ `lock_series` - obtain a group of locks atomically: all locks of the series are obtained at the same moment with the same TTL
1026
+ and are held together while your block of code is executing (or, if a block is not passed, while the locks live).
1027
+
1028
+ **Guarantees**:
1029
+ - **all or nothing**: one transaction obtains all locks of the series; an attempt that can not obtain some lock obtains nothing,
1030
+ so a failed series leaves no obtained locks (and no lock requests in the lock queues);
1031
+ - **all locks at the same time**: all locks are obtained with the same TTL (`ttl:`) at the same moment, so the block of code
1032
+ never works with a partially held series and `timed: true` limits it by the real lifetime of the locks;
1033
+ - **no hold-and-wait**: nothing is held while the series waits for its locks: the free locks of the series are not locked
1034
+ and their TTL is not spent on the waiting;
1035
+ - **no deadlocks between lock series**: the order of the lock names does not matter (`lock_series("a", "b")` and
1036
+ `lock_series("b", "a")` work in parallel threads): the lock request of the series has one position in all its lock queues,
1037
+ so the lock requests wait only for earlier lock requests. Your own nested logic is out of it: code that holds a lock and
1038
+ then waits for a series still can wait for a lock that is needed by another acquirer (the waiting is limited by the limits
1039
+ of the series);
1040
+
1041
+ **How the options work**:
1042
+ - method options are the same as for the `#lock` method: `ttl`, `queue_ttl`, `retry_count`, `retry_delay`, `retry_jitter`, etc;
1043
+ - limits are the limits of **the whole series**, not of each lock: `timeout:` is one time period for all locks, `retry_count:` is
1044
+ the number of attempts to obtain all locks, `queue_ttl:` is the lifetime of the lock request of the series;
1045
+ - `ttl:` is the TTL of each lock of the series (the same value for all locks);
1046
+ - `rw_mode:` is the same for all locks of the series (see [Read/Write Locks](#readwrite-locks));
1047
+ - `priority:` is the same for all locks of the series (see [Lock Request Prioritization](#lock-request-prioritization)):
1048
+ the lock request of the series has this priority in all its lock queues;
1049
+ - `conflict_strategy:` works per lock: reentrant locks (already obtained by your outer logic, `:work_through`/`:extendable_work_through`)
1050
+ are not obtained and not released by the series: the extended ones (`:extendable_work_through`) get their TTL back after the block,
1051
+ like in the `#lock` method; a lock with a failed conflict (`:dead_locking`, read-to-write lock upgrade) fails the whole series;
1052
+ - `fail_fast: true` fails the series on the first lock that is already obtained;
1053
+ - a duplicated lock name is the same lock (`lock_series("a", "b", "a")` works with two locks); at least one lock name is required
1054
+ (`RedisQueuedLocks::ArgumentError`);
1055
+
1056
+ **Details and limitations**:
1057
+ - when your block of code has completed its execution (with or without an exception) all locks obtained by the series are released;
1058
+ - an interrupted series (`Timeout.timeout` of your logic, `Thread#kill`, a Redis error) releases its locks and drops its lock requests,
1059
+ when it is possible (a dead process or an unavailable Redis: the locks live till their TTL, the lock requests till their `queue_ttl`);
1035
1060
  - the series releases only locks obtained by itself and only if they are still owned by the current acquirer:
1036
- - reentrant locks (already obtained by your outer logic, `conflict_strategy: :work_through`/`:extendable_work_through`) are kept;
1037
- - a lock that is expired during your block and obtained by another acquirer is kept;
1038
- - `read_write_mode:` is the same for all locks of the series (see [Read/Write Locks](#readwrite-locks));
1039
- - `priority:` is the same for all locks of the series (see [Lock Request Prioritization](#lock-request-prioritization));
1040
- - if you did not pass a block - all locks will be released with their forumla-based TTL described above;
1041
- - **HOW WILL THIS CASE WORK IN FUTURE RELESAE**: all locks will have the same TTL (`5 seconds` for our example, not `15/10/5 seconds`);
1061
+ a lock that is expired during your block and obtained by another acquirer is kept;
1062
+ - if you did not pass a block - all locks live till their TTL;
1063
+ - locks of the series are regular locks with regular keys: `#lock_info`, `#unlock`, `#extend_lock_ttl`, `#clear_locks` and the swarm
1064
+ work with each of them separately;
1065
+ - `:queued` access strategy: a series that is ahead of other lock requests makes them wait even for its free locks (the waiting
1066
+ series is the next obtainer of these locks): one long-waiting series delays the lock requests of all its locks for its waiting time
1067
+ (no longer than its `queue_ttl`, `timeout` and `retry_count` limits allow);
1068
+ - `:random` access strategy: the series is obtained only when all its locks are free at the same moment, so a big series can
1069
+ wait for a long time under load (lock requests of the `:random` strategy have no reserved turn);
1070
+ - all processes that work with the same locks should use the same RQL version: older versions obtain the series lock by lock;
1042
1071
 
1043
1072
  ##### How to use:
1044
1073
 
@@ -1049,12 +1078,27 @@ applied with the same value to the each lock acquirement process (for each lock
1049
1078
  client.lock_series("a", "b", "c") { ...some_code... }
1050
1079
 
1051
1080
  # result-oriented way:
1052
- # - (block is passed)
1081
+ # - (block is not passed)
1082
+ client.lock_series('x', 'y', 'z', ttl: 5_000)
1083
+ # =>
1084
+ {
1085
+ ok: true,
1086
+ result: {
1087
+ lock_series: ["x", "y", "z"], # your locks
1088
+ rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], # internal RQL lock-keys of your locks
1089
+ acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", # the acquirer of the locks
1090
+ hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", # the host of the locks
1091
+ ts: 1769506100.676884, # the time when the locks were obtained, epoch
1092
+ ttl: 5000 # the ttl of each lock, milliseconds
1093
+ }
1094
+ }
1095
+
1096
+ # - (detailed result, block is passed)
1053
1097
  detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true) { 1 + 2 }
1054
1098
  detailed_result # =>
1055
1099
  {
1056
1100
  yield_result: 3, # result of your block of code
1057
- lock_release_strategy: :immediate_release_after_yield, # (block is passed)
1101
+ locks_release_strategy: :immediate_release_after_yield, # (block is passed)
1058
1102
  locks_released_at: 1769506100.676884, # (instrumentation) release time, epoch
1059
1103
  locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
1060
1104
  locks_hold_time: 0.65, # (instrumentation) lock series hold period (hold preiod of all required locks), milliseconds
@@ -1062,12 +1106,12 @@ detailed_result # =>
1062
1106
  rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"] # internal RQL lock-keys of your locks
1063
1107
  }
1064
1108
 
1065
- # - (block is not passed)
1109
+ # - (detailed result, block is not passed)
1066
1110
  detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true)
1067
1111
  detailed_result # =>
1068
1112
  {
1069
1113
  yield_result: nil, # block is not given - nothing to return
1070
- lock_release_strategy: :redis_key_ttl, # block is not given - locks are living in redis with their TTL
1114
+ locks_release_strategy: :redis_key_ttl, # block is not given - locks are living in redis with their TTL
1071
1115
  locks_released_at: nil, # (instrumentation) # block is not given - locks are living in redis with their TTL
1072
1116
  locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
1073
1117
  locks_hold_time: nil, # (instrumentation) # block is not given - locks are living in redis with their TTL
@@ -1084,53 +1128,71 @@ detailed_result # =>
1084
1128
  client.lock('a', ttl: 10_000_000)
1085
1129
 
1086
1130
  # - try to bobtain series of locks with `a` inside the series (`a` can not be obtained for now)
1087
- client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
1131
+ client.lock_series('c', 'a', 'b') # ... `a` is still obtained
1088
1132
  # =>
1089
1133
  {
1090
1134
  ok: false,
1091
1135
  result: {
1092
1136
  error: :failed_to_acquire_lock_series,
1093
- detailed_errors: { 'a' => { ok: false, result: :retry_limit_reached } },
1137
+ detailed_errors: { 'a' => { ok: false, result: :retry_limit_reached } }, # (empty without `failed_on_lock`)
1094
1138
  lock_series: ["c", "a", "b"],
1095
- acquired_locks: ["c"],
1096
- missing_locks: ["a", "b"],
1097
- failed_on_lock: ["a"],
1139
+ acquired_locks: [], # always empty: nothing is obtained by the failed series
1140
+ missing_locks: ["c", "a", "b"],
1141
+ failed_on_lock: "a", # the first lock that blocked the last completed attempt (`nil`: unknown)
1098
1142
  }
1099
1143
  }
1100
1144
  ```
1101
1145
 
1102
- ###### New instrumentaiton events:
1103
-
1104
- - `rw_mode` is the mode of the series (`read_write_mode`, the same for all locks of the series);
1146
+ ###### Instrumentation events:
1147
+
1148
+ - `rw_mode` is the mode of the series (the same for all locks of the series);
1149
+ - `#lock` events (`lock_obtained`, `lock_obtaining_failed`, ...) are not raised for the locks of the series, each lock has its own series events
1150
+ with the series context in the name (`lock_key` is the lock, `lock_keys` are all locks of the series):
1151
+ - `lock_series_intermediate_lock_obtained` - intermediate obtaining: the lock passed all checks of an attempt of the series (the lock is
1152
+ free and the lock request of the series is next). Nothing is held yet: the series can still wait for its other locks or fail.
1153
+ Raised once per lock of the series (not per attempt), in the order of the lock names up to the lock that blocks the series;
1154
+ - `lock_series_lock_obtained` - full obtaining: the lock is really obtained, all locks of the series are held;
1155
+ - `lock_series_lock_obtaining_failed` - the lock that blocked the failed series, once per series (not raised when the blocking lock
1156
+ is unknown: no attempt found it, see `failed_lock_key` of `lock_series_obtaining_failed`);
1157
+ - reentrant locks are not obtained by the series: they have no lock events (see `reentrant_lock_keys` of the series events);
1105
1158
  - see [Instrumentation Events](#instrumentation-events) for the payload details;
1106
1159
 
1107
1160
  ```ruby
1108
- # NEW instrumentation events (examples)
1109
- "redis_queued_locks.lock_series_obtained" => # {lock_keys: ["rql:lock:s", "rql:lock:t", "rql:lock:u"], rw_mode: :write, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, instrument: nil}
1110
- "redis_queued_locks.lock_series_hold_and_release" => # {lock_keys: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], rw_mode: :write, hold_time: 0.29, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, instrument: nil}
1161
+ # instrumentation events (examples)
1162
+ "redis_queued_locks.lock_series_intermediate_lock_obtained" => # {lock_key: "rql:lock:s", lock_keys: ["rql:lock:s", "rql:lock:t"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, instrument: nil}
1163
+ "redis_queued_locks.lock_series_lock_obtained" => # {lock_key: "rql:lock:s", lock_keys: ["rql:lock:s", "rql:lock:t"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, instrument: nil}
1164
+ "redis_queued_locks.lock_series_lock_obtaining_failed" => # {lock_key: "rql:lock:y", lock_keys: ["rql:lock:x", "rql:lock:y"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, result: :retry_limit_reached, instrument: nil}
1165
+ "redis_queued_locks.lock_series_obtained" => # {lock_keys: ["rql:lock:s", "rql:lock:t", "rql:lock:u"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, reentrant_lock_keys: [], instrument: nil}
1166
+ "redis_queued_locks.lock_series_hold_and_release" => # {lock_keys: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], rw_mode: :write, priority: 0, hold_time: 0.29, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, reentrant_lock_keys: [], instrument: nil}
1167
+ "redis_queued_locks.lock_series_obtaining_failed" => # {lock_keys: ["rql:lock:x", "rql:lock:y"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, result: :retry_limit_reached, failed_lock_key: "rql:lock:y", instrument: nil}
1111
1168
  ```
1112
1169
 
1113
- ###### New logs:
1170
+ ###### Logs:
1114
1171
 
1115
- - `rw_mode` is the mode of the series (`read_write_mode`, the same for all locks of the series);
1172
+ - `rw_mode` is the mode of the series (the same for all locks of the series);
1116
1173
  - see [Logging](#logging) for the logged keys;
1117
1174
 
1118
1175
  ```shell
1119
- # NEW logs (examples)
1176
+ # logs (examples)
1120
1177
  [redis_queued_locks.start_lock_series_obtaining] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
1121
- [redis_queued_locks.lock_series_obtained] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms)
1122
- [redis_queued_locks.expire_lock_series] lock_keys => '["rql:lock:x", "rql:lock:y", "rql:lock:z"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
1178
+ [redis_queued_locks.start_try_to_lock_series_cycle] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
1179
+ [redis_queued_locks.lock_series_intermediate_lock_obtained] lock_key => 'rql:lock:a' lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms)
1180
+ [redis_queued_locks.lock_series_lock_obtained] lock_key => 'rql:lock:a' lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms)
1181
+ [redis_queued_locks.lock_series_obtained] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms) reentrant_lock_keys => '[]'
1182
+ [redis_queued_locks.expire_lock_series] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
1123
1183
  ```
1124
1184
 
1125
1185
  ----
1126
1186
 
1127
- #### #lock_series! - (PoC) exceptional `lock_series`
1187
+ #### #lock_series! - exceptional `lock_series`
1128
1188
 
1129
1189
  <sup>\[[back to top](#usage)\]</sup>
1130
1190
 
1131
- > (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future (very-very soon)
1191
+ Raises the same errors as `#lock!` when the series can not be obtained (`RedisQueuedLocks::LockAlreadyObtainedError`,
1192
+ `RedisQueuedLocks::ConflictLockObtainError`, `RedisQueuedLocks::LockAcquirementRetryLimitError`,
1193
+ `RedisQueuedLocks::LockAcquirementTimeoutError`).
1132
1194
 
1133
- For more details see `lock_series` [docs](#lock_series---poc-acquire-a-series-of-locks)
1195
+ For more details see `lock_series` [docs](#lock_series---acquire-a-series-of-locks)
1134
1196
 
1135
1197
  ```ruby
1136
1198
  client.lock_series!("x", "y", "z") { ...some_code... }
@@ -1229,18 +1291,18 @@ rql.lock_info("your_lock_name")
1229
1291
 
1230
1292
  ```ruby
1231
1293
  # <for read/write locks>
1232
- # (see `read_write_mode:` kwarg attribute of #lock/#lock! methods and "Read/Write Locks" documentation section)
1294
+ # (see `rw_mode:` kwarg attribute of #lock/#lock! methods and "Read/Write Locks" documentation section)
1233
1295
  # (lock requests are not included: use #queue_info to see the queued readers and writers)
1234
1296
 
1235
1297
  # -> case 1: the lock is held by readers, writers and readers are waiting in the lock queues;
1236
1298
 
1237
1299
  # process#1 and process#2 (readers, work in parallel):
1238
- rql.lock("your_lock_name", read_write_mode: :read, ttl: 5_000, meta: { "kek" => "pek" }) { ... }
1239
- rql.lock("your_lock_name", read_write_mode: :read, ttl: 3_000) { ... }
1300
+ rql.lock("your_lock_name", rw_mode: :read, ttl: 5_000, meta: { "kek" => "pek" }) { ... }
1301
+ rql.lock("your_lock_name", rw_mode: :read, ttl: 3_000) { ... }
1240
1302
  # process#3 (writer, waits for all readers in the write lock queue):
1241
- rql.lock("your_lock_name", read_write_mode: :write) { ... }
1303
+ rql.lock("your_lock_name", rw_mode: :write) { ... }
1242
1304
  # process#4 (reader, waits for the earlier writer in the read lock queue):
1243
- rql.lock("your_lock_name", read_write_mode: :read) { ... }
1305
+ rql.lock("your_lock_name", rw_mode: :read) { ... }
1244
1306
 
1245
1307
  rql.lock_info("your_lock_name")
1246
1308
 
@@ -1445,7 +1507,7 @@ rql.unlock("your_lock_name")
1445
1507
 
1446
1508
  - releases the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
1447
1509
  other read locks, the write lock and lock requests are not affected (`#unlock` releases the lock entirely);
1448
- - suitable for read locks obtained without a block of code (`lock(..., read_write_mode: :read)` without a block);
1510
+ - suitable for read locks obtained without a block of code (`lock(..., rw_mode: :read)` without a block);
1449
1511
  - the current acquirer is calculated in the same way as for `#lock` (`#current_acquirer_id`: process, thread, fiber, ractor, identity),
1450
1512
  so call it from the same thread/fiber that obtained the read lock;
1451
1513
  - the read lock of a different acquirer can be released via `:acquirer_id` (for example, extracted from `#lock_info`/`#locks_info`);
@@ -1463,7 +1525,7 @@ rql.unlock("your_lock_name")
1463
1525
  - raises the `"redis_queued_locks.explicit_read_lock_release"` instrumentation event;
1464
1526
 
1465
1527
  ```ruby
1466
- rql.lock('report', read_write_mode: :read, ttl: 30_000) # => obtained without a block
1528
+ rql.lock('report', rw_mode: :read, ttl: 30_000) # => obtained without a block
1467
1529
  # ...
1468
1530
  rql.unlock_read('report')
1469
1531
  # =>
@@ -1779,7 +1841,7 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1779
1841
  - `milliseconds` - (required) `[Integer]`
1780
1842
  - how many milliseconds should be added to the lock's TTL;
1781
1843
  - should be a positive `Integer`;
1782
- - `:read_write_mode` - (optional) `[Symbol]`
1844
+ - `:rw_mode` - (optional) `[Symbol]`
1783
1845
  - `:write` (default) - extends the write lock (the behavior described below);
1784
1846
  - `:read` - extends the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
1785
1847
  - the read lock expiration, the read locks registry TTL and the read lock data TTL are extended;
@@ -2774,19 +2836,19 @@ rql.lock("user_#{user.id}", priority: :high) { update_profile } # priority: 50
2774
2836
 
2775
2837
  <sup>\[[back to top](#table-of-contents)\]</sup>
2776
2838
 
2777
- - `read_write_mode:` attribute of `#lock` and `#lock!`:
2839
+ - `rw_mode:` attribute of `#lock` and `#lock!`:
2778
2840
  - `:write` (**default**) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
2779
2841
  - `:read` - shared lock: waits for the write lock only (read locks of different acquirers work in parallel);
2780
2842
 
2781
2843
  ```ruby
2782
2844
  # readers work in parallel
2783
- rql.lock('report', read_write_mode: :read) { read_report }
2845
+ rql.lock('report', rw_mode: :read) { read_report }
2784
2846
 
2785
2847
  # writer waits for all readers (and readers wait for the writer)
2786
- rql.lock('report', read_write_mode: :write) { rebuild_report }
2848
+ rql.lock('report', rw_mode: :write) { rebuild_report }
2787
2849
 
2788
2850
  # write mode is default:
2789
- rql.lock('report') { rebuild_report } # read_write_mode: :write by default
2851
+ rql.lock('report') { rebuild_report } # rw_mode: :write by default
2790
2852
  ```
2791
2853
 
2792
2854
  - lock request ordering (`access_strategy`):
@@ -2807,11 +2869,11 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
2807
2869
  - `#unlock` releases the lock entirely: the write lock, all read locks (with their data) and all lock requests;
2808
2870
  - `#unlock_read` (aka `#release_read_lock`) releases the read lock of the current acquirer
2809
2871
  (suitable for read locks obtained without a block), or the read lock of the acquirer passed via `acquirer_id:`;
2810
- - `#extend_lock_ttl(lock_name, milliseconds, read_write_mode: :read)` extends the read lock of the current acquirer only;
2872
+ - `#extend_lock_ttl(lock_name, milliseconds, rw_mode: :read)` extends the read lock of the current acquirer only;
2811
2873
  `all_read_locks: true` extends all live read locks of the lock;
2812
- - `#lock_series` with `read_write_mode: :read` obtains a series of read locks (the mode is the same for all locks of the series):
2874
+ - `#lock_series` with `rw_mode: :read` obtains a series of read locks (the mode is the same for all locks of the series):
2813
2875
  - read lock series of different acquirers work in parallel, writers wait for them;
2814
- - after the block (or when the series fails) only the read locks of the current acquirer are released;
2876
+ - after the block only the read locks of the current acquirer are released;
2815
2877
  - reentrant read locks of the series (already obtained by the outer logic of the same acquirer) are not released by the series;
2816
2878
  - reentrant locks (the same acquirer, see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)):
2817
2879
  - read lock request under the obtained read lock or write lock is processed as a reentrant lock of the obtained lock
@@ -2931,12 +2993,21 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
2931
2993
  "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2932
2994
  ```
2933
2995
 
2934
- - lock series logs (raised from `#lock_series`/`#lock_series!`, PoC; `rw_mode` is the mode of the series):
2996
+ - lock series logs (raised from `#lock_series`/`#lock_series!`; `rw_mode` is the mode of the series; with `log_lock_try` each
2997
+ attempt of the series writes the `try_lock.*` logs of the list above with its own `lock_key`, one log per lock of the series):
2935
2998
 
2936
2999
  ```ruby
2937
3000
  "[redis_queued_locks.start_lock_series_obtaining]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2938
- "[redis_queued_locks.lock_series_obtained]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
3001
+ "[redis_queued_locks.start_try_to_lock_series_cycle]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
3002
+ "[redis_queued_locks.dead_score_reached__reset_series_position]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
3003
+ "[redis_queued_locks.lock_series_intermediate_lock_obtained]" # (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
3004
+ "[redis_queued_locks.lock_series_lock_obtained]" # (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
3005
+ "[redis_queued_locks.lock_series_lock_obtaining_failed]" # (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
3006
+ "[redis_queued_locks.lock_series_obtained]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "reentrant_lock_keys");
3007
+ "[redis_queued_locks.lock_series_obtaining_failed]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result", "failed_lock_key");
3008
+ "[redis_queued_locks.dequeue_from_lock_series_queues]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2939
3009
  "[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
3010
+ "[redis_queued_locks.decrease_lock_series]" # (logs "lock_keys", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2940
3011
  ```
2941
3012
 
2942
3013
  - operation logs (one log line per operation that changes Redis keys, with the time of the whole operation; the same data as
@@ -2988,8 +3059,16 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
2988
3059
  # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2989
3060
  # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2990
3061
  # - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2991
- # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
3062
+ # - "[redis_queued_locks.start_try_to_lock_series_cycle]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
3063
+ # - "[redis_queued_locks.dead_score_reached__reset_series_position]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
3064
+ # - "[redis_queued_locks.lock_series_intermediate_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
3065
+ # - "[redis_queued_locks.lock_series_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
3066
+ # - "[redis_queued_locks.lock_series_lock_obtaining_failed]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
3067
+ # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "reentrant_lock_keys");
3068
+ # - "[redis_queued_locks.lock_series_obtaining_failed]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result", "failed_lock_key");
3069
+ # - "[redis_queued_locks.dequeue_from_lock_series_queues]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2992
3070
  # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
3071
+ # - "[redis_queued_locks.decrease_lock_series]" (logs "lock_keys", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2993
3072
  # - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
2994
3073
  # - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
2995
3074
  # - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
@@ -3131,8 +3210,12 @@ List of instrumentation events
3131
3210
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
3132
3211
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
3133
3212
  - ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
3213
+ - ["redis_queued_locks.lock_series_intermediate_lock_obtained"](#redis_queued_lockslock_series_intermediate_lock_obtained)
3214
+ - ["redis_queued_locks.lock_series_lock_obtained"](#redis_queued_lockslock_series_lock_obtained)
3215
+ - ["redis_queued_locks.lock_series_lock_obtaining_failed"](#redis_queued_lockslock_series_lock_obtaining_failed)
3134
3216
  - ["redis_queued_locks.lock_series_obtained"](#redis_queued_lockslock_series_obtained)
3135
3217
  - ["redis_queued_locks.lock_series_hold_and_release"](#redis_queued_lockslock_series_hold_and_release)
3218
+ - ["redis_queued_locks.lock_series_obtaining_failed"](#redis_queued_lockslock_series_obtaining_failed)
3136
3219
  - ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
3137
3220
  - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
3138
3221
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
@@ -3142,8 +3225,8 @@ List of instrumentation events
3142
3225
  - ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
3143
3226
 
3144
3227
  What is instrumented (each event has a log line with the same name, see [Logging](#logging)):
3145
- - the lock acquirement (`#lock`/`#lock!`, `#lock_series`/`#lock_series!`): each meaningful outcome (the lock is obtained,
3146
- not obtained, held and released);
3228
+ - the lock acquirement (`#lock`/`#lock!`, `#lock_series`/`#lock_series!`): each meaningful outcome (the lock or the whole
3229
+ series is obtained, not obtained, held and released);
3147
3230
  - each operation that changes Redis keys (`#unlock`, `#unlock_read`, `#clear_locks`, `#clear_locks_of`/`#clear_current_locks`,
3148
3231
  `#extend_lock_ttl`, `#clear_dead_requests`, `#reindex_locks`): one event with the time of the whole operation;
3149
3232
  - lock information queries (`#lock_info`, `#locks`, `#queues`, `#keys`, ...) are not instrumented;
@@ -3169,8 +3252,7 @@ Detalized event semantics and payload structure:
3169
3252
  ##### `"redis_queued_locks.lock_obtaining_failed"`
3170
3253
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3171
3254
  - a moment when the lock acquirement is failed (the lock is not obtained);
3172
- - raised from `#lock`/`#lock!` (with `raise_errors: true` - before the error is raised) and from `#lock_series`/`#lock_series!`
3173
- (for the lock of the series that is not obtained);
3255
+ - raised from `#lock`/`#lock!` (with `raise_errors: true` - before the error is raised);
3174
3256
  - payload:
3175
3257
  - `:lock_key` - `string` - lock name;
3176
3258
  - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
@@ -3250,36 +3332,110 @@ Detalized event semantics and payload structure:
3250
3332
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
3251
3333
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
3252
3334
 
3335
+ ##### `"redis_queued_locks.lock_series_intermediate_lock_obtained"`
3336
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3337
+ - intermediate obtaining: a moment when the lock of the series passed all checks of an attempt of the series (the lock is free and the lock
3338
+ request of the series is next). Nothing is held yet: the series can still wait for its other locks or fail;
3339
+ - raised from `#lock_series`/`#lock_series!` once per lock of the series (not per attempt), in the order of the lock names up to the lock
3340
+ that blocks the series; reentrant locks of the series are not reported;
3341
+ - payload:
3342
+ - `:lock_key` - `string` - the lock of the series;
3343
+ - `:lock_keys` - `array<string>` - all locks of the series;
3344
+ - `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3345
+ - `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3346
+ - `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
3347
+ - `:acq_id` - `string` - lock acquirer identifier;
3348
+ - `:hst_id` - `string` - lock's host identifier;
3349
+ - `:ts` - `float`/`epoch` - the time of the event;
3350
+ - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring up to this moment;
3351
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
3352
+
3353
+ ##### `"redis_queued_locks.lock_series_lock_obtained"`
3354
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3355
+ - full obtaining: a moment when the lock of the series was really obtained (all locks of the series are held);
3356
+ - raised from `#lock_series`/`#lock_series!` for each lock obtained by the series, right before `lock_series_obtained`; reentrant locks of
3357
+ the series are not reported (see `:reentrant_lock_keys` of `lock_series_obtained`);
3358
+ - payload:
3359
+ - `:lock_key` - `string` - the lock of the series;
3360
+ - `:lock_keys` - `array<string>` - all locks of the series;
3361
+ - `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3362
+ - `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3363
+ - `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
3364
+ - `:acq_id` - `string` - lock acquirer identifier;
3365
+ - `:hst_id` - `string` - lock's host identifier;
3366
+ - `:ts` - `float`/`epoch` - the time when the series was obtained;
3367
+ - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
3368
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
3369
+
3370
+ ##### `"redis_queued_locks.lock_series_lock_obtaining_failed"`
3371
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3372
+ - a moment when the series acquirement is failed: the lock of the series that blocked it;
3373
+ - raised from `#lock_series`/`#lock_series!` once per failed series (with `raise_errors: true` - before the error is raised), right before
3374
+ `lock_series_obtaining_failed`; it is not raised when the blocking lock is unknown (no attempt found it);
3375
+ - payload:
3376
+ - `:lock_key` - `string` - the lock that blocked the series;
3377
+ - `:lock_keys` - `array<string>` - all locks of the series;
3378
+ - `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3379
+ - `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3380
+ - `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
3381
+ - `:acq_id` - `string` - lock acquirer identifier;
3382
+ - `:hst_id` - `string` - lock's host identifier;
3383
+ - `:ts` - `float`/`epoch` - the time when the series acquirement was failed;
3384
+ - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
3385
+ - `:result` - `symbol` - the failure result (see `lock_series_obtaining_failed`);
3386
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
3387
+
3253
3388
  ##### `"redis_queued_locks.lock_series_obtained"`
3254
3389
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3255
- - (PoC) a moment when all locks of the series were obtained;
3390
+ - a moment when all locks of the series were obtained;
3256
3391
  - raised from `#lock_series`/`#lock_series!`;
3257
3392
  - payload:
3258
3393
  - `:lock_keys` - `array<string>` - lock names of the series;
3259
- - `:rw_mode` - `symbol` - the mode of the series (`read_write_mode`): `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3394
+ - `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3260
3395
  - `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3261
- - `:ttl` - `integer`/`milliseconds` - the `ttl` option of the series (each lock of the series gets its own ttl,
3262
- see [lock_series](#lock_series---poc-acquire-a-series-of-locks));
3396
+ - `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series (see [lock_series](#lock_series---acquire-a-series-of-locks));
3263
3397
  - `:acq_id` - `string` - lock acquirer identifier;
3264
3398
  - `:hst_id` - `string` - lock's host identifier;
3265
3399
  - `:ts` - `numeric`/`epoch` - the time when the series was obtained;
3266
3400
  - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
3401
+ - `:reentrant_lock_keys` - `array<string>` - the locks of the series that are obtained by your outer logic (reentrant locks:
3402
+ they are not obtained and not released by the series);
3267
3403
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
3268
3404
 
3269
3405
  ##### `"redis_queued_locks.lock_series_hold_and_release"`
3270
3406
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3271
- - (PoC) an event signalizes about the "hold+and+release" process of the series is finished;
3407
+ - an event signalizes about the "hold+and+release" process of the series is finished;
3272
3408
  - raised from `#lock_series`/`#lock_series!` when invoked with a block of code;
3273
3409
  - payload:
3274
3410
  - `:lock_keys` - `array<string>` - lock names of the series;
3275
- - `:rw_mode` - `symbol` - the mode of the series (`read_write_mode`): `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3411
+ - `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3276
3412
  - `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3277
3413
  - `:hold_time` - `float`/`milliseconds` - the series hold time;
3278
- - `:ttl` - `integer`/`milliseconds` - the `ttl` option of the series;
3414
+ - `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
3279
3415
  - `:acq_id` - `string` - lock acquirer identifier;
3280
3416
  - `:hst_id` - `string` - lock's host identifier;
3281
3417
  - `:ts` - `float`/`epoch` - the time when the series was released;
3282
3418
  - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
3419
+ - `:reentrant_lock_keys` - `array<string>` - the locks of the series that are obtained by your outer logic (reentrant locks:
3420
+ they are not obtained and not released by the series);
3421
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
3422
+
3423
+ ##### `"redis_queued_locks.lock_series_obtaining_failed"`
3424
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3425
+ - a moment when the series acquirement is failed (no lock of the series is obtained);
3426
+ - raised from `#lock_series`/`#lock_series!` (with `raise_errors: true` - before the error is raised);
3427
+ - payload:
3428
+ - `:lock_keys` - `array<string>` - lock names of the series;
3429
+ - `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3430
+ - `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3431
+ - `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
3432
+ - `:acq_id` - `string` - lock acquirer identifier;
3433
+ - `:hst_id` - `string` - lock's host identifier;
3434
+ - `:ts` - `float`/`epoch` - the time when the series acquirement was failed;
3435
+ - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
3436
+ - `:result` - `symbol` - the failure result: `:timeout_reached`, `:retry_limit_reached`, `:fail_fast_no_try`, `:fail_fast_after_try`, `:conflict_dead_lock`, `:conflict_lock_upgrade`;
3437
+ - `:failed_lock_key` - `string`/`nil` - the first lock that blocked the last completed attempt of the series (`nil`: unknown:
3438
+ no attempt was completed or some lock state was changed during the attempt);
3283
3439
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
3284
3440
 
3285
3441
  ##### `"redis_queued_locks.explicit_lock_release"`
@@ -3382,11 +3538,6 @@ Detalized event semantics and payload structure:
3382
3538
  the global "timeout request" queue and managed by a single global "timeout wathcer" thread). it should prevent any logic and timeout intersections,
3383
3539
  some GVL-related things and problem situations when the global watcher thread is "dead";
3384
3540
  - **strict redlock algorithm support** (support for many `RedisClient` instances that are fully independent (distributed redis instances));
3385
- - #lock_series` - acquire a series of locks:
3386
- - (NOTE: "Proof of Concept" implementation is ready to use);
3387
- ```ruby
3388
- rql.lock_series('lock_a', 'lock_b', 'lock_c') { puts 'locked' }
3389
- ```
3390
3541
  - an ability to release all locks and all requests of the concrete acquirer id or host id (or both in validation-orianted combination);
3391
3542
  - detailed lock informotion inside the error object in cases of exceptions (at the moment we have this info inside the error message only that hard to analyze in work);
3392
3543
  - a convenient way to mark any `lock` invocation as "non-instrumentable" / "non-loggable" (as an alternative to `VoidNotifier` and to `VoidLogger`);
@@ -3435,7 +3586,7 @@ Detalized event semantics and payload structure:
3435
3586
  - add a library-level exception, when RQL-related key in Redis (required for its logic) has incompatible type (means: some other program uses our key with their own type and logic and RQL can't work properly);
3436
3587
  - yardoc docs with CI check (full doc coverage check);
3437
3588
  - split *exception* inheritance to the groups: `lock obtaining errors`, `block invocation errors`, `swarm errors`, and other groups (research possible groups):
3438
- - in some cases we need to intercept "Lock Obtaining Process Erros", in other cases: "Block Invocation Errors", and so on (in `Lock Serirs PoC` for example);
3589
+ - in some cases we need to intercept "Lock Obtaining Process Erros", in other cases: "Block Invocation Errors", and so on;
3439
3590
  - think about `#debounce` and `#throttle` mechanisms;
3440
3591
 
3441
3592
  ---