redis_queued_locks 1.16.2 → 1.18.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +281 -0
  3. data/.claude/rules/acquirer.md +81 -0
  4. data/.claude/rules/arguments.md +60 -0
  5. data/.claude/rules/git.md +46 -0
  6. data/.claude/rules/logic.md +63 -0
  7. data/.claude/rules/swarm.md +120 -0
  8. data/.claude/rules/tests.md +48 -0
  9. data/.claude/rules/type-checking.md +50 -0
  10. data/.claude/rules/visitors.md +69 -0
  11. data/.rubocop.rbs.yml +31 -0
  12. data/.rubocop.yml +3 -3
  13. data/.ruby-version +1 -1
  14. data/CHANGELOG.md +80 -0
  15. data/CLAUDE.md +36 -0
  16. data/README.md +367 -82
  17. data/Rakefile +31 -2
  18. data/github_ci/.keep +0 -0
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/delay_execution.rb +2 -2
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +6 -3
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +9 -7
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +20 -10
  23. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +34 -16
  24. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +240 -28
  25. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +398 -132
  26. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +12 -6
  27. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +33 -9
  28. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +73 -24
  29. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +9 -1
  30. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +182 -3
  31. data/lib/redis_queued_locks/acquirer/is_locked.rb +19 -2
  32. data/lib/redis_queued_locks/acquirer/is_queued.rb +6 -2
  33. data/lib/redis_queued_locks/acquirer/lock_info.rb +96 -6
  34. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +8 -4
  35. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -7
  36. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +126 -29
  37. data/lib/redis_queued_locks/acquirer/locks.rb +103 -6
  38. data/lib/redis_queued_locks/acquirer/queue_info.rb +38 -9
  39. data/lib/redis_queued_locks/acquirer/queues.rb +28 -4
  40. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +35 -1
  41. data/lib/redis_queued_locks/acquirer/release_lock.rb +40 -9
  42. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +43 -3
  43. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +143 -0
  44. data/lib/redis_queued_locks/acquirer.rb +1 -0
  45. data/lib/redis_queued_locks/client.rb +274 -158
  46. data/lib/redis_queued_locks/config/dsl.rb +4 -10
  47. data/lib/redis_queued_locks/resource.rb +117 -9
  48. data/lib/redis_queued_locks/swarm/flush_zombies.rb +46 -6
  49. data/lib/redis_queued_locks/swarm/supervisor.rb +1 -1
  50. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +166 -44
  51. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +45 -47
  52. data/lib/redis_queued_locks/swarm/zombie_info.rb +42 -0
  53. data/lib/redis_queued_locks/version.rb +2 -2
  54. data/rbs_collection.lock.yaml +10 -2
  55. data/rbs_collection.yaml +0 -2
  56. data/redis_queued_locks.gemspec +1 -1
  57. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -1
  58. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +0 -1
  59. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +5 -0
  60. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +12 -6
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +74 -7
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +4 -2
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -0
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +4 -2
  65. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +19 -1
  66. data/sig/redis_queued_locks/acquirer/lock_info.rbs +9 -1
  67. data/sig/redis_queued_locks/acquirer/locks.rbs +10 -1
  68. data/sig/redis_queued_locks/acquirer/queue_info.rbs +2 -4
  69. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  70. data/sig/redis_queued_locks/acquirer/release_lock.rbs +8 -1
  71. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +44 -0
  72. data/sig/redis_queued_locks/client.rbs +22 -2
  73. data/sig/redis_queued_locks/resource.rbs +9 -2
  74. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +1 -1
  75. data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +13 -5
  76. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +6 -6
  77. data/sig/redis_queued_locks/swarm/zombie_info.rbs +5 -0
  78. metadata +17 -5
  79. data/github_ci/ruby3.3.gemfile +0 -17
  80. data/github_ci/ruby3.3.gemfile.lock +0 -216
data/README.md CHANGED
@@ -2,13 +2,19 @@
2
2
 
3
3
  [![Tests (RSpec)](https://github.com/0exp/redis_queued_locks/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![Lint (Rubocop)](https://github.com/0exp/redis_queued_locks/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![TypeCheck (Runtime/RBS)](https://github.com/0exp/redis_queued_locks/actions/workflows/typecheck-runtime.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![TypeCheck (Static/Steep)](https://github.com/0exp/redis_queued_locks/actions/workflows/typecheck-static.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions)
4
4
 
5
- <a href="https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/">Distributed locks</a> with "prioritized lock acquisition queue" capabilities based on the Redis Database.
5
+ **RedisQueuedLocks** (aka **RQL**, aka **RQL-lock**) - <a href="https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/">distributed locks</a> with "prioritized lock acquisition queue" capabilities based on the Redis Database.
6
6
 
7
7
  Each lock request is put into the request queue (each lock is hosted by its 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.
8
8
 
9
9
  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.
10
10
 
11
- Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), **zombie locks elimination**, support for **reentrant locks**, logging and instrumentation (and much more).
11
+ Supports `Read`/`Write` locks and `Reentrant` locks.
12
+
13
+ Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), **zombie locks elimination**, logging and instrumentation (and much more).
14
+
15
+ ---
16
+
17
+ #### For future features and updates check the [ROADMAP](#roadmap) section.
12
18
 
13
19
  ---
14
20
 
@@ -30,6 +36,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
30
36
  - [locked?](#locked)
31
37
  - [queued?](#queued)
32
38
  - [unlock](#unlock---release-a-lock) (aka `release_lock`)
39
+ - [unlock_read](#unlock_read---release-the-read-lock-of-the-current-acquirer) (aka `release_read_lock`)
33
40
  - [clear_locks](#clear_locks---release-all-locks-and-lock-queues) (aka `release_locks`)
34
41
  - [clear_locks_of](#clear_locks_of) (aka `release_locks_of`)
35
42
  - [clear_current_locks](#clear_current_locks) (aka `release_current_locks`)
@@ -60,18 +67,22 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
60
67
  - [Lock Access Strategies](#lock-access-strategies)
61
68
  - [queued](#lock-access-strategies)
62
69
  - [random](#lock-access-strategies)
70
+ - [Read/Write Locks](#readwrite-locks)
63
71
  - [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)
64
72
  - [Logging](#logging)
65
73
  - [Logging Configuration](#logging-configuration)
66
74
  - [Instrumentation](#instrumentation)
67
75
  - [Instrumentation Configuration](#instrumentation-configuration)
68
76
  - [Instrumentation Events](#instrumentation-events)
69
- - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_hold_and_release)
77
+ - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
70
78
  - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
71
79
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
72
80
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
73
81
  - ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
82
+ - ["redis_queued_locks.lock_series_obtained"](#redis_queued_lockslock_series_obtained)
83
+ - ["redis_queued_locks.lock_series_hold_and_release"](#redis_queued_lockslock_series_hold_and_release)
74
84
  - ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
85
+ - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
75
86
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
76
87
  - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
77
88
  - [Roadmap](#roadmap)
@@ -89,7 +100,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
89
100
  - Redis Version: `>= 7`, `~> 8.x`;
90
101
  - Redis Protocol: `RESP3`;
91
102
  - gem `redis-client`: `~> 0.20`;
92
- - Ruby: `>= 3.3`;
103
+ - Ruby: `>= 4.0`;
93
104
 
94
105
  ---
95
106
 
@@ -371,6 +382,8 @@ end
371
382
 
372
383
  - [lock](#lock---obtain-a-lock)
373
384
  - [lock!](#lock---exeptional-lock-obtaining)
385
+ - [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
386
+ - [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
374
387
  - [lock_info](#lock_info)
375
388
  - [queue_info](#queue_info)
376
389
  - [locked?](#locked)
@@ -416,6 +429,7 @@ def lock(
416
429
  raise_errors: false,
417
430
  fail_fast: false,
418
431
  conflict_strategy: config['default_conflict_strategy'],
432
+ read_write_mode: :write,
419
433
  access_strategy: config['default_access_strategy'],
420
434
  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config['uniq_identifier'] proc;
421
435
  meta: nil,
@@ -496,6 +510,13 @@ def lock(
496
510
  - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
497
511
  - `:dead_locking` - fail with deadlock exception;
498
512
  - See [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) documentation section for details;
513
+ - `read_write_mode` - (optional) - `[Symbol]`
514
+ - The lock mode (read/write lock semantics);
515
+ - `:write` by default;
516
+ - Modes:
517
+ - `:write` - exclusive lock: waits for the write lock and for all read locks;
518
+ - `:read` - shared lock: waits for the write lock only (read locks do not block each other);
519
+ - See [Read/Write Locks](#readwrite-locks) documentation section for details;
499
520
  - `identity` - (optional) `[String]`
500
521
  - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
501
522
  collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
@@ -505,7 +526,7 @@ def lock(
505
526
  - Identity calculator is pre-configured in `config['uniq_identifier']`;
506
527
  - `meta` - (optional) `[NilClass,Hash<String|Symbol,Any>]`
507
528
  - A custom metadata wich will be passed to the lock data in addition to the existing data;
508
- - Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`);
529
+ - Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`, `rw_mode`);
509
530
  - `nil` by default (means "no metadata");
510
531
  - `detailed_acq_timeout_error` - (optional) `[Boolean]`
511
532
  - When the lock acquirement try reached the acquirement time limit (:timeout option) the
@@ -836,6 +857,7 @@ def lock!(
836
857
  instrumenter: config['instrumenter'],
837
858
  access_strategy: config['default_access_strategy'],
838
859
  conflict_strategy: config['default_conflict_strategy'],
860
+ read_write_mode: :write,
839
861
  log_sampling_enabled: config['log_sampling_enabled'],
840
862
  log_sampling_percent: config['log_sampling_percent'],
841
863
  log_sampler: config['log_sampler'],
@@ -876,6 +898,12 @@ applied with the same value to the each lock acquirement process (for each lock
876
898
  - `"y"` - 10 seconds
877
899
  - `"z"` - 5 seconds
878
900
  - when your block of code has completed their execution - all locks will be released immediatly;
901
+ - when the series fails (any lock can not be obtained, with or without an exception) - the locks that are already
902
+ obtained by the series are released immediatly (`acquired_locks` of the failure result lists them);
903
+ - the series releases only locks obtained by itself and only if they are still owned by the current acquirer:
904
+ - reentrant locks (already obtained by your outer logic, `conflict_strategy: :work_through`/`:extendable_work_through`) are kept;
905
+ - a lock that is expired during your block and obtained by another acquirer is kept;
906
+ - `read_write_mode:` is the same for all locks of the series (see [Read/Write Locks](#readwrite-locks));
879
907
  - if you did not pass a block - all locks will be released with their forumla-based TTL described above;
880
908
  - **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`);
881
909
 
@@ -940,19 +968,25 @@ client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
940
968
 
941
969
  ###### New instrumentaiton events:
942
970
 
971
+ - `rw_mode` is the mode of the series (`read_write_mode`, the same for all locks of the series);
972
+ - see [Instrumentation Events](#instrumentation-events) for the payload details;
973
+
943
974
  ```ruby
944
975
  # NEW instrumentation events (examples)
945
- "redis_queued_locks.lock_series_obtained" => # {lock_keys: ["rql:lock:s", "rql:lock:t", "rql:lock:u"], ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: "2026-01-28 22:05:06 +0300", acq_time: 2.61, instrument: nil}
946
- "redis_queued_locks.lock_series_hold_and_release" => # {lock_keys: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], 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}
976
+ "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}
977
+ "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}
947
978
  ```
948
979
 
949
980
  ###### New logs:
950
981
 
982
+ - `rw_mode` is the mode of the series (`read_write_mode`, the same for all locks of the series);
983
+ - see [Logging](#logging) for the logged keys;
984
+
951
985
  ```shell
952
986
  # NEW logs (examples)
953
- [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'
954
- [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' acq_time => 2.55 (ms)
955
- [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'
987
+ [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'
988
+ [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' acq_time => 2.55 (ms)
989
+ [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'
956
990
  ```
957
991
 
958
992
  ----
@@ -979,6 +1013,7 @@ client.lock_series!("x", "y", "z") { ...some_code... }
979
1013
  - returns `nil` if lock does not exist;
980
1014
  - lock data (`Hash<String,String|Integer>`):
981
1015
  - `"lock_key"` - `string` - lock key in redis;
1016
+ - `"rw_mode"` - `string` - lock mode: `"write"` (or `"read"` for read locks info, see [Read/Write Locks](#readwrite-locks));
982
1017
  - `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity);
983
1018
  - `"hst_id"` - `string` - host identifier (process_id/thread_id/ractor_id/identity);
984
1019
  - `"ts"` - `numeric`/`epoch` - the time when lock was obtained;
@@ -1002,6 +1037,7 @@ rql.lock_info("your_lock_name")
1002
1037
  # =>
1003
1038
  {
1004
1039
  "lock_key" => "rql:lock:your_lock_name",
1040
+ "rw_mode" => "write",
1005
1041
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1006
1042
  "hst_id" => "rql:acq:123/456/678/374dd74324",
1007
1043
  "ts" => 123456789.12345,
@@ -1018,6 +1054,7 @@ rql.lock_info("your_lock_name")
1018
1054
  # =>
1019
1055
  {
1020
1056
  "lock_key" => "rql:lock:your_lock_name",
1057
+ "rw_mode" => "write",
1021
1058
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1022
1059
  "hst_id" => "rql:acq:123/456/678/374dd74324",
1023
1060
  "ts" => 123456789.12345,
@@ -1040,6 +1077,7 @@ rql.lock_info("your_lock_name")
1040
1077
  # =>
1041
1078
  {
1042
1079
  "lock_key" => "rql:lock:your_lock_name",
1080
+ "rw_mode" => "write",
1043
1081
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1044
1082
  "hst_id" => "rql:acq:123/456/678/374dd74324",
1045
1083
  "ts" => 123456789.12345,
@@ -1073,12 +1111,15 @@ you can receive the lock queue info with empty queue value (an empty array).
1073
1111
  - set is ordered by score in ASC manner (inside the Redis Set);
1074
1112
  - score is represented as a timestamp when the lock request was made;
1075
1113
  - represents the acquirer identifier and their score as an array of hashes;
1076
- - returns `nil` if lock queue does not exist;
1114
+ - returns `nil` if lock queues (write and read) do not exist;
1077
1115
  - lock queue data (`Hash<String,String|Array<Hash<String|Numeric>>`):
1078
- - `"lock_queue"` - `string` - lock queue key in redis;
1116
+ - `"lock_queue"` - `string` - lock queue key in redis (write lock requests);
1079
1117
  - `"queue"` - `array` - an array of lock requests (array of hashes):
1080
1118
  - `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
1081
1119
  - `"score"` - `float`/`epoch` - time when the lock request was made (epoch);
1120
+ - `"rw_mode"` - `string` - the requested lock mode (`"write"`);
1121
+ - `"read_lock_queue"`, `"read_queue"` - (only when read lock requests exist) the read lock queue key and read lock requests
1122
+ with the same structure (`"rw_mode"` - `"read"`), see [Read/Write Locks](#readwrite-locks);
1082
1123
 
1083
1124
  ```ruby
1084
1125
  rql.queue_info("your_lock_name")
@@ -1087,9 +1128,15 @@ rql.queue_info("your_lock_name")
1087
1128
  {
1088
1129
  "lock_queue" => "rql:lock_queue:your_lock_name",
1089
1130
  "queue" => [
1090
- { "acq_id" => "rql:acq:123/456/567/678/fa76df9cc2", "score" => 1711606640.540842},
1091
- { "acq_id" => "rql:acq:123/567/456/679/c7bfcaf4f9", "score" => 1711606640.540906},
1092
- { "acq_id" => "rql:acq:555/329/523/127/7329553b11", "score" => 1711606640.540963},
1131
+ { "acq_id" => "rql:acq:123/456/567/678/fa76df9cc2", "score" => 1711606640.540842, "rw_mode" => "write" },
1132
+ { "acq_id" => "rql:acq:123/567/456/679/c7bfcaf4f9", "score" => 1711606640.540906, "rw_mode" => "write" },
1133
+ { "acq_id" => "rql:acq:555/329/523/127/7329553b11", "score" => 1711606640.540963, "rw_mode" => "write" },
1134
+ # ...etc
1135
+ ],
1136
+ # (when read lock requests exist)
1137
+ "read_lock_queue" => "rql:lock_read_queue:your_lock_name",
1138
+ "read_queue" => [
1139
+ { "acq_id" => "rql:acq:123/456/567/700/fa76df9cc2", "score" => 1711606640.541011, "rw_mode" => "read" },
1093
1140
  # ...etc
1094
1141
  ]
1095
1142
  }
@@ -1197,6 +1244,42 @@ rql.unlock("your_lock_name")
1197
1244
 
1198
1245
  ---
1199
1246
 
1247
+ #### #unlock_read - release the read lock of the current acquirer
1248
+
1249
+ <sup>\[[back to top](#usage)\]</sup>
1250
+
1251
+ - releases the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
1252
+ other read locks, the write lock and lock requests are not affected (`#unlock` releases the lock entirely);
1253
+ - suitable for read locks obtained without a block of code (`lock(..., read_write_mode: :read)` without a block);
1254
+ - the current acquirer is calculated in the same way as for `#lock` (`#current_acquirer_id`: process, thread, fiber, ractor, identity),
1255
+ so call it from the same thread/fiber that obtained the read lock;
1256
+ - has an alias: `#release_read_lock`;
1257
+ - accepts:
1258
+ - `lock_name` - (required) `[String]` - the lock name whose read lock should be released;
1259
+ - `:identity` - (optional) `[String]` - the same identity as for `#lock` (`uniq_identity` by default);
1260
+ - `:logger`, `:instrumenter`, `:instrument`, `:log_sampling_enabled`, `:log_sampling_percent`, `:log_sampler`, `:log_sample_this`,
1261
+ `:instr_sampling_enabled`, `:instr_sampling_percent`, `:instr_sampler`, `:instr_sample_this` - (optional) - the same as for `#unlock`;
1262
+ - returns `{ ok: true, result: { rel_time:, rel_key:, rel_acq_id:, lock_res: } }` (`lock_res` - `:released` or `:nothing_to_release`);
1263
+ - raises the `"redis_queued_locks.explicit_read_lock_release"` instrumentation event;
1264
+
1265
+ ```ruby
1266
+ rql.lock('report', read_write_mode: :read, ttl: 30_000) # => obtained without a block
1267
+ # ...
1268
+ rql.unlock_read('report')
1269
+ # =>
1270
+ {
1271
+ ok: true,
1272
+ result: {
1273
+ rel_time: 0.32, # milliseconds
1274
+ rel_key: 'rql:lock:report',
1275
+ rel_acq_id: 'rql:acq:17580/2260/2380/2280/3f16b93973612580',
1276
+ lock_res: :released # or :nothing_to_release
1277
+ }
1278
+ }
1279
+ ```
1280
+
1281
+ ---
1282
+
1200
1283
  #### #clear_locks - release all locks and lock queues
1201
1284
 
1202
1285
  <sup>\[[back to top](#usage)\]</sup>
@@ -1465,6 +1548,25 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1465
1548
  - the lock name which ttl should be extended;
1466
1549
  - `milliseconds` - (required) `[Integer]`
1467
1550
  - how many milliseconds should be added to the lock's TTL;
1551
+ - `:read_write_mode` - (optional) `[Symbol]`
1552
+ - `:write` (default) - extends the write lock (the behavior described below);
1553
+ - `:read` - extends the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
1554
+ - the read lock expiration, the read locks registry TTL and the read lock data TTL are extended;
1555
+ - an expired read lock (or a read lock replaced by a writer) is never extended ("revived"):
1556
+ `{ ok: false, result: :async_expire_or_no_lock }` is returned;
1557
+ - the extension is atomic (it is checked and applied inside a transaction that watches the write lock);
1558
+ - `:all_read_locks` - (optional) `[Boolean]`
1559
+ - `false` by default;
1560
+ - `:read` mode only (ignored in `:write` mode): `true` extends **all live read locks** of the lock
1561
+ (of all acquirers) instead of the read lock of the current acquirer:
1562
+ - each live read lock (its expiration and its data TTL) is extended by `milliseconds`,
1563
+ the read locks registry TTL is extended to the longest read lock;
1564
+ - expired read locks are not extended ("revived"); nothing is extended while the write lock is held;
1565
+ - read locks obtained concurrently with the extension (after it reads the read locks) are not extended;
1566
+ - returns `{ ok: true, result: { extended_locks_count: <the number of extended read locks> } }`
1567
+ when at least one read lock is extended;
1568
+ - `:identity` - (optional) `[String]`
1569
+ - the same identity as for `#lock` (`uniq_identity` by default); used for the current acquirer calculation in `:read` mode;
1468
1570
  - `:instrumenter` - (optional) `[#notify]`
1469
1571
  - custom instrumenter object;
1470
1572
  - pre-configured in `config['instrumetner']`;
@@ -1500,7 +1602,9 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1500
1602
  - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;
1501
1603
  - makes sense when instrumentation sampling is enabled;
1502
1604
  - `false` by default;
1503
- - returns `{ ok: true, result: :ttl_extended }` when ttl is extended;
1605
+ - returns `{ ok: true, result: { extended_locks_count: Integer } }` when ttl is extended:
1606
+ - `extended_locks_count` - the number of locks whose TTL is extended: `1` for the write lock
1607
+ and for the read lock of the current acquirer, the number of extended read locks for `all_read_locks: true`;
1504
1608
  - returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
1505
1609
  some steps of invocation (see **Important** section below);
1506
1610
  - **Important**:
@@ -1520,7 +1624,7 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1520
1624
  rql.extend_lock_ttl("my_lock", 5_000) # NOTE: add 5_000 milliseconds
1521
1625
 
1522
1626
  # => `ok` case
1523
- { ok: true, result: :ttl_extended }
1627
+ { ok: true, result: { extended_locks_count: 1 } }
1524
1628
 
1525
1629
  # => `failed` case
1526
1630
  { ok: false, result: :async_expire_or_no_lock }
@@ -1676,7 +1780,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
1676
1780
  - returns `Set<Hash<Symbol,Any>>` (see [#queue_info](#queue_info) and examples below for details).
1677
1781
  - contained data: `{ queue: String, requests: Array<Hash<String,Any>> }`
1678
1782
  - `:queue` - `String` - lock key queue in Redis;
1679
- - `:requests` - `Array<Hash<String,Any>>` - lock requests in the que with their acquirer id and score.
1783
+ - `:requests` - `Array<Hash<String,Any>>` - lock requests in the que with their acquirer id, score and requested lock mode
1784
+ (`"rw_mode"` - `"write"` for `rql:lock_queue:*`, `"read"` for read lock queues `rql:lock_read_queue:*`).
1680
1785
 
1681
1786
  ```ruby
1682
1787
  rql.queues_info # or rql.qeuues_info(scan_size: 123)
@@ -1684,14 +1789,13 @@ rql.queues_info # or rql.qeuues_info(scan_size: 123)
1684
1789
  => #<Set:
1685
1790
  {{:queue=>"rql:lock_queue:some-lock-123",
1686
1791
  :requests=>
1687
- [{"acq_id"=>"rql:acq:38529/4500/4520/4360/66093702f24a3129", "score"=>1711606640.540842},
1688
- {"acq_id"=>"rql:acq:38529/4580/4600/4360/66093702f24a3129", "score"=>1711606640.540906},
1689
- {"acq_id"=>"rql:acq:38529/4620/4640/4360/66093702f24a3129", "score"=>1711606640.5409632}]},
1690
- {:queue=>"rql:lock_queue:some-lock-456",
1792
+ [{"acq_id"=>"rql:acq:38529/4500/4520/4360/66093702f24a3129", "score"=>1711606640.540842, "rw_mode"=>"write"},
1793
+ {"acq_id"=>"rql:acq:38529/4580/4600/4360/66093702f24a3129", "score"=>1711606640.540906, "rw_mode"=>"write"},
1794
+ {"acq_id"=>"rql:acq:38529/4620/4640/4360/66093702f24a3129", "score"=>1711606640.5409632, "rw_mode"=>"write"}]},
1795
+ {:queue=>"rql:lock_read_queue:some-lock-456",
1691
1796
  :requests=>
1692
- [{"acq_id"=>"rql:acq:38529/4380/4400/4360/66093702f24a3129", "score"=>1711606640.540722},
1693
- {"acq_id"=>"rql:acq:38529/4420/4440/4360/66093702f24a3129", "score"=>1711606640.5407748},
1694
- {"acq_id"=>"rql:acq:38529/4460/4480/4360/66093702f24a3129", "score"=>1711606640.540808}]},
1797
+ [{"acq_id"=>"rql:acq:38529/4380/4400/4360/66093702f24a3129", "score"=>1711606640.540722, "rw_mode"=>"read"},
1798
+ {"acq_id"=>"rql:acq:38529/4420/4440/4360/66093702f24a3129", "score"=>1711606640.5407748, "rw_mode"=>"read"}]},
1695
1799
  ...}>
1696
1800
  ```
1697
1801
  ---
@@ -2104,6 +2208,92 @@ rql.possible_host_ids
2104
2208
 
2105
2209
  ---
2106
2210
 
2211
+ ## Read/Write Locks
2212
+
2213
+ <sup>\[[back to top](#table-of-contents)\]</sup>
2214
+
2215
+ - `read_write_mode:` attribute of `#lock` and `#lock!`:
2216
+ - `:write` (default) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
2217
+ - `:read` - shared lock: waits for the write lock only (read locks of different acquirers work in parallel);
2218
+
2219
+ ```ruby
2220
+ # readers work in parallel
2221
+ rql.lock('report', read_write_mode: :read) { read_report }
2222
+
2223
+ # writer waits for all readers (and readers wait for the writer)
2224
+ rql.lock('report', read_write_mode: :write) { rebuild_report }
2225
+ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2226
+ ```
2227
+
2228
+ - lock request ordering (`access_strategy`):
2229
+ - `:queued` (default) - FIFO between modes: read lock request waits for every earlier write lock request,
2230
+ write lock request waits for every earlier read and write lock request (so writers are not starved by a
2231
+ continuous flow of readers and consecutive readers obtain the lock together);
2232
+ - `:random` - lock queues are ignored: any free lock is obtained (a continuous flow of readers can starve writers);
2233
+ - read locks:
2234
+ - each read lock has its own `ttl` and is released (after the block execution) or expired independently of other read locks;
2235
+ - `ttl` should be a positive `Integer` (`RedisQueuedLocks::ArgumentError`);
2236
+ - each read lock has its own data with the same structure as the write lock data: acquirer, host, timestamp,
2237
+ initial ttl, `:meta` (with the same reserved keys) and reentrant lock data (`spc_cnt`, `spc_ext_ttl`, ...);
2238
+ the read lock data lives as long as the read lock and is returned by `#lock_info`/`#locks_info` for each reader;
2239
+ - `#unlock` releases the lock entirely: the write lock, all read locks (with their data) and all lock requests;
2240
+ - `#unlock_read` (aka `#release_read_lock`) releases the read lock of the current acquirer only
2241
+ (suitable for read locks obtained without a block);
2242
+ - `#extend_lock_ttl(lock_name, milliseconds, read_write_mode: :read)` extends the read lock of the current acquirer only;
2243
+ `all_read_locks: true` extends all live read locks of the lock;
2244
+ - `#lock_series` with `read_write_mode: :read` obtains a series of read locks (the mode is the same for all locks of the series):
2245
+ - read lock series of different acquirers work in parallel, writers wait for them;
2246
+ - after the block (or when the series fails) only the read locks of the current acquirer are released;
2247
+ - reentrant read locks of the series (already obtained by the outer logic of the same acquirer) are not released by the series;
2248
+ - reentrant locks (the same acquirer, see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)):
2249
+ - read lock request under the obtained read lock or write lock is processed as a reentrant lock of the obtained lock
2250
+ in accordance with `conflict_strategy`;
2251
+ - write lock request under the obtained read lock (read-to-write lock upgrade) fails with
2252
+ `RedisQueuedLocks::ConflictLockObtainError` (`{ ok: false, result: :conflict_lock_upgrade }`) for
2253
+ `:work_through` and `:extendable_work_through` (`:dead_locking` fails as usual, `:wait_for_lock` waits for the read lock expiration);
2254
+ - info methods:
2255
+ - `#locked?` - the lock is obtained by a writer or by any reader;
2256
+ - `#queued?` - there are write or read lock requests;
2257
+ - `#lock_info` - write lock info (`'rw_mode' => 'write'`), or read locks info when there is no write lock:
2258
+ ```ruby
2259
+ {
2260
+ 'lock_key' => 'rql:lock:report',
2261
+ 'rw_mode' => 'read',
2262
+ 'rem_ttl' => 4985, # remaining ttl of the longest read lock (milliseconds)
2263
+ 'readers' => [
2264
+ {
2265
+ 'acq_id' => 'rql:acq:...',
2266
+ 'hst_id' => 'rql:hst:...',
2267
+ 'ts' => 1791582966.125299,
2268
+ 'ini_ttl' => 5000,
2269
+ 'rem_ttl' => 4985,
2270
+ 'some_meta_key' => 'some_meta_value' # (custom `:meta`)
2271
+ },
2272
+ { 'acq_id' => 'rql:acq:...', 'hst_id' => 'rql:hst:...', 'ts' => 1791582967.5, 'ini_ttl' => 5000, 'rem_ttl' => 3120 }
2273
+ ]
2274
+ }
2275
+ ```
2276
+ - `#queue_info` - additional `"read_lock_queue"`/`"read_queue"` keys when read lock requests exist;
2277
+ each request of `#queue_info`/`#queues_info` has `"rw_mode"` (`"write"`/`"read"`);
2278
+ - `#locks`/`#locks_info`, `#queues`/`#queues_info`, `#keys` - include read locks and read lock queues
2279
+ (`#locks_info` info has `'rw_mode'` - `'write'` or `'read'`);
2280
+ - logs and instrumentation: `rw_mode` is added to the lock logs and to the instrumentation payloads
2281
+ (lock series logs and events: the mode of the series), see [Logging](#logging) and [Instrumentation Events](#instrumentation-events);
2282
+ - data layout:
2283
+ - `rql:lock:<lock_name>` - write lock (`HASH`, the classic RQL lock);
2284
+ - `rql:lock_queue:<lock_name>` - write lock requests (`ZSET`, the classic RQL lock queue);
2285
+ - `rql:lock_readers:<lock_name>` - read locks (`ZSET`: acquirer id => read lock expiration time in Redis time, milliseconds);
2286
+ - `rql:lock_read_queue:<lock_name>` - read lock requests (`ZSET`);
2287
+ - `rql:lock_reader:<lock_name>:<acquirer_id>` - read lock data (`HASH`, the same structure as the write lock, read lock ttl);
2288
+ - implementation notes:
2289
+ - optimistic transactions only (`WATCH`/`MULTI`): readers watch the write lock key, writers watch the write lock key and the read locks key,
2290
+ so readers never invalidate transactions of each other;
2291
+ - lock queues are responsible for the ordering only, mutual exclusion is guaranteed by the lock state check;
2292
+ - **all your processes should use the RQL version with read/write locks** before you start to use read locks:
2293
+ write locks of older RQL versions do not check read locks;
2294
+
2295
+ ---
2296
+
2107
2297
  ## Deadlocks and Reentrant locks
2108
2298
 
2109
2299
  <sup>\[[back to top](#table-of-contents)\]</sup>
@@ -2129,39 +2319,64 @@ rql.possible_host_ids
2129
2319
  - [Logging Configuration](#logging-configuration)
2130
2320
 
2131
2321
 
2322
+ - each lock log contains `rw_mode` - the lock mode (`'write'` or `'read'`, see [Read/Write Locks](#readwrite-locks)):
2323
+ - the **requested** mode in lock obtaining logs, try-lock logs (`try_lock.*`) and `fail_fast_or_limits_reached_or_deadlock__dequeue`;
2324
+ - the **held** mode in `expire_lock` and `decrease_lock` (the lock that is released or decreased: a reentrant read lock request
2325
+ under the write lock of the same acquirer holds the write lock);
2326
+
2132
2327
  - default logs (raised from `#lock`/`#lock!`):
2133
2328
 
2134
2329
  ```ruby
2135
- "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2136
- "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2137
- "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2138
- "[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time");
2139
- "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "acq_time");
2140
- "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "acq_time");
2141
- "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2142
- "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2143
- "[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2330
+ "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2331
+ "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2332
+ "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2333
+ "[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2334
+ "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2335
+ "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2336
+ "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2337
+ "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2338
+ "[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2144
2339
  ```
2145
2340
 
2146
2341
  - additional logs (raised from `#lock`/`#lock!` with `confg[:log_lock_try] == true`):
2147
2342
 
2148
2343
  ```ruby
2149
- "[redis_queued_locks.try_lock.start]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2150
- "[redis_queued_locks.try_lock.rconn_fetched]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2151
- "[redis_queued_locks.try_lock.same_process_conflict_detected]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2152
- "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status");
2153
- "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", "last_ext_ttl", "last_ext_ts");
2154
- "[redis_queued_locks.try_lock.reentrant_lock__work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", last_spc_ts);
2155
- "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", "last_spc_ts");
2156
- "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2157
- "[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2158
- "[redis_queued_locks.try_lock.get_first_from_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue");
2159
- "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2160
- "[redis_queued_locks.try_lock.exit__no_first]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue", "<current_lock_data>");
2161
- "[redis_queued_locks.try_lock.exit__lock_still_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
2162
- "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2344
+ "[redis_queued_locks.try_lock.start]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2345
+ "[redis_queued_locks.try_lock.rconn_fetched]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2346
+ "[redis_queued_locks.try_lock.same_process_conflict_detected]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2347
+ "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status");
2348
+ "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_ext_ttl", "last_ext_ts");
2349
+ "[redis_queued_locks.try_lock.reentrant_lock__work_through]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
2350
+ "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
2351
+ "[redis_queued_locks.try_lock.single_process_lock_conflict__lock_upgrade]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
2352
+ "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2353
+ "[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2354
+ "[redis_queued_locks.try_lock.get_first_from_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue");
2355
+ "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2356
+ "[redis_queued_locks.try_lock.exit__no_first]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue", "<current_lock_data>");
2357
+ "[redis_queued_locks.try_lock.exit__lock_still_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
2358
+ "[redis_queued_locks.try_lock.exit__write_request_ahead]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "ahead_acq_id");
2359
+ "[redis_queued_locks.try_lock.exit__read_request_ahead]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "ahead_acq_id");
2360
+ "[redis_queued_locks.try_lock.exit__read_lock_still_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue");
2361
+ "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2362
+ ```
2363
+
2364
+ - lock series logs (raised from `#lock_series`/`#lock_series!`, PoC; `rw_mode` is the mode of the series):
2365
+
2366
+ ```ruby
2367
+ "[redis_queued_locks.start_lock_series_obtaining]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2368
+ "[redis_queued_locks.lock_series_obtained]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2369
+ "[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2163
2370
  ```
2164
2371
 
2372
+ - read/write try-lock logs (see [Read/Write Locks](#readwrite-locks)):
2373
+ - `single_process_lock_conflict__lock_upgrade` - write lock request of the acquirer that holds the read lock
2374
+ (read-to-write lock upgrade is not supported for `:work_through` and `:extendable_work_through` conflict strategies);
2375
+ - `exit__write_request_ahead` - read lock request waits for an earlier write lock request (`ahead_acq_id`, `:queued` access strategy);
2376
+ - `exit__read_request_ahead` - write lock request waits for an earlier read lock request (`ahead_acq_id`, `:queued` access strategy);
2377
+ - `exit__read_lock_still_obtained` - write lock request waits for live read locks
2378
+ (read lock request: waits for the own read lock with `:wait_for_lock` conflict strategy);
2379
+
2165
2380
  ---
2166
2381
 
2167
2382
  ### Logging Configuration
@@ -2179,15 +2394,20 @@ rql.possible_host_ids
2179
2394
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
2180
2395
  # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
2181
2396
  # - at this moment the only debug logs are realised in following cases:
2182
- # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2183
- # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2184
- # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2185
- # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
2186
- # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
2187
- # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
2188
- # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2189
- # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2190
- # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2397
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2398
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2399
+ # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2400
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2401
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2402
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2403
+ # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2404
+ # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2405
+ # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2406
+ # - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2407
+ # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2408
+ # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2409
+ # - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
2410
+ # the mode of the series for lock series logs;
2191
2411
  # - by default uses VoidLogger that does nothing;
2192
2412
  config['logger'] = RedisQueuedLocks::Logging::VoidLogger
2193
2413
 
@@ -2195,19 +2415,25 @@ config['logger'] = RedisQueuedLocks::Logging::VoidLogger
2195
2415
  # - adds additional debug logs;
2196
2416
  # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
2197
2417
  # - it adds following debug logs in addition to the existing:
2198
- # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2199
- # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2200
- # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2201
- # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status");
2202
- # - "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", "last_ext_ttl", "last_ext_ts");
2203
- # - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", last_spc_ts);
2204
- # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat")";
2205
- # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2206
- # - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue");
2207
- # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2208
- # - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue", "<current_lock_data>");
2209
- # - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
2210
- # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
2418
+ # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2419
+ # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2420
+ # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2421
+ # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status");
2422
+ # - "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_ext_ttl", "last_ext_ts");
2423
+ # - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
2424
+ # - "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
2425
+ # - "[redis_queued_locks.try_lock.single_process_lock_conflict__lock_upgrade]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
2426
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2427
+ # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2428
+ # - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue");
2429
+ # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2430
+ # - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue", "<current_lock_data>");
2431
+ # - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
2432
+ # - "[redis_queued_locks.try_lock.exit__write_request_ahead]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "ahead_acq_id");
2433
+ # - "[redis_queued_locks.try_lock.exit__read_request_ahead]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "ahead_acq_id");
2434
+ # - "[redis_queued_locks.try_lock.exit__read_lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue");
2435
+ # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2436
+ # - "rw_mode" is the requested lock mode ('write'/'read');
2211
2437
  config['log_lock_try'] = false
2212
2438
 
2213
2439
  # (default: false)
@@ -2307,12 +2533,15 @@ config['instr_sampler'] = RedisQueuedLocks::Instrument::Sampler
2307
2533
 
2308
2534
  List of instrumentation events
2309
2535
 
2310
- - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_hold_and_release)
2536
+ - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
2311
2537
  - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
2312
2538
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
2313
2539
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
2314
2540
  - ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
2541
+ - ["redis_queued_locks.lock_series_obtained"](#redis_queued_lockslock_series_obtained)
2542
+ - ["redis_queued_locks.lock_series_hold_and_release"](#redis_queued_lockslock_series_hold_and_release)
2315
2543
  - ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
2544
+ - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
2316
2545
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
2317
2546
  - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
2318
2547
 
@@ -2327,6 +2556,7 @@ Detalized event semantics and payload structure:
2327
2556
  - `:acq_id` - `string` - lock acquirer identifier;
2328
2557
  - `:hst_id` - `string` - lock's host identifier;
2329
2558
  - `:lock_key` - `string` - lock name;
2559
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2330
2560
  - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend;
2331
2561
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2332
2562
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
@@ -2337,6 +2567,8 @@ Detalized event semantics and payload structure:
2337
2567
  - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2338
2568
  - payload:
2339
2569
  - `:lock_key` - `string` - lock name;
2570
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2571
+ a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
2340
2572
  - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2341
2573
  - `:acq_id` - `string` - lock acquirer identifier;
2342
2574
  - `:hst_id` - `string` - lock's host identifier;
@@ -2350,6 +2582,8 @@ Detalized event semantics and payload structure:
2350
2582
  - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2351
2583
  - payload:
2352
2584
  - `:lock_key` - `string` - lock name;
2585
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2586
+ a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
2353
2587
  - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2354
2588
  - `:acq_id` - `string` - lock acquirer identifier;
2355
2589
  - `:hst_id` - `string` - lock's host identifier;
@@ -2367,6 +2601,7 @@ Detalized event semantics and payload structure:
2367
2601
  - `:acq_id` - `string` - lock acquirer identifier;
2368
2602
  - `:hst_id` - `string` - lock's host identifier;
2369
2603
  - `:lock_key` - `string` - lock name;
2604
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2370
2605
  - `:ts` - `numeric`/`epoch` - the time when lock was obtained;
2371
2606
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2372
2607
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
@@ -2383,18 +2618,63 @@ Detalized event semantics and payload structure:
2383
2618
  - `:hst_id` - `string` - lock's host identifier;
2384
2619
  - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;
2385
2620
  - `:lock_key` - `string` - lock name;
2621
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2622
+ a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
2386
2623
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2387
2624
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2388
2625
 
2626
+ ##### `"redis_queued_locks.lock_series_obtained"`
2627
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2628
+ - (PoC) a moment when all locks of the series were obtained;
2629
+ - raised from `#lock_series`/`#lock_series!`;
2630
+ - payload:
2631
+ - `:lock_keys` - `array<string>` - lock names of the series;
2632
+ - `:rw_mode` - `symbol` - the mode of the series (`read_write_mode`): `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2633
+ - `:ttl` - `integer`/`milliseconds` - the `ttl` option of the series (each lock of the series gets its own ttl,
2634
+ see [lock_series](#lock_series---poc-acquire-a-series-of-locks));
2635
+ - `:acq_id` - `string` - lock acquirer identifier;
2636
+ - `:hst_id` - `string` - lock's host identifier;
2637
+ - `:ts` - `numeric`/`epoch` - the time when the series was obtained;
2638
+ - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
2639
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
2640
+
2641
+ ##### `"redis_queued_locks.lock_series_hold_and_release"`
2642
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2643
+ - (PoC) an event signalizes about the "hold+and+release" process of the series is finished;
2644
+ - raised from `#lock_series`/`#lock_series!` when invoked with a block of code;
2645
+ - payload:
2646
+ - `:lock_keys` - `array<string>` - lock names of the series;
2647
+ - `:rw_mode` - `symbol` - the mode of the series (`read_write_mode`): `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2648
+ - `:hold_time` - `float`/`milliseconds` - the series hold time;
2649
+ - `:ttl` - `integer`/`milliseconds` - the `ttl` option of the series;
2650
+ - `:acq_id` - `string` - lock acquirer identifier;
2651
+ - `:hst_id` - `string` - lock's host identifier;
2652
+ - `:ts` - `float`/`epoch` - the time when the series was released;
2653
+ - `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
2654
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
2655
+
2389
2656
  ##### `"redis_queued_locks.explicit_lock_release"`
2390
2657
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2391
2658
  - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
2392
2659
  - raised from `#unlock`;
2660
+ - the write lock, all read locks and both lock request queues (write and read) are released;
2393
2661
  - payload:
2394
2662
  - `:at` - `float`/`epoch` - the time when the lock was released;
2395
2663
  - `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
2396
2664
  - `:lock_key` - `string` - released lock (lock name);
2397
- - `:lock_key_queue` - `string` - released lock queue (lock queue name);
2665
+ - `:lock_key_queue` - `string` - released lock queue (the queue of write lock requests);
2666
+
2667
+ ##### `"redis_queued_locks.explicit_read_lock_release"`
2668
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2669
+ - an event signalizes about the explicit release of the read lock of the concrete acquirer (invoked via `RedisQueuedLock#unlock_read`);
2670
+ - raised from `#unlock_read` (aka `#release_read_lock`);
2671
+ - payload:
2672
+ - `:at` - `float`/`epoch` - the time when the read lock was released;
2673
+ - `:rel_time` - `float`/`milliseconds` - time spent on read lock releasing;
2674
+ - `:lock_key` - `string` - lock key (`rql:lock:<lock_name>`);
2675
+ - `:acq_id` - `string` - the read lock acquirer;
2676
+ - `:lock_res` - `symbol` - `:released` or `:nothing_to_release`;
2677
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#unlock_read` method as `:instrument` attribute;
2398
2678
 
2399
2679
  ##### `"redis_queued_locks.explicit_all_locks_release"`
2400
2680
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
@@ -2403,7 +2683,8 @@ Detalized event semantics and payload structure:
2403
2683
  - payload:
2404
2684
  - `:rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
2405
2685
  - `:at` - `float`/`epoch` - the time when the operation has ended;
2406
- - `:rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
2686
+ - `:rel_key_cnt` - `integer` - released redis keys count (write locks, readers registries, read lock data,
2687
+ write and read lock request queues);
2407
2688
 
2408
2689
  ##### `"redis_queued_locks.release_locks_of"`
2409
2690
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
@@ -2414,8 +2695,8 @@ Detalized event semantics and payload structure:
2414
2695
  - `:at` - `float`/`epoch` - the time when the opertaion has ended;
2415
2696
  - `:acq_id` - `string` - refused acquirer identifier;
2416
2697
  - `:hst_id` - `string` - refused host identifier;
2417
- - `:rel_key_cnt` - `integer` - released locks count;
2418
- - `:tch_queue_cnt` - `:integer` - the number of queues from which the concrete host/acquirer was removed;
2698
+ - `:rel_key_cnt` - `integer` - released locks count (write locks and read locks);
2699
+ - `:tch_queue_cnt` - `:integer` - the number of queues (write and read lock request queues) from which the concrete host/acquirer was removed;
2419
2700
 
2420
2701
  ---
2421
2702
 
@@ -2443,14 +2724,17 @@ Detalized event semantics and payload structure:
2443
2724
  - an ability to release all locks and all requests of the concrete acquirer id or host id (or both in validation-orianted combination);
2444
2725
  - 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);
2445
2726
  - a convenient way to mark any `lock` invocation as "non-instrumentable" / "non-loggable" (as an alternative to `VoidNotifier` and to `VoidLogger`);
2446
- - `Read`/`Write` semantics: you can mark your locks as `read` or `write` lock in order to simulate `read`/`write` lock behavior:
2447
- - `read` - watis - `write`;
2448
- - `read` - not waits - `read`;
2449
- - `write` - waits - `read`;
2450
- - `write` - waits - `write`;
2451
- - **write** mode is a default behavior for all RQL locks;
2727
+ - `README`-section `Issues with other Libs` about issues in other libraries that can broke RQL features:
2728
+ - `Sentry`'s' bugs with Ractors:
2729
+ - `Sentry`-OpenTelemetry problems with its global mutexes and non-sharable objects that is not accessible from the ractors (sentry tyies to
2730
+ intercept all `redis-client` invocations managing their logic via global Mutex instance that brokes `swarm-mode` with `non-sharable` memory
2731
+ access errors, cuz RQL's swarm works using its own RedisClient-instnace, that fails isnide the `Sentry`'s patch: Sentry tries to use the same
2732
+ Mutex (instantiated inside the main Ractor) from the our non-main Ractor (swar-mode works under the non-main ractor) and this process fails\
2733
+ on non-sharable memory error);
2452
2734
  - **Minor**:
2453
2735
  - an ability to return all insturmentation metrics from the `lock` invocation (and after block `yield`ing);
2736
+ - think about a PG-like `stats`-data/table, that should be helpful for lock history acquirement analyzation :thinking: (suitable for cases when we want
2737
+ to check if the lock was acquired by some different parallel process durign the some period of time in the past or not);
2454
2738
  - think about "thread priority" configuration :thinking:;
2455
2739
  - add `hst_id` to all methods that works with queues info;
2456
2740
  - try to return the `fiber object id` to the lock host identifier (we cant use fiber object id cuz `ObjectSpace` has no access to the fiber object space after the any ractor object initialization)
@@ -2472,7 +2756,7 @@ Detalized event semantics and payload structure:
2472
2756
  - better code stylization (+ some refactorings);
2473
2757
  - `RedisQueuedLocks::Acquirer::Try.try_to_lock` - detailed successful result analization;
2474
2758
  - Support for LIFO strategy;
2475
- - better specs with 100% test coverage (total specs rework);
2759
+ - specs refactoring with 100% test coverage (total specs rework);
2476
2760
  - statistics with UI;
2477
2761
  - JSON log formatter;
2478
2762
  - **automatic** deadlock detection;
@@ -2485,6 +2769,7 @@ Detalized event semantics and payload structure:
2485
2769
  - yardoc docs with CI check (full doc coverage check);
2486
2770
  - split *exception* inheritance to the groups: `lock obtaining errors`, `block invocation errors`, `swarm errors`, and other groups (research possible groups):
2487
2771
  - 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);
2772
+ - think about `#debounce` and `#throttle` mechanisms;
2488
2773
 
2489
2774
  ---
2490
2775