redis_queued_locks 1.20.0 → 1.22.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +249 -36
  3. data/.claude/rules/acquirer.md +16 -14
  4. data/.claude/rules/arguments.md +2 -2
  5. data/.claude/rules/changelog.md +27 -0
  6. data/.claude/rules/git.md +1 -1
  7. data/.claude/rules/logic.md +34 -13
  8. data/.claude/rules/swarm.md +4 -5
  9. data/.claude/rules/tests.md +9 -6
  10. data/.claude/rules/type-checking.md +13 -14
  11. data/.claude/rules/visitors.md +13 -14
  12. data/.claude/skills/rql-code-review/SKILL.md +134 -43
  13. data/CHANGELOG.md +105 -125
  14. data/CLAUDE.md +11 -9
  15. data/README.md +501 -136
  16. data/Rakefile +11 -0
  17. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +68 -10
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +82 -15
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -14
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +441 -63
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
  23. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
  24. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +5 -5
  25. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +176 -39
  26. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +322 -49
  27. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +125 -13
  28. data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
  29. data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
  30. data/lib/redis_queued_locks/acquirer/keys.rb +16 -3
  31. data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
  32. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +11 -5
  33. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +20 -10
  34. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +105 -36
  35. data/lib/redis_queued_locks/acquirer/locks.rb +32 -11
  36. data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -8
  37. data/lib/redis_queued_locks/acquirer/queues.rb +26 -6
  38. data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
  39. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +37 -6
  40. data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
  41. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +45 -6
  42. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
  43. data/lib/redis_queued_locks/client.rb +140 -56
  44. data/lib/redis_queued_locks/config.rb +105 -14
  45. data/lib/redis_queued_locks/errors.rb +34 -4
  46. data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
  47. data/lib/redis_queued_locks/logging/sampler.rb +4 -8
  48. data/lib/redis_queued_locks/resource.rb +137 -12
  49. data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
  50. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
  51. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +8 -6
  52. data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
  53. data/lib/redis_queued_locks/swarm.rb +86 -5
  54. data/lib/redis_queued_locks/utilities.rb +1 -1
  55. data/lib/redis_queued_locks/version.rb +2 -2
  56. data/rbs_collection.lock.yaml +4 -0
  57. data/rbs_collection.yaml +2 -1
  58. data/sig/manifest.yml +1 -0
  59. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
  60. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +25 -5
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +32 -12
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +59 -11
  65. data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
  66. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +4 -4
  67. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +6 -4
  68. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +23 -1
  69. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +16 -3
  70. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  71. data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
  72. data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
  73. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
  74. data/sig/redis_queued_locks/client.rbs +24 -10
  75. data/sig/redis_queued_locks/config.rbs +54 -1
  76. data/sig/redis_queued_locks/errors.rbs +5 -2
  77. data/sig/redis_queued_locks/instrument/sampler.rbs +3 -3
  78. data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
  79. data/sig/redis_queued_locks/logging/sampler.rbs +3 -3
  80. data/sig/redis_queued_locks/resource.rbs +23 -13
  81. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
  82. data/sig/redis_queued_locks/utilities.rbs +2 -1
  83. data/sig/redis_queued_locks.rbs +7 -0
  84. data/sig/vendor/redis_client.rbs +1 -0
  85. metadata +4 -3
  86. /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
data/README.md CHANGED
@@ -4,11 +4,11 @@
4
4
 
5
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
- 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.
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: requests of a higher [priority](#lock-request-prioritization) go first, requests of the same priority are processed in FIFO order. 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
- Supports `Read`/`Write` locks and `Reentrant` locks.
11
+ Supports [lock request prioritization](#lock-request-prioritization), `Read`/`Write` locks and `Reentrant` locks.
12
12
 
13
13
  Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), **zombie locks elimination**, **[lock indexing](#lock-index)**, logging and instrumentation (and much more).
14
14
 
@@ -47,6 +47,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
47
47
  - [locks_info](#locks_info---get-list-of-locks-with-their-info)
48
48
  - [queues_info](#queues_info---get-list-of-queues-with-their-info)
49
49
  - [clear_dead_requests](#clear_dead_requests)
50
+ - [reindex_locks](#reindex_locks)
50
51
  - [current_acquirer_id](#current_acquirer_id)
51
52
  - [current_host_id](#current_host_id)
52
53
  - [possible_host_ids](#possible_host_ids)
@@ -70,6 +71,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
70
71
  - [Lock Access Strategies](#lock-access-strategies)
71
72
  - [queued](#lock-access-strategies)
72
73
  - [random](#lock-access-strategies)
74
+ - [Lock Request Prioritization](#lock-request-prioritization)
73
75
  - [Read/Write Locks](#readwrite-locks)
74
76
  - [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)
75
77
  - [Logging](#logging)
@@ -78,6 +80,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
78
80
  - [Instrumentation Configuration](#instrumentation-configuration)
79
81
  - [Instrumentation Events](#instrumentation-events)
80
82
  - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
83
+ - ["redis_queued_locks.lock_obtaining_failed"](#redis_queued_lockslock_obtaining_failed)
81
84
  - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
82
85
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
83
86
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
@@ -88,6 +91,9 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
88
91
  - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
89
92
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
90
93
  - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
94
+ - ["redis_queued_locks.extend_lock_ttl"](#redis_queued_locksextend_lock_ttl)
95
+ - ["redis_queued_locks.clear_dead_requests"](#redis_queued_locksclear_dead_requests)
96
+ - ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
91
97
  - [Roadmap](#roadmap)
92
98
  - [Build and Develop](#build-and-develop)
93
99
  - [Contributing](#contributing)
@@ -120,7 +126,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
120
126
 
121
127
  <sup>\[[back to top](#table-of-contents)\]</sup>
122
128
 
123
- > 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) which guarantees that the request queue will never be stacked.
129
+ > 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: requests of a higher [priority](#lock-request-prioritization) go first, requests of the same priority are processed in FIFO order. Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
124
130
 
125
131
  > 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.
126
132
 
@@ -191,16 +197,20 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
191
197
 
192
198
  # (seconds) (supports nil)
193
199
  # - nil means "no timeout" and you are only limited by "retry_count" config;
200
+ # - 0 is the same as nil (no timeout);
194
201
  config['try_to_lock_timeout'] = 10
195
202
 
196
203
  # (milliseconds) (default: 5_000)
197
204
  # - lock's time to live
205
+ # - should be a positive Integer
198
206
  config['default_lock_ttl'] = 5_000
199
207
 
200
208
  # (seconds) (default: 15)
201
- # - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the queue);
209
+ # - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the
210
+ # lock requests of its priority);
202
211
  # - (the enabled lock index) the lock request queue lives as long as its latest lock request (the queue TTL is refreshed with
203
212
  # the queue ttl on each lock request): queues of dead acquirers expire by themselves;
213
+ # - should be a positive Integer
204
214
  config['default_queue_ttl'] = 15
205
215
 
206
216
  # (boolean) (default: false)
@@ -241,6 +251,30 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
241
251
  # retries, timeouts and so on). if lock is free to obtain - it will be obtained;
242
252
  config['default_access_strategy'] = :queued
243
253
 
254
+ # (hash) (default: { lowest: -100, low: -50, normal: 0, high: 50, highest: 100 })
255
+ # - Lock request priority names: a priority name (Symbol) => its priority (Integer from `-100` to `100`);
256
+ # - The names can be used instead of the Integer priorities in the `:priority` attribute and in
257
+ # `config['default_lock_priority']`: `rql.lock('name', priority: :high) { ... }`;
258
+ # - Define the names of your application (keep the default names with `merge`):
259
+ # `config['lock_priorities'] = config['lock_priorities'].merge(payment: 90, email: -10)`;
260
+ # - The default value is frozen (it is shared by all clients): assign a new hash, don't change it in place;
261
+ # - See [Lock Request Prioritization](#lock-request-prioritization) documentation section for details;
262
+ config['lock_priorities'] = { lowest: -100, low: -50, normal: 0, high: 50, highest: 100 }
263
+
264
+ # (integer/symbol) (default: 0)
265
+ # - The default lock request priority: an Integer from `-100` (the lowest) to `100` (the highest)
266
+ # or a priority name of `config['lock_priorities']`;
267
+ # - The priority name should be a key of `config['lock_priorities']` (`RedisQueuedLocks::ConfigValidationError` otherwise):
268
+ # it is checked once after the configuration block (`Client.new { ... }`, `#configure { ... }`: the keys can be assigned
269
+ # in any order; an inconsistent pair is never applied: both keys keep their previous values, also when the block fails)
270
+ # and on a direct `config[...] =` assignment (before the value is applied: change the priority names and the default
271
+ # name together in a configuration block);
272
+ # - Can be customized in methods `#lock`, `#lock!`, `#lock_series` and `#lock_series!` via `:priority` attribute;
273
+ # - `:queued` access strategy: the lock request is placed in the lock request queue after all requests of the same
274
+ # or higher priority and before all requests of the lower priority (FIFO inside the same priority);
275
+ # - See [Lock Request Prioritization](#lock-request-prioritization) documentation section for details;
276
+ config['default_lock_priority'] = 0
277
+
244
278
  # (symbol) (default: :wait_for_lock)
245
279
  # - Global default conflict strategy mode;
246
280
  # - Can be customized in methods `#lock` and `#lock!` via `:conflict_strategy` attribute (see method signatures of #lock and #lock! methods);
@@ -260,6 +294,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
260
294
  # - (the enabled lock index) how many locks (lock names of the lock index) will be processed at a time
261
295
  # (the ZSCAN count of the lock index pass);
262
296
  # - affects the performance of your Redis and Ruby Application (configure thoughtfully);
297
+ # - should be a positive Integer
263
298
  config['lock_release_batch_size'] = 100
264
299
 
265
300
  # (default: 300)
@@ -268,12 +303,14 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
268
303
  # (the ZSCAN count of the lock index pass, DEL for batch deletion);
269
304
  # - affects the ruby's memory (cuz this batch will be stored in Set object in first, and then will be splatted to the DEL redis method invocation) (configure thoughtfully);
270
305
  # - affects the performance of your Redis (configure thoughtfully);
306
+ # - should be a positive Integer
271
307
  config['clear_locks_of__lock_scan_size'] = 300
272
308
 
273
309
  # (default: 300)
274
310
  # - how many lock queues will be processed in one round trip in #clear_locks_of and in #clear_current_locks methods
275
311
  # (lock queues are found by the same SCAN pass as locks or derived from the lock names of the same lock index pass);
276
312
  # - affects the performance of your Redis (configure thoughtfully);
313
+ # - should be a positive Integer
277
314
  config['clear_locks_of__queue_scan_size'] = 300
278
315
 
279
316
  # (default: 500)
@@ -282,6 +319,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
282
319
  # - (the enabled lock index) how many locks (lock names of the lock index) should be processed at a time
283
320
  # (the ZSCAN count of the lock index pass);
284
321
  # - affects the performance of your Redis and Ruby Application (configure thoughtfully);
322
+ # - should be a positive Integer
285
323
  config['key_extraction_batch_size'] = 500
286
324
 
287
325
  # (default: 1 day)
@@ -327,15 +365,28 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
327
365
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
328
366
  # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
329
367
  # - at this moment the only debug logs are realised in following cases:
330
- # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
331
- # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
332
- # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
333
- # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
334
- # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
335
- # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
336
- # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
337
- # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
338
- # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat");
368
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
369
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
370
+ # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
371
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
372
+ # - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
373
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
374
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
375
+ # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
376
+ # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
377
+ # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
378
+ # - "[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");
380
+ # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
381
+ # - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
382
+ # - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
383
+ # - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
384
+ # - "[redis_queued_locks.release_locks_of]" (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
385
+ # - "[redis_queued_locks.extend_lock_ttl]" (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
386
+ # - "[redis_queued_locks.clear_dead_requests]" (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
387
+ # - "[redis_queued_locks.reindex_locks]" (logs "idx_key_cnt", "idx_time", "at");
388
+ # - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
389
+ # the mode of the series for lock series logs;
339
390
  # - by default uses VoidLogger that does nothing;
340
391
  config['logger'] = RedisQueuedLocks::Logging::VoidLogger
341
392
 
@@ -343,19 +394,26 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
343
394
  # - adds additional debug logs;
344
395
  # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
345
396
  # - it adds following debug logs in addition to the existing:
346
- # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
347
- # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
348
- # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
349
- # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status");
350
- # - "[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");
351
- # - "[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);
352
- # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat")";
353
- # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
354
- # - "[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");
355
- # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
356
- # - "[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>");
357
- # - "[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>");
358
- # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
397
+ # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
398
+ # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
399
+ # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
400
+ # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status");
401
+ # - "[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");
402
+ # - "[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");
403
+ # - "[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");
404
+ # - "[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");
405
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
406
+ # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
407
+ # - "[redis_queued_locks.try_lock.dead_request_removed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "dead_acq_id", "dead_priority");
408
+ # - "[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", "first_priority_in_queue");
409
+ # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
410
+ # - "[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", "first_priority_in_queue", "<current_lock_data>");
411
+ # - "[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", "first_priority_in_queue", "locked_by_acq_id", "<current_lock_data>");
412
+ # - "[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", "ahead_priority");
413
+ # - "[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", "ahead_priority");
414
+ # - "[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", "first_priority_in_queue");
415
+ # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
416
+ # - "rw_mode" is the requested lock mode ('write'/'read');
359
417
  config['log_lock_try'] = false
360
418
 
361
419
  # (default: false)
@@ -366,7 +424,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
366
424
 
367
425
  # (default: 15)
368
426
  # - the percent of cases that should be logged;
369
- # - take an effect when <config['log_sampling_enalbed']> is true;
427
+ # - take an effect when <config['log_sampling_enabled']> is true;
370
428
  # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
371
429
  config['log_sampling_percent'] = 15
372
430
 
@@ -383,11 +441,11 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
383
441
  # - enables <instrumentaion sampling>: only the configured percent of RQL cases will be instrumented;
384
442
  # - disabled by default;
385
443
  # - works in tandem with <config['instr_sampling_percent'] and <config['instr_sampler']>;
386
- config['nstr_sampling_enabled'] = false
444
+ config['instr_sampling_enabled'] = false
387
445
 
388
446
  # (default: 15)
389
447
  # - the percent of cases that should be instrumented;
390
- # - take an effect when <config['instr_sampling_enalbed']> is true;
448
+ # - take an effect when <config['instr_sampling_enabled']> is true;
391
449
  # - works in tandem with <config['instr_sampling_enabled']> and <config['instr_sampler']> configs;
392
450
  config['instr_sampling_percent'] = 15
393
451
 
@@ -409,15 +467,18 @@ end
409
467
  <sup>\[[back to top](#table-of-contents)\]</sup>
410
468
 
411
469
  - [lock](#lock---obtain-a-lock)
412
- - [lock!](#lock---exeptional-lock-obtaining)
470
+ - [lock!](#lock---exceptional-lock-obtaining)
413
471
  - [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
414
472
  - [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
415
473
  - [lock_info](#lock_info)
416
474
  - [queue_info](#queue_info)
417
475
  - [locked?](#locked)
418
476
  - [queued?](#queued)
419
- - [unlock](#unlock---release-a-lock)
420
- - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
477
+ - [unlock](#unlock---release-a-lock) (aka `release_lock`)
478
+ - [unlock_read](#unlock_read---release-the-read-lock-of-the-current-acquirer) (aka `release_read_lock`)
479
+ - [clear_locks](#clear_locks---release-all-locks-and-lock-queues) (aka `release_locks`)
480
+ - [clear_locks_of](#clear_locks_of) (aka `release_locks_of`)
481
+ - [clear_current_locks](#clear_current_locks) (aka `release_current_locks`)
421
482
  - [extend_lock_ttl](#extend_lock_ttl)
422
483
  - [locks](#locks---get-list-of-obtained-locks)
423
484
  - [queues](#queues---get-list-of-lock-request-queues)
@@ -430,6 +491,10 @@ end
430
491
  - [current_host_id](#current_host_id)
431
492
  - [possible_host_ids](#possible_host_ids)
432
493
 
494
+ The numeric options of the methods (ttls, timeouts, retries, scan and batch sizes, zombie ttl) are validated with the same
495
+ types and ranges as their `config` keys: `Integer` values only (`timeout: 0.5` is rejected), `RedisQueuedLocks::ArgumentError`
496
+ (`RedisQueuedLocks::SwarmArgumentError` for the swarm methods) otherwise.
497
+
433
498
  ---
434
499
 
435
500
  #### #lock - obtain a lock
@@ -465,6 +530,7 @@ def lock(
465
530
  conflict_strategy: config['default_conflict_strategy'],
466
531
  read_write_mode: :write,
467
532
  access_strategy: config['default_access_strategy'],
533
+ priority: config['default_lock_priority'],
468
534
  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config['uniq_identifier'] proc;
469
535
  meta: nil,
470
536
  detailed_acq_timeout_error: config['detailed_acq_timeout_error'],
@@ -488,6 +554,7 @@ def lock(
488
554
  - Lock name to be obtained.
489
555
  - `ttl` - (optional) - [Integer]
490
556
  - Lock's time to live (in milliseconds);
557
+ - should be a positive `Integer` (write and read locks);
491
558
  - pre-configured in `config['default_lock_ttl']`;
492
559
  - `queue_ttl` - (optional) `[Integer]`
493
560
  - Lifetime of the acuier's lock request. In seconds.
@@ -502,9 +569,14 @@ def lock(
502
569
  - pre-configured in `config['lock_index_grace_period']`;
503
570
  - `timeout` - (optional) `[Integer,NilClass]`
504
571
  - Time period a client should try to acquire the lock (in seconds). Nil means "without timeout".
572
+ - `0` is the same as `nil` (without timeout);
505
573
  - pre-configured in `config['try_to_lock_timeout']`;
574
+ - the timeout can not be swallowed by `rescue` of your logger/instrumenter (see `timed` below);
506
575
  - `timed` - (optiona) `[Boolean]`
507
576
  - Limit the invocation time period of the passed block of code by the lock's TTL.
577
+ - the block is interrupted by an internal `RedisQueuedLocks::Exception` (an `::Exception`, like `Timeout::ExitException` of Ruby's `Timeout`): `rescue => e`,
578
+ `rescue StandardError`, `rescue Timeout::Error` and `rescue nil` inside the block can not swallow it (only `rescue Exception`),
579
+ `ensure` of the block still runs;
508
580
  - pre-configured in `config['is_timed_by_default']`;
509
581
  - `false` by default;
510
582
  - `retry_count` - (optional) `[Integer,NilClass]`
@@ -519,7 +591,7 @@ def lock(
519
591
  - `instrumenter` - (optional) `[#notify]`
520
592
  - See RedisQueuedLocks::Instrument::ActiveSupport for example;
521
593
  - See [Instrumentation](#instrumentation) section of docs;
522
- - pre-configured in `config['isntrumenter']` with void notifier (`RedisQueuedLocks::Instrumenter::VoidNotifier`);
594
+ - pre-configured in `config['instrumenter']` with void notifier (`RedisQueuedLocks::Instrument::VoidNotifier`);
523
595
  - `instrument` - (optional) `[NilClass,Any]`
524
596
  - Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
525
597
  - `nil` by default (means "no custom instrumentation data");
@@ -541,6 +613,19 @@ def lock(
541
613
  if lock is free to obtain - it will be obtained;
542
614
  - pre-configured in `config['default_access_strategy']`;
543
615
  - See [Lock Access Strategies](#lock-access-strategies) documentation section for details;
616
+ - `priority` - (optional) - `[Integer,Symbol]`
617
+ - The lock request priority: an Integer from `-100` (the lowest) to `100` (the highest), `0` by default,
618
+ or a priority name of `config['lock_priorities']` (`:lowest`, `:low`, `:normal`, `:high`, `:highest` by default,
619
+ the names of your application can be configured);
620
+ - `:queued` access strategy: the lock request is placed in the lock request queue after all requests of the same or
621
+ higher priority and before all requests of the lower priority (strict priority, FIFO inside the same priority;
622
+ read and write lock requests too);
623
+ - `:random` access strategy: the lock request does not wait for its turn (it ignores the order of the lock request queues
624
+ and the priorities of other requests), but it is queued at the position of its priority like a `:queued` one: `:queued`
625
+ lock requests wait for it;
626
+ - the obtained lock is never taken away: the priority orders the waiting lock requests only;
627
+ - pre-configured in `config['default_lock_priority']`;
628
+ - See [Lock Request Prioritization](#lock-request-prioritization) documentation section for details;
544
629
  - `conflict_strategy` - (optional) - `[Symbol]`
545
630
  - The conflict strategy mode for cases when the process that obtained the lock
546
631
  want to acquire this lock again;
@@ -603,7 +688,7 @@ def lock(
603
688
  - pre-configured in `config['log_sampling_enabled']`;
604
689
  - `log_sampling_percent` - (optional) `[Integer]`
605
690
  - the percent of cases that should be logged;
606
- - take an effect when `log_sampling_enalbed` is true;
691
+ - take an effect when `log_sampling_enabled` is true;
607
692
  - works in tandem with `log_sampling_enabled` and `log_sampler` options;
608
693
  - pre-configured in `config['log_sampling_percent']`;
609
694
  - `log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
@@ -625,7 +710,7 @@ def lock(
625
710
  - pre-configured in `config['instr_sampling_enabled']`;
626
711
  - `instr_sampling_percent` - (optional) `[Integer]`
627
712
  - the percent of cases that should be instrumented;
628
- - take an effect when `instr_sampling_enalbed` is true;
713
+ - take an effect when `instr_sampling_enabled` is true;
629
714
  - works in tandem with `instr_sampling_enabled` and `instr_sampler` options;
630
715
  - pre-configured in `config['instr_sampling_percent']`;
631
716
  - `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
@@ -663,6 +748,7 @@ Return value:
663
748
  result: {
664
749
  lock_key: "rql:lock:my_lock",
665
750
  acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
751
+ hst_id: "rql:hst:26672/2280/2320/70ea5dbf10ea1056",
666
752
  ts: 1711909612.653696,
667
753
  ttl: 10000,
668
754
  process: :lock_obtaining
@@ -676,7 +762,8 @@ Return value:
676
762
  - `:lock_obtaining` - classic lock obtaining proces. Default behavior (`conflict_strategy: :wait_for_lock`);
677
763
  - `:extendable_conflict_work_through` - reentrant lock acquiring process with lock's TTL extension. Suitable for `conflict_strategy: :extendable_work_through`;
678
764
  - `:conflict_work_through` - reentrant lock acquiring process without lock's TTL extension. Suitable for `conflict_strategy: :work_through`;
679
- - `:dead_locking` - current process tries to acquire a lock that is already acquired by them. Suitalbe for `conflict_startegy: :dead_locking`;
765
+ - (`conflict_strategy: :dead_locking` does not obtain the lock already obtained by the current acquirer:
766
+ it fails with `{ ok: false, result: :conflict_dead_lock }`);
680
767
  - For more details see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) readme section;
681
768
  - For successful lock obtaining:
682
769
  ```ruby
@@ -688,7 +775,7 @@ Return value:
688
775
  hst_id: String, # host identifier ("process_id/thread_id/ractor_id/identity")
689
776
  ts: Float, # time (epoch) when lock was obtained (float, Time#to_f)
690
777
  ttl: Integer, # lock's time to live in milliseconds (integer)
691
- process: Symbol # which logical process has acquired the lock (:lock_obtaining, :extendable_conflict_work_through, :conflict_work_through, :conflict_dead_lock)
778
+ process: Symbol # which logical process has acquired the lock (:lock_obtaining, :extendable_conflict_work_through, :conflict_work_through)
692
779
  }
693
780
  }
694
781
  ```
@@ -700,21 +787,21 @@ Return value:
700
787
  result: {
701
788
  lock_key: "rql:lock:my_lock",
702
789
  acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
703
- acq_id: "rql:acq:26672/2280/2320/70ea5dbf10ea1056",
790
+ hst_id: "rql:hst:26672/2280/2320/70ea5dbf10ea1056",
704
791
  ts: 1711909612.653696,
705
792
  ttl: 10000,
706
- process: :lock_obtaining # for custom conflict strategies may be: :conflict_dead_lock, :conflict_work_through, :extendable_conflict_work_through
793
+ process: :lock_obtaining # for reentrant locks (conflict strategies): :conflict_work_through, :extendable_conflict_work_through
707
794
  }
708
795
  }
709
796
  ```
710
797
  - For failed lock obtaining:
711
798
  ```ruby
712
- { ok: false, result: :timeout_reached }
713
- { ok: false, result: :retry_count_reached }
799
+ { ok: false, result: :timeout_reached } # see <timeout> option
800
+ { ok: false, result: :retry_limit_reached } # see <retry_count> option
714
801
  { 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)
715
803
  { ok: false, result: :fail_fast_no_try } # see <fail_fast> option
716
804
  { ok: false, result: :fail_fast_after_try } # see <fail_fast> option
717
- { ok: false, result: :unknown }
718
805
  ```
719
806
 
720
807
  Examples:
@@ -775,8 +862,9 @@ rql.lock_info("my_lock")
775
862
  # =>
776
863
  {
777
864
  "lock_key" => "rql:lock:my_lock",
865
+ "rw_mode" => "write",
778
866
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
779
- "hst_id" => "rql:acq:123/456/678/374dd74324",
867
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
780
868
  "ts" => 123456789,
781
869
  "ini_ttl" => 123456,
782
870
  "rem_ttl" => 123440,
@@ -787,7 +875,7 @@ rql.lock_info("my_lock")
787
875
 
788
876
  - (`:queue_ttl`) setting a short limit of time to the lock request queue position (if a process fails to acquire
789
877
  the lock within this period of time (and before timeout/retry_count limits occurs of course) -
790
- its lock request will be moved to the end of queue):
878
+ its lock request will be moved to the end of queue: the end of the lock requests of its priority):
791
879
 
792
880
  ```ruby
793
881
  rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
@@ -899,6 +987,7 @@ def lock!(
899
987
  instrument: nil,
900
988
  instrumenter: config['instrumenter'],
901
989
  access_strategy: config['default_access_strategy'],
990
+ priority: config['default_lock_priority'],
902
991
  conflict_strategy: config['default_conflict_strategy'],
903
992
  read_write_mode: :write,
904
993
  log_sampling_enabled: config['log_sampling_enabled'],
@@ -947,6 +1036,7 @@ applied with the same value to the each lock acquirement process (for each lock
947
1036
  - reentrant locks (already obtained by your outer logic, `conflict_strategy: :work_through`/`:extendable_work_through`) are kept;
948
1037
  - a lock that is expired during your block and obtained by another acquirer is kept;
949
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));
950
1040
  - if you did not pass a block - all locks will be released with their forumla-based TTL described above;
951
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`);
952
1042
 
@@ -1027,9 +1117,9 @@ client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
1027
1117
 
1028
1118
  ```shell
1029
1119
  # NEW logs (examples)
1030
- [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'
1031
- [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)
1032
- [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'
1120
+ [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
1033
1123
  ```
1034
1124
 
1035
1125
  ----
@@ -1060,9 +1150,9 @@ client.lock_series!("x", "y", "z") { ...some_code... }
1060
1150
  - `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity);
1061
1151
  - `"hst_id"` - `string` - host identifier (process_id/thread_id/ractor_id/identity);
1062
1152
  - `"ts"` - `numeric`/`epoch` - the time when lock was obtained;
1063
- - `"init_ttl"` - `integer` - (milliseconds) initial lock key ttl;
1153
+ - `"ini_ttl"` - `integer` - (milliseconds) initial lock key ttl;
1064
1154
  - `"rem_ttl"` - `integer` - (milliseconds) remaining lock key ttl;
1065
- - `<custom metadata>`- `string`/`integer` - custom metadata passed to the `lock`/`lock!` methods via `meta:` keyword argument (see [lock]((#lock---obtain-a-lock)) method documentation);
1155
+ - `<custom metadata>`- `string`/`integer` - custom metadata passed to the `lock`/`lock!` methods via `meta:` keyword argument (see [lock](#lock---obtain-a-lock) method documentation);
1066
1156
  - additional keys for **reentrant locks** and **extendable reentrant locks**:
1067
1157
  - for any type of reentrant locks:
1068
1158
  - `"spc_cnt"` - `integer` - how many times the lock was obtained as reentrant lock;
@@ -1082,7 +1172,7 @@ rql.lock_info("your_lock_name")
1082
1172
  "lock_key" => "rql:lock:your_lock_name",
1083
1173
  "rw_mode" => "write",
1084
1174
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1085
- "hst_id" => "rql:acq:123/456/678/374dd74324",
1175
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
1086
1176
  "ts" => 123456789.12345,
1087
1177
  "ini_ttl" => 5_000,
1088
1178
  "rem_ttl" => 4_999
@@ -1099,7 +1189,7 @@ rql.lock_info("your_lock_name")
1099
1189
  "lock_key" => "rql:lock:your_lock_name",
1100
1190
  "rw_mode" => "write",
1101
1191
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1102
- "hst_id" => "rql:acq:123/456/678/374dd74324",
1192
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
1103
1193
  "ts" => 123456789.12345,
1104
1194
  "ini_ttl" => 5_000,
1105
1195
  "rem_ttl" => 4_999,
@@ -1122,12 +1212,12 @@ rql.lock_info("your_lock_name")
1122
1212
  "lock_key" => "rql:lock:your_lock_name",
1123
1213
  "rw_mode" => "write",
1124
1214
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1125
- "hst_id" => "rql:acq:123/456/678/374dd74324",
1215
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
1126
1216
  "ts" => 123456789.12345,
1127
1217
  "ini_ttl" => 5_000,
1128
1218
  "rem_ttl" => 9_444,
1129
1219
  # ==> keys for any type of reentrant lock:
1130
- "spc_count" => 2, # how many times the lock was obtained as reentrant lock
1220
+ "spc_cnt" => 2, # how many times the lock was obtained as reentrant lock
1131
1221
  # ==> keys for extendable reentarnt locks with `:extendable_work_through` strategy:
1132
1222
  "spc_ext_ttl" => 5_000, # sum of TTL of the each <extendable> reentrant lock (3_000 + 2_000)
1133
1223
  "l_spc_ext_ini_ttl" => 2_000, # TTL of the last <extendable> reentrant lock
@@ -1209,15 +1299,18 @@ lock acquirers and their position in queue. The queue of write lock requests is
1209
1299
  - get the lock queue information;
1210
1300
  - queue represents the ordered set of lock key reqests:
1211
1301
  - set is ordered by score in ASC manner (inside the Redis Set);
1212
- - score is represented as a timestamp when the lock request was made;
1302
+ - score is represented as a timestamp when the lock request was made shifted by the
1303
+ [priority](#lock-request-prioritization) band (`-priority * 2**32` seconds: the default priority `0` keeps the timestamp);
1213
1304
  - represents the acquirer identifier and their score as an array of hashes;
1214
1305
  - returns `nil` if lock queues (write and read) do not exist;
1215
1306
  - lock queue data (`Hash<String,String|Array<Hash<String|Numeric>>`):
1216
1307
  - `"lock_queue"` - `string` - lock queue key in redis (write lock requests);
1217
1308
  - `"queue"` - `array` - an array of lock requests (array of hashes):
1218
1309
  - `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
1219
- - `"score"` - `float`/`epoch` - time when the lock request was made (epoch);
1310
+ - `"score"` - `float`/`epoch` - the position of the lock request: time when the lock request was made (epoch) shifted by
1311
+ `-priority * 2**32` seconds (the time itself for the default priority `0`);
1220
1312
  - `"rw_mode"` - `string` - the requested lock mode (`"write"`);
1313
+ - `"priority"` - `integer` - the lock request priority (from `-100` to `100`, see [Lock Request Prioritization](#lock-request-prioritization));
1221
1314
  - `"read_lock_queue"`, `"read_queue"` - (only when read lock requests exist) the read lock queue key and read lock requests
1222
1315
  with the same structure (`"rw_mode"` - `"read"`), see [Read/Write Locks](#readwrite-locks);
1223
1316
 
@@ -1228,15 +1321,16 @@ rql.queue_info("your_lock_name")
1228
1321
  {
1229
1322
  "lock_queue" => "rql:lock_queue:your_lock_name",
1230
1323
  "queue" => [
1231
- { "acq_id" => "rql:acq:123/456/567/678/fa76df9cc2", "score" => 1711606640.540842, "rw_mode" => "write" },
1232
- { "acq_id" => "rql:acq:123/567/456/679/c7bfcaf4f9", "score" => 1711606640.540906, "rw_mode" => "write" },
1233
- { "acq_id" => "rql:acq:555/329/523/127/7329553b11", "score" => 1711606640.540963, "rw_mode" => "write" },
1324
+ # (a lock request of the high priority: ahead of the requests of the default priority)
1325
+ { "acq_id" => "rql:acq:123/456/567/678/fa76df9cc2", "score" => -2583360655.459158, "rw_mode" => "write", "priority" => 1 },
1326
+ { "acq_id" => "rql:acq:123/567/456/679/c7bfcaf4f9", "score" => 1711606640.540906, "rw_mode" => "write", "priority" => 0 },
1327
+ { "acq_id" => "rql:acq:555/329/523/127/7329553b11", "score" => 1711606640.540963, "rw_mode" => "write", "priority" => 0 },
1234
1328
  # ...etc
1235
1329
  ],
1236
1330
  # (when read lock requests exist)
1237
1331
  "read_lock_queue" => "rql:lock_read_queue:your_lock_name",
1238
1332
  "read_queue" => [
1239
- { "acq_id" => "rql:acq:123/456/567/700/fa76df9cc2", "score" => 1711606640.541011, "rw_mode" => "read" },
1333
+ { "acq_id" => "rql:acq:123/456/567/700/fa76df9cc2", "score" => 1711606640.541011, "rw_mode" => "read", "priority" => 0 },
1240
1334
  # ...etc
1241
1335
  ]
1242
1336
  }
@@ -1319,7 +1413,7 @@ rql.queued?("your_lock_name") # => true/false
1319
1413
  Return:
1320
1414
  - `[Hash<Symbol,Boolean|Hash<Symbol,Numeric|String|Symbol>>]` (`{ ok: true/false, result: Hasn }`);
1321
1415
  - `:result` format;
1322
- - `:rel_time` - `Float` - time spent to process redis commands (in seconds);
1416
+ - `:rel_time` - `Float` - time spent to process redis commands (in milliseconds);
1323
1417
  - `:rel_key` - `String` - released lock key (RedisQueudLocks-internal lock key name from Redis);
1324
1418
  - `:rel_queue` - `String` - released lock queue key (RedisQueuedLocks-internal queue key name from Redis);
1325
1419
  - `:queue_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required queue does not exist);
@@ -1334,7 +1428,7 @@ rql.unlock("your_lock_name")
1334
1428
  {
1335
1429
  ok: true,
1336
1430
  result: {
1337
- rel_time: 0.02, # time spent to lock release (in seconds)
1431
+ rel_time: 0.02, # time spent to lock release (in milliseconds)
1338
1432
  rel_key: "rql:lock:your_lock_name", # released lock key
1339
1433
  rel_queue: "rql:lock_queue:your_lock_name", # released lock key queue
1340
1434
  queue_res: :released, # or :nothing_to_release
@@ -1424,7 +1518,7 @@ rql.unlock_read('report', acquirer_id: reader['acq_id'])
1424
1518
  - pre-configured value in `config['logger']`;
1425
1519
  - `:instrumenter` - (optional) `[#notify]`
1426
1520
  - custom instrumenter object;
1427
- - pre-configured value in `config['isntrumenter']`;
1521
+ - pre-configured value in `config['instrumenter']`;
1428
1522
  - `:instrument` - (optional) `[NilClass,Any]`
1429
1523
  - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
1430
1524
  - `:log_sampling_enabled` - (optional) `[Boolean]`
@@ -1507,7 +1601,7 @@ rql.clear_locks
1507
1601
  - pre-configured value in `config['logger']`;
1508
1602
  - `:instrumenter` - (optional) `[#notify]`
1509
1603
  - custom instrumenter object;
1510
- - pre-configured value in `config['isntrumenter']`;
1604
+ - pre-configured value in `config['instrumenter']`;
1511
1605
  - `:instrument` - (optional) `[NilClass,Any]`
1512
1606
  - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
1513
1607
  - `:log_sampling_enabled` - (optional) `[Boolean]`
@@ -1615,7 +1709,7 @@ rql.clear_current_locks
1615
1709
  - pre-configured value in `config['logger']`;
1616
1710
  - `:instrumenter` - (optional) `[#notify]`
1617
1711
  - custom instrumenter object;
1618
- - pre-configured value in `config['isntrumenter']`;
1712
+ - pre-configured value in `config['instrumenter']`;
1619
1713
  - `:instrument` - (optional) `[NilClass,Any]`
1620
1714
  - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
1621
1715
  - `:log_sampling_enabled` - (optional) `[Boolean]`
@@ -1684,6 +1778,7 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1684
1778
  - the lock name which ttl should be extended;
1685
1779
  - `milliseconds` - (required) `[Integer]`
1686
1780
  - how many milliseconds should be added to the lock's TTL;
1781
+ - should be a positive `Integer`;
1687
1782
  - `:read_write_mode` - (optional) `[Symbol]`
1688
1783
  - `:write` (default) - extends the write lock (the behavior described below);
1689
1784
  - `:read` - extends the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
@@ -1743,17 +1838,10 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1743
1838
  and for the read lock of the current acquirer, the number of extended read locks for `all_read_locks: true`;
1744
1839
  - returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
1745
1840
  some steps of invocation (see **Important** section below);
1746
- - **Important**:
1747
- - the method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension;
1748
- - the method consists of two commands:
1749
- - (1) read current pttl;
1750
- - (2) set new ttl that is calculated as "current pttl + additional milliseconds";
1751
- - the method uses Redis'es **CAS** (check-and-set) behavior;
1752
- - what can happen during these steps:
1753
- - lock is expired between commands or before the first command;
1754
- - lock is expired before the second command;
1755
- - lock is expired AND newly acquired by another process (so you will extend the
1756
- totally new lock with fresh PTTL);
1841
+ - **Important** (`:write` mode):
1842
+ - the write lock TTL is extended atomically by one Lua script (`PTTL` + `PEXPIRE` with "current pttl + additional milliseconds");
1843
+ - the lock owner is not checked: the current write lock is extended, so when your lock is expired and newly obtained
1844
+ by another acquirer, the lock of that acquirer is extended;
1757
1845
  - use it at your own risk and consider the async nature when calling this method;
1758
1846
 
1759
1847
  ```ruby
@@ -1909,6 +1997,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
1909
1997
  "hst_id"=>"rql:hst:41478/4320/4360/848818f09d8c3420",
1910
1998
  "ts"=>1711607112.670343,
1911
1999
  "ini_ttl"=>15000,
2000
+ "lock_key"=>"rql:lock:some-lock-123",
2001
+ "rw_mode"=>"write",
1912
2002
  "rem_ttl"=>13998}},
1913
2003
  {:lock=>"rql:lock:some-lock-456",
1914
2004
  :status=>:released,
@@ -1929,8 +2019,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
1929
2019
  - returns `Set<Hash<Symbol,Any>>` (see [#queue_info](#queue_info) and examples below for details).
1930
2020
  - contained data: `{ queue: String, requests: Array<Hash<String,Any>> }`
1931
2021
  - `:queue` - `String` - lock key queue in Redis;
1932
- - `:requests` - `Array<Hash<String,Any>>` - lock requests in the que with their acquirer id, score and requested lock mode
1933
- (`"rw_mode"` - `"write"` for `rql:lock_queue:*`, `"read"` for read lock queues `rql:lock_read_queue:*`).
2022
+ - `:requests` - `Array<Hash<String,Any>>` - lock requests in the que with their acquirer id, score, requested lock mode
2023
+ (`"rw_mode"` - `"write"` for `rql:lock_queue:*`, `"read"` for read lock queues `rql:lock_read_queue:*`) and priority.
1934
2024
 
1935
2025
  ```ruby
1936
2026
  rql.queues_info # or rql.qeuues_info(scan_size: 123)
@@ -1938,13 +2028,13 @@ rql.queues_info # or rql.qeuues_info(scan_size: 123)
1938
2028
  => #<Set:
1939
2029
  {{:queue=>"rql:lock_queue:some-lock-123",
1940
2030
  :requests=>
1941
- [{"acq_id"=>"rql:acq:38529/4500/4520/4360/66093702f24a3129", "score"=>1711606640.540842, "rw_mode"=>"write"},
1942
- {"acq_id"=>"rql:acq:38529/4580/4600/4360/66093702f24a3129", "score"=>1711606640.540906, "rw_mode"=>"write"},
1943
- {"acq_id"=>"rql:acq:38529/4620/4640/4360/66093702f24a3129", "score"=>1711606640.5409632, "rw_mode"=>"write"}]},
2031
+ [{"acq_id"=>"rql:acq:38529/4500/4520/4360/66093702f24a3129", "score"=>1711606640.540842, "rw_mode"=>"write", "priority"=>0},
2032
+ {"acq_id"=>"rql:acq:38529/4580/4600/4360/66093702f24a3129", "score"=>1711606640.540906, "rw_mode"=>"write", "priority"=>0},
2033
+ {"acq_id"=>"rql:acq:38529/4620/4640/4360/66093702f24a3129", "score"=>1711606640.5409632, "rw_mode"=>"write", "priority"=>0}]},
1944
2034
  {:queue=>"rql:lock_read_queue:some-lock-456",
1945
2035
  :requests=>
1946
- [{"acq_id"=>"rql:acq:38529/4380/4400/4360/66093702f24a3129", "score"=>1711606640.540722, "rw_mode"=>"read"},
1947
- {"acq_id"=>"rql:acq:38529/4420/4440/4360/66093702f24a3129", "score"=>1711606640.5407748, "rw_mode"=>"read"}]},
2036
+ [{"acq_id"=>"rql:acq:38529/4380/4400/4360/66093702f24a3129", "score"=>1711606640.540722, "rw_mode"=>"read", "priority"=>0},
2037
+ {"acq_id"=>"rql:acq:38529/4420/4440/4360/66093702f24a3129", "score"=>1711606640.5407748, "rw_mode"=>"read", "priority"=>0}]},
1948
2038
  ...}>
1949
2039
  ```
1950
2040
  ---
@@ -1956,8 +2046,10 @@ rql.queues_info # or rql.qeuues_info(scan_size: 123)
1956
2046
  In some cases your lock requests may become "dead". It means that your lock request lives in lock queue in Redis without
1957
2047
  any processing. It can happen when your processs that are enqueeud to the lock queue is failed unexpectedly (for some reason)
1958
2048
  before the lock acquire moment occurs and when no any other process does not need this lock anymore.
1959
- For this case your lock reuquest will be cleared only when any process will try
1960
- to acquire this lock again (cuz lock acquirement triggers the removement of expired requests).
2049
+ For this case your lock request will be cleared only when another lock request meets it: a lock request of the same
2050
+ [priority](#lock-request-prioritization) (lock acquirement drops the expired requests of its priority) or a `:queued` lock request
2051
+ it blocks (the head of the queue, an earlier request of the other read/write mode); the dead requests of other priorities stay
2052
+ in the queue.
1961
2053
 
1962
2054
  NOTE: with the enabled [Lock Index](#lock-index) the lock request queue lives as long as its latest lock request (the queue TTL
1963
2055
  is refreshed with the request `queue_ttl` on each lock request), so the queue of dead requests expires by itself (queues of older
@@ -1966,7 +2058,12 @@ RQL versions and queues created with the disabled lock index have no TTL: see [#
1966
2058
  In order to help with these dead requests you may periodically call `#clear_dead_requests`
1967
2059
  with corresponding `:dead_ttl` option, that is pre-configured by default via `config['dead_request_ttl']`.
1968
2060
  Lock queues are found via the redis `SCAN` (one pass) or via the [Lock Index](#lock-index) (one `ZSCAN` pass, the keyspace is
1969
- never scanned: the enabled lock index).
2061
+ never scanned: the enabled lock index). Each batch of lock queues takes two round trips: the lock requests are read, then the dead
2062
+ requests of each [priority](#lock-request-prioritization) found in the queues are dropped (no second round trip without dead requests).
2063
+ With `by_priority: true` the lock requests are not read: only the first lock request of each priority is read (the dead requests
2064
+ of a priority are found by its first lock request): a batch takes one round trip per priority of its lock queue with the most
2065
+ priorities + 1 (the dead requests of a priority are dropped in the round trip that reads the next priority), so the transfer does not
2066
+ depend on the length of the lock queues: use it for long lock queues with few priorities.
1970
2067
 
1971
2068
  `:dead_ttl` option is required because of it is no any **fast** and **resource-free** way to understand which request
1972
2069
  is dead now and is it really dead cuz each request queue can host their requests with
@@ -1979,14 +2076,20 @@ Accepts:
1979
2076
  - `:scan_size` - (optional) `[Integer]`
1980
2077
  - the batch of scanned keys for Redis'es SCAN command;
1981
2078
  - (the enabled lock index) the `ZSCAN` count of the [Lock Index](#lock-index) pass and the number of locks (both lock queues
1982
- of each lock) processed in one round trip;
2079
+ of each lock) processed in one batch;
1983
2080
  - has a preconfigured valie in `config['lock_release_batch_size']`;
2081
+ - `:by_priority` - (optional) `[Boolean]`
2082
+ - read only the first lock request of each [priority](#lock-request-prioritization) in the lock queues instead of all lock
2083
+ requests: a batch of lock queues takes one round trip per priority of its lock queue with the most priorities + 1 (instead
2084
+ of two round trips), the transfer does not depend on the length of the lock queues (for long lock queues with few
2085
+ priorities);
2086
+ - `false` by default (all lock requests are read);
1984
2087
  - `:logger` - (optional) `[::Logger,#debug]`
1985
2088
  - custom logger object;
1986
2089
  - pre-configured in `config['logger']`;
1987
2090
  - `:instrumenter` - (optional) `[#notify]`
1988
2091
  - custom instrumenter object;
1989
- - pre-configured in `config['isntrumenter']`;
2092
+ - pre-configured in `config['instrumenter']`;
1990
2093
  - `:instrument` - (optional) `[NilClass,Any]`
1991
2094
  - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
1992
2095
  - `nil` by default (no additional data);
@@ -2017,7 +2120,8 @@ Accepts:
2017
2120
  - makes sense when instrumentation sampling is enabled;
2018
2121
  - `false` by default;
2019
2122
 
2020
- Returns: `{ ok: true, processed_queues: Set<String> }` returns the list of processed lock queues;
2123
+ Returns: `{ ok: true, result: { processed_queues: Set<String> } }` returns the set of processed lock request queues
2124
+ (write and read lock request queues);
2021
2125
 
2022
2126
  ```ruby
2023
2127
  rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
@@ -2025,13 +2129,19 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
2025
2129
  # =>
2026
2130
  {
2027
2131
  ok: true,
2028
- processed_queues: [
2029
- "rql:lock_queue:some-lock-123",
2030
- "rql:lock_queue:some-lock-456",
2031
- "rql:lock_queue:your-other-lock",
2032
- ...
2033
- ]
2132
+ result: {
2133
+ processed_queues: #<Set: {
2134
+ "rql:lock_queue:some-lock-123",
2135
+ "rql:lock_queue:some-lock-456",
2136
+ "rql:lock_read_queue:some-lock-456",
2137
+ "rql:lock_queue:your-other-lock",
2138
+ ...
2139
+ }>
2140
+ }
2034
2141
  }
2142
+
2143
+ # long lock queues with few priorities: only the first lock request of each priority is read
2144
+ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000, by_priority: true)
2035
2145
  ```
2036
2146
 
2037
2147
  ---
@@ -2061,6 +2171,41 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
2061
2171
  - `:lock_index_grace_period` - (optional) `[Integer]`
2062
2172
  - the lock index grace period (in milliseconds);
2063
2173
  - pre-configured in `config['lock_index_grace_period']`;
2174
+ - `:logger` - (optional) `[::Logger,#debug]`
2175
+ - custom logger object;
2176
+ - pre-configured in `config['logger']`;
2177
+ - `:instrumenter` - (optional) `[#notify]`
2178
+ - custom instrumenter object;
2179
+ - pre-configured in `config['instrumetner']`;
2180
+ - `:instrument` - (optional) `[NilClass,Any]`;
2181
+ - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
2182
+ - `nil` by default (no additional data);
2183
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
2184
+ - enables **log sampling**;
2185
+ - pre-configured in `config['log_sampling_enabled']`;
2186
+ - `:log_sampling_percent` - (optional) `[Integer]`
2187
+ - **log sampling**:the percent of cases that should be logged;
2188
+ - pre-configured in `config['log_sampling_percent']`;
2189
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
2190
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
2191
+ - pre-configured in `config['log_sampler']`;
2192
+ - `log_sample_this` - (optional) `[Boolean]`
2193
+ - marks the method that everything should be logged despite the enabled log sampling;
2194
+ - makes sense when log sampling is enabled;
2195
+ - `false` by default;
2196
+ - `:instr_sampling_enabled` - (optional) `[Boolean]`
2197
+ - enables **instrumentaion sampling**;
2198
+ - pre-configured in `config['instr_sampling_enabled']`;
2199
+ - `instr_sampling_percent` - (optional) `[Integer]`
2200
+ - the percent of cases that should be instrumented;
2201
+ - pre-configured in `config['instr_sampling_percent']`;
2202
+ - `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
2203
+ - percent-based log sampler that decides should be RQL case instrumented or not;
2204
+ - pre-configured in `config['instr_sampler']`;
2205
+ - `instr_sample_this` - (optional) `[Boolean]`
2206
+ - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;
2207
+ - makes sense when instrumentation sampling is enabled;
2208
+ - `false` by default;
2064
2209
  - returns `{ ok: true, result: { idx_key_cnt: Integer } }` (the number of indexed keys);
2065
2210
 
2066
2211
  ```ruby
@@ -2235,6 +2380,8 @@ rql.possible_host_ids
2235
2380
  # NOTE: auto-swarm your RQL client after initalization (run swarm elements and their supervisor)
2236
2381
  config['swarm.auto_swarm'] = false
2237
2382
 
2383
+ # NOTE: the periods, the zombie ttl and the scan sizes should be positive Integers
2384
+
2238
2385
  # supervisor configs
2239
2386
  config['swarm.supervisor.liveness_probing_period'] = 2 # NOTE: in seconds
2240
2387
 
@@ -2514,19 +2661,121 @@ rql.possible_host_ids
2514
2661
  - can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);
2515
2662
  - supports different strategies:
2516
2663
  - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;
2664
+ the queue is ordered by the [lock request priorities](#lock-request-prioritization) (FIFO inside the same priority);
2517
2665
  - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist, retries, timeouts and so on). if lock is free to obtain - it will be obtained;
2666
+ (the priorities of other lock requests are ignored too; the lock request itself is queued at the position of its
2667
+ [priority](#lock-request-prioritization) like a `:queued` one: `:queued` lock requests wait for it);
2518
2668
  - for current implementation detalis check:
2519
2669
  - [Configuration](#configuration) documentation: see `config['default_access_strategy']` config docs;
2520
2670
  - [#lock](#lock---obtain-a-lock) method documentation: see `access_strategy` attribute docs;
2521
2671
 
2522
2672
  ---
2523
2673
 
2674
+ ## Lock Request Prioritization
2675
+
2676
+ <sup>\[[back to top](#table-of-contents)\]</sup>
2677
+
2678
+ - `priority:` attribute of `#lock`, `#lock!`, `#lock_series` and `#lock_series!` (`config['default_lock_priority']` by default):
2679
+ an Integer from `-100` (the lowest priority) to `100` (the highest priority), `0` - (**default**) normal priority,
2680
+ or a priority name (Symbol) of `config['lock_priorities']`;
2681
+ - priority names (`config['lock_priorities']`, a priority name => its Integer priority):
2682
+ - default names: `:lowest` (`-100`), `:low` (`-50`), `:normal` (`0`), `:high` (`50`), `:highest` (`100`);
2683
+ - name the priorities of your application (keep the default names with `merge`; the default value is frozen: assign
2684
+ a new hash);
2685
+ - a name is resolved to its Integer priority on each lock acquirement: the lock request queues, `#queue_info`/`#queues_info`,
2686
+ logs and instrumentation have the Integer priority; an unknown name fails with `RedisQueuedLocks::ArgumentError`;
2687
+ - `config['default_lock_priority']` can be a priority name too: an unknown one fails the configuration with
2688
+ `RedisQueuedLocks::ConfigValidationError` (checked after the configuration block, the keys can be assigned in any order,
2689
+ and on a direct `config[...] =` assignment);
2690
+ - `:queued` access strategy (default): the lock request is placed in the lock request queue after all requests of the same
2691
+ or higher priority and before all requests of the lower priority: strict priority between priorities, FIFO inside the same
2692
+ priority (read and write lock requests too, see [Read/Write Locks](#readwrite-locks));
2693
+
2694
+ ```ruby
2695
+ # a balance operation goes ahead of the email notifications that are already waiting for the lock
2696
+ rql.lock("user_#{user.id}", priority: 50) { process_balance }
2697
+
2698
+ # a payment goes ahead of the balance operations
2699
+ rql.lock("user_#{user.id}", priority: 90) { process_payment }
2700
+
2701
+ # email notifications wait for all lock requests of higher priorities
2702
+ rql.lock("user_#{user.id}", priority: -10) { send_email }
2703
+
2704
+ # normal priority by default
2705
+ rql.lock("user_#{user.id}") { update_profile } # priority: 0
2706
+
2707
+ # the default priority of the client (for example, in background workers)
2708
+ rql.configure { |config| config['default_lock_priority'] = -10 }
2709
+
2710
+ # priority names: the default ones (:lowest, :low, :normal, :high, :highest) and the names of your application
2711
+ rql.configure do |config|
2712
+ config['lock_priorities'] = config['lock_priorities'].merge(payment: 90, balance: 50, email: -10)
2713
+ end
2714
+ rql.lock("user_#{user.id}", priority: :payment) { process_payment } # priority: 90
2715
+ rql.lock("user_#{user.id}", priority: :email) { send_email } # priority: -10
2716
+ rql.lock("user_#{user.id}", priority: :high) { update_profile } # priority: 50
2717
+ ```
2718
+
2719
+ - how it works:
2720
+ - the position of the lock request in the queue (the score) is the request time shifted by the priority band:
2721
+ `request time - priority * 2**32 seconds` (each priority has its own range of positions; the default priority `0` keeps
2722
+ the request time: the positions of RQL versions without priorities);
2723
+ - every lock request drops dead requests (older than `queue_ttl`) of its own priority in both lock queues (in the same round
2724
+ trip as before priorities); dead requests of other priorities that block it (the head of the queue and the earlier request
2725
+ of the other read/write mode: the priority and the request time are decoded from the position) are dropped with all dead
2726
+ requests of their priorities in one more round trip of the same attempt, so a dead request of any priority never blocks the
2727
+ queue and the cost of an attempt does not depend on the number of priorities in use;
2728
+ - dead requests of lower priorities never block: they are dropped by the lock requests of their priority (or lower ones)
2729
+ and by `#clear_dead_requests` (it drops the dead requests of every priority found in the queues);
2730
+ - lock requests at infinite positions (`-inf`/`+inf`: manual writes of the lock queues, RQL never writes them) have the
2731
+ bound priorities (`100`/`-100`) in `#queue_info`/`#queues_info` and logs; a request at `-inf` is a dead one (like before
2732
+ priorities): it is dropped when it blocks a lock request and by `#clear_dead_requests`; a request at `+inf` is behind all
2733
+ requests and is never dropped as a dead one;
2734
+ - `#queue_info`/`#queues_info` return the priority of each lock request (`"priority"`); the lock acquirement logs and
2735
+ instrumentation events (classic, reentrant and lock series ones) have the `priority` of the lock request, the `log_lock_try`
2736
+ logs also have the priorities of the requests ahead (`first_priority_in_queue`, `ahead_priority`, `dead_priority`;
2737
+ `first_priority_in_queue => nil` when the lock queue has no requests);
2738
+ - limitations and recommendations:
2739
+ - the priority orders the waiting lock requests only: the obtained lock is never taken away (no preemption);
2740
+ - strict priority: a continuous flow of requests of a higher priority can starve the requests of lower priorities (they
2741
+ reach their `timeout`/`retry_count` limits): give them longer limits;
2742
+ - the released lock is passed by polling: the next lock request notices the released lock on its next attempt
2743
+ (`retry_delay`): use a shorter `retry_delay` for the important logic;
2744
+ - the default `retry_count` (`3`) gives a short waiting time: the priority matters for the requests that wait longer
2745
+ (`retry_count: nil` with `timeout`);
2746
+ - requests of the same priority made within ~61 µs of each other are ordered by their acquirer ids (the precision of
2747
+ the shifted positions);
2748
+ - each lock request drops dead requests with its own `queue_ttl` (the requests of its priority and the requests that block it):
2749
+ a request that waits longer than the shorter `queue_ttl` of such requests is dropped by their attempts and comes back at its
2750
+ old position on its own attempts (its own `queue_ttl` is not reached yet), so the requests behind it with a shorter
2751
+ `queue_ttl` don't wait for it: it loses its place (and the advantage of its priority) to them, the lock goes to the request
2752
+ whose attempt finds the lock free first; use the same `queue_ttl` for the lock requests of a lock (a longer `queue_ttl` does
2753
+ not keep a request in the queue);
2754
+ - `:random` access strategy: the lock request does not wait for its turn (the order of the queues and the priorities of other
2755
+ requests are ignored), but it is queued at the position of its priority: `:queued` lock requests wait for it (a `:random`
2756
+ request of a high priority holds back the waiting `:queued` requests of lower priorities until it obtains the lock or
2757
+ leaves the queue);
2758
+ - dead lock requests are dropped by the lock requests that meet them (the requests of their priority and the requests they
2759
+ block): the dead requests of the priorities that no lock request uses any more (and the dead requests met only by `:random`
2760
+ lock requests) stay in the queues; they never block (an accepted trade-off of the head-driven cleanup: the cost of an
2761
+ attempt does not depend on the number of priorities); without the [Lock Index](#lock-index) (its queues expire) and the
2762
+ [swarm](#swarm-mode-and-zombie-locks) (it drops the requests of dead processes) call `#clear_dead_requests` periodically
2763
+ (e.g. from a scheduled job) to drop them (`by_priority: true` for long lock queues: only the first lock request of each
2764
+ priority is read);
2765
+ - all processes that share locks should use RQL versions with priorities before positive priorities are used (`:high`,
2766
+ `:highest`, any priority above `0`): older versions drop such lock requests as dead ones (the order is broken; the mutual
2767
+ exclusion is not affected: lock queues never take part in it); the default priority (`0`) and negative priorities
2768
+ (`:low`, `:lowest`) work with older versions too: older versions keep such requests behind their own ones and never drop
2769
+ them as dead ones (the dead ones are dropped by the upgraded processes);
2770
+
2771
+ ---
2772
+
2524
2773
  ## Read/Write Locks
2525
2774
 
2526
2775
  <sup>\[[back to top](#table-of-contents)\]</sup>
2527
2776
 
2528
2777
  - `read_write_mode:` attribute of `#lock` and `#lock!`:
2529
- - `:write` (default) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
2778
+ - `:write` (**default**) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
2530
2779
  - `:read` - shared lock: waits for the write lock only (read locks of different acquirers work in parallel);
2531
2780
 
2532
2781
  ```ruby
@@ -2535,17 +2784,22 @@ rql.lock('report', read_write_mode: :read) { read_report }
2535
2784
 
2536
2785
  # writer waits for all readers (and readers wait for the writer)
2537
2786
  rql.lock('report', read_write_mode: :write) { rebuild_report }
2538
- rql.lock('report') { rebuild_report } # (write mode is the default mode)
2787
+
2788
+ # write mode is default:
2789
+ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
2539
2790
  ```
2540
2791
 
2541
2792
  - lock request ordering (`access_strategy`):
2542
2793
  - `:queued` (default) - FIFO between modes: read lock request waits for every earlier write lock request,
2543
2794
  write lock request waits for every earlier read and write lock request (so writers are not starved by a
2544
- continuous flow of readers and consecutive readers obtain the lock together);
2545
- - `:random` - lock queues are ignored: any free lock is obtained (a continuous flow of readers can starve writers);
2795
+ continuous flow of readers and consecutive readers obtain the lock together); with
2796
+ [lock request priorities](#lock-request-prioritization) "earlier" means "ahead in the queue": a request of a higher
2797
+ priority is ahead of all requests of lower priorities in both modes (FIFO inside the same priority);
2798
+ - `:random` - the order of the lock queues is ignored: any free lock is obtained (a continuous flow of readers can starve
2799
+ writers); the lock request itself is queued like any lock request (`:queued` lock requests wait for it);
2546
2800
  - read locks:
2547
2801
  - each read lock has its own `ttl` and is released (after the block execution) or expired independently of other read locks;
2548
- - `ttl` should be a positive `Integer` (`RedisQueuedLocks::ArgumentError`);
2802
+ - `ttl` should be a positive `Integer`;
2549
2803
  - each read lock has its own data with the same structure as the write lock data: acquirer, host, timestamp,
2550
2804
  initial ttl, `:meta` (with the same reserved keys) and reentrant lock data (`spc_cnt`, `spc_ext_ttl`, ...);
2551
2805
  the read lock data lives as long as the read lock and is returned by `#lock_info`/`#locks_info` for each reader
@@ -2641,12 +2895,13 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2641
2895
  - default logs (raised from `#lock`/`#lock!`):
2642
2896
 
2643
2897
  ```ruby
2644
- "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2645
- "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2646
- "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2647
- "[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2648
- "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2649
- "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2898
+ "[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2899
+ "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2900
+ "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2901
+ "[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
2902
+ "[redis_queued_locks.lock_obtaining_failed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
2903
+ "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
2904
+ "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
2650
2905
  "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2651
2906
  "[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2652
2907
  "[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
@@ -2663,24 +2918,38 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2663
2918
  "[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");
2664
2919
  "[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");
2665
2920
  "[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");
2666
- "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2921
+ "[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2667
2922
  "[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2668
- "[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");
2923
+ "[redis_queued_locks.try_lock.dead_request_removed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "dead_acq_id", "dead_priority");
2924
+ "[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", "first_priority_in_queue");
2669
2925
  "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2670
- "[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>");
2671
- "[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>");
2672
- "[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");
2673
- "[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");
2674
- "[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");
2926
+ "[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", "first_priority_in_queue", "<current_lock_data>");
2927
+ "[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", "first_priority_in_queue", "locked_by_acq_id", "<current_lock_data>");
2928
+ "[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", "ahead_priority");
2929
+ "[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", "ahead_priority");
2930
+ "[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", "first_priority_in_queue");
2675
2931
  "[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2676
2932
  ```
2677
2933
 
2678
2934
  - lock series logs (raised from `#lock_series`/`#lock_series!`, PoC; `rw_mode` is the mode of the series):
2679
2935
 
2680
2936
  ```ruby
2681
- "[redis_queued_locks.start_lock_series_obtaining]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2682
- "[redis_queued_locks.lock_series_obtained]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2683
- "[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2937
+ "[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");
2939
+ "[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2940
+ ```
2941
+
2942
+ - operation logs (one log line per operation that changes Redis keys, with the time of the whole operation; the same data as
2943
+ the instrumentation event of the operation without `instrument`):
2944
+
2945
+ ```ruby
2946
+ "[redis_queued_locks.explicit_lock_release]" # (logs "lock_key", "lock_key_queue", "rel_time", "at");
2947
+ "[redis_queued_locks.explicit_read_lock_release]" # (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
2948
+ "[redis_queued_locks.explicit_all_locks_release]" # (logs "at", "rel_time", "rel_key_cnt");
2949
+ "[redis_queued_locks.release_locks_of]" # (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
2950
+ "[redis_queued_locks.extend_lock_ttl]" # (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
2951
+ "[redis_queued_locks.clear_dead_requests]" # (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
2952
+ "[redis_queued_locks.reindex_locks]" # (logs "idx_key_cnt", "idx_time", "at");
2684
2953
  ```
2685
2954
 
2686
2955
  - read/write try-lock logs (see [Read/Write Locks](#readwrite-locks)):
@@ -2698,7 +2967,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2698
2967
  <sup>\[[back to top](#table-of-contents)\]</sup>
2699
2968
 
2700
2969
  **NOTICE**: logging can be sampled via:
2701
- - `config['log_samplign_enabled'] = true` (**false** by default);
2970
+ - `config['log_sampling_enabled'] = true` (**false** by default);
2702
2971
  - `config['log_sampler'] = RedisQueuedLocks::Logging::Sampler` (used by default);
2703
2972
  - see **RedisQueuedLocks::Logging::Sampler** implementation in source code for customization details;
2704
2973
 
@@ -2708,18 +2977,26 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2708
2977
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
2709
2978
  # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
2710
2979
  # - at this moment the only debug logs are realised in following cases:
2711
- # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2712
- # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2713
- # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2714
- # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2715
- # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2716
- # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2980
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2981
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2982
+ # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2983
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
2984
+ # - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
2985
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
2986
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
2717
2987
  # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2718
2988
  # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2719
2989
  # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2720
- # - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2721
- # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2722
- # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2990
+ # - "[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");
2992
+ # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2993
+ # - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
2994
+ # - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
2995
+ # - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
2996
+ # - "[redis_queued_locks.release_locks_of]" (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
2997
+ # - "[redis_queued_locks.extend_lock_ttl]" (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
2998
+ # - "[redis_queued_locks.clear_dead_requests]" (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
2999
+ # - "[redis_queued_locks.reindex_locks]" (logs "idx_key_cnt", "idx_time", "at");
2723
3000
  # - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
2724
3001
  # the mode of the series for lock series logs;
2725
3002
  # - by default uses VoidLogger that does nothing;
@@ -2737,15 +3014,16 @@ config['logger'] = RedisQueuedLocks::Logging::VoidLogger
2737
3014
  # - "[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");
2738
3015
  # - "[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");
2739
3016
  # - "[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");
2740
- # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
3017
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
2741
3018
  # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2742
- # - "[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");
3019
+ # - "[redis_queued_locks.try_lock.dead_request_removed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "dead_acq_id", "dead_priority");
3020
+ # - "[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", "first_priority_in_queue");
2743
3021
  # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2744
- # - "[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>");
2745
- # - "[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>");
2746
- # - "[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");
2747
- # - "[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");
2748
- # - "[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");
3022
+ # - "[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", "first_priority_in_queue", "<current_lock_data>");
3023
+ # - "[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", "first_priority_in_queue", "locked_by_acq_id", "<current_lock_data>");
3024
+ # - "[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", "ahead_priority");
3025
+ # - "[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", "ahead_priority");
3026
+ # - "[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", "first_priority_in_queue");
2749
3027
  # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2750
3028
  # - "rw_mode" is the requested lock mode ('write'/'read');
2751
3029
  config['log_lock_try'] = false
@@ -2758,7 +3036,7 @@ config['log_sampling_enabled'] = false
2758
3036
 
2759
3037
  # (default: 15)
2760
3038
  # - the percent of cases that should be logged;
2761
- # - take an effect when <config['log_sampling_enalbed']> is true;
3039
+ # - take an effect when <config['log_sampling_enabled']> is true;
2762
3040
  # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
2763
3041
  config['log_sampling_percent'] = 15
2764
3042
 
@@ -2825,7 +3103,7 @@ config['instr_sampling_enabled'] = false
2825
3103
 
2826
3104
  # (default: 15)
2827
3105
  # - the percent of cases that should be instrumented;
2828
- # - take an effect when <config['instr_sampling_enalbed']> is true;
3106
+ # - take an effect when <config['instr_sampling_enabled']> is true;
2829
3107
  # - works in tandem with <config['instr_sampling_enabled']> and <config['instr_sampler']> configs;
2830
3108
  config['instr_sampling_percent'] = 15
2831
3109
 
@@ -2848,6 +3126,7 @@ config['instr_sampler'] = RedisQueuedLocks::Instrument::Sampler
2848
3126
  List of instrumentation events
2849
3127
 
2850
3128
  - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
3129
+ - ["redis_queued_locks.lock_obtaining_failed"](#redis_queued_lockslock_obtaining_failed)
2851
3130
  - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
2852
3131
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
2853
3132
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
@@ -2858,6 +3137,17 @@ List of instrumentation events
2858
3137
  - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
2859
3138
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
2860
3139
  - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
3140
+ - ["redis_queued_locks.extend_lock_ttl"](#redis_queued_locksextend_lock_ttl)
3141
+ - ["redis_queued_locks.clear_dead_requests"](#redis_queued_locksclear_dead_requests)
3142
+ - ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
3143
+
3144
+ 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);
3147
+ - each operation that changes Redis keys (`#unlock`, `#unlock_read`, `#clear_locks`, `#clear_locks_of`/`#clear_current_locks`,
3148
+ `#extend_lock_ttl`, `#clear_dead_requests`, `#reindex_locks`): one event with the time of the whole operation;
3149
+ - lock information queries (`#lock_info`, `#locks`, `#queues`, `#keys`, ...) are not instrumented;
3150
+ - the swarm (`#probe_hosts`, `#flush_zombies` and the swarm elements) is not instrumented yet;
2861
3151
 
2862
3152
  Detalized event semantics and payload structure:
2863
3153
 
@@ -2871,10 +3161,29 @@ Detalized event semantics and payload structure:
2871
3161
  - `:hst_id` - `string` - lock's host identifier;
2872
3162
  - `:lock_key` - `string` - lock name;
2873
3163
  - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3164
+ - `:priority` - `integer` - lock request priority: from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
2874
3165
  - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend;
2875
3166
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2876
3167
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2877
3168
 
3169
+ ##### `"redis_queued_locks.lock_obtaining_failed"`
3170
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3171
+ - 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);
3174
+ - payload:
3175
+ - `:lock_key` - `string` - lock name;
3176
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3177
+ - `:priority` - `integer` - lock request priority: from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
3178
+ - `:ttl` - `integer`/`milliseconds` - requested lock ttl;
3179
+ - `:acq_id` - `string` - lock acquirer identifier;
3180
+ - `:hst_id` - `string` - lock's host identifier;
3181
+ - `:ts` - `numeric`/`epoch` - the time when the lock acquirement is failed;
3182
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
3183
+ - `:result` - `symbol` - the failure result: `:timeout_reached`, `:retry_limit_reached`, `:fail_fast_no_try`,
3184
+ `:fail_fast_after_try`, `:conflict_dead_lock`, `:conflict_lock_upgrade` (see [lock](#lock---obtain-a-lock));
3185
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
3186
+
2878
3187
  ##### `"redis_queued_locks.extendable_reentrant_lock_obtained"`
2879
3188
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2880
3189
  - an event signalizes about the "extendable reentrant lock" obtaining is happened;
@@ -2883,6 +3192,7 @@ Detalized event semantics and payload structure:
2883
3192
  - `:lock_key` - `string` - lock name;
2884
3193
  - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2885
3194
  a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
3195
+ - `:priority` - `integer` - requested lock request priority: from `-100` to `100` (a priority name is reported as its integer priority; a reentrant lock request is not queued, see [Lock Request Prioritization](#lock-request-prioritization));
2886
3196
  - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2887
3197
  - `:acq_id` - `string` - lock acquirer identifier;
2888
3198
  - `:hst_id` - `string` - lock's host identifier;
@@ -2898,6 +3208,7 @@ Detalized event semantics and payload structure:
2898
3208
  - `:lock_key` - `string` - lock name;
2899
3209
  - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2900
3210
  a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
3211
+ - `:priority` - `integer` - requested lock request priority: from `-100` to `100` (a priority name is reported as its integer priority; a reentrant lock request is not queued, see [Lock Request Prioritization](#lock-request-prioritization));
2901
3212
  - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2902
3213
  - `:acq_id` - `string` - lock acquirer identifier;
2903
3214
  - `:hst_id` - `string` - lock's host identifier;
@@ -2916,6 +3227,7 @@ Detalized event semantics and payload structure:
2916
3227
  - `:hst_id` - `string` - lock's host identifier;
2917
3228
  - `:lock_key` - `string` - lock name;
2918
3229
  - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3230
+ - `:priority` - `integer` - lock request priority: from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
2919
3231
  - `:ts` - `numeric`/`epoch` - the time when lock was obtained;
2920
3232
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2921
3233
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
@@ -2934,6 +3246,7 @@ Detalized event semantics and payload structure:
2934
3246
  - `:lock_key` - `string` - lock name;
2935
3247
  - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
2936
3248
  a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
3249
+ - `:priority` - `integer` - requested lock request priority: from `-100` to `100` (a priority name is reported as its integer priority; a reentrant lock request is not queued, see [Lock Request Prioritization](#lock-request-prioritization));
2937
3250
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2938
3251
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2939
3252
 
@@ -2944,6 +3257,7 @@ Detalized event semantics and payload structure:
2944
3257
  - payload:
2945
3258
  - `:lock_keys` - `array<string>` - lock names of the series;
2946
3259
  - `:rw_mode` - `symbol` - the mode of the series (`read_write_mode`): `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3260
+ - `: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));
2947
3261
  - `:ttl` - `integer`/`milliseconds` - the `ttl` option of the series (each lock of the series gets its own ttl,
2948
3262
  see [lock_series](#lock_series---poc-acquire-a-series-of-locks));
2949
3263
  - `:acq_id` - `string` - lock acquirer identifier;
@@ -2959,6 +3273,7 @@ Detalized event semantics and payload structure:
2959
3273
  - payload:
2960
3274
  - `:lock_keys` - `array<string>` - lock names of the series;
2961
3275
  - `:rw_mode` - `symbol` - the mode of the series (`read_write_mode`): `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3276
+ - `: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));
2962
3277
  - `:hold_time` - `float`/`milliseconds` - the series hold time;
2963
3278
  - `:ttl` - `integer`/`milliseconds` - the `ttl` option of the series;
2964
3279
  - `:acq_id` - `string` - lock acquirer identifier;
@@ -2977,6 +3292,7 @@ Detalized event semantics and payload structure:
2977
3292
  - `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
2978
3293
  - `:lock_key` - `string` - released lock (lock name);
2979
3294
  - `:lock_key_queue` - `string` - released lock queue (the queue of write lock requests);
3295
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#unlock` method as `:instrument` attribute;
2980
3296
 
2981
3297
  ##### `"redis_queued_locks.explicit_read_lock_release"`
2982
3298
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
@@ -2999,6 +3315,7 @@ Detalized event semantics and payload structure:
2999
3315
  - `:at` - `float`/`epoch` - the time when the operation has ended;
3000
3316
  - `:rel_key_cnt` - `integer` - released redis keys count (write locks, readers registries, read lock data,
3001
3317
  write and read lock request queues);
3318
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#clear_locks` method as `:instrument` attribute;
3002
3319
 
3003
3320
  ##### `"redis_queued_locks.release_locks_of"`
3004
3321
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
@@ -3011,6 +3328,42 @@ Detalized event semantics and payload structure:
3011
3328
  - `:hst_id` - `string` - refused host identifier;
3012
3329
  - `:rel_key_cnt` - `integer` - released locks count (write locks and read locks);
3013
3330
  - `:tch_queue_cnt` - `:integer` - the number of queues (write and read lock request queues) from which the concrete host/acquirer was removed;
3331
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#clear_locks_of`/`#clear_current_locks` method as `:instrument` attribute;
3332
+
3333
+ ##### `"redis_queued_locks.extend_lock_ttl"`
3334
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3335
+ - an event signalizes about the lock TTL extension (successful or not);
3336
+ - raised from `#extend_lock_ttl`;
3337
+ - payload:
3338
+ - `:lock_key` - `string` - lock key (`rql:lock:<lock_name>`);
3339
+ - `:rw_mode` - `symbol` - `:write` (the write lock) or `:read` (the read lock of the acquirer or all read locks);
3340
+ - `:all_read_locks` - `boolean` - all read locks of the lock are extended (`:read` mode);
3341
+ - `:ext_ttl` - `integer`/`milliseconds` - the extension (`milliseconds` argument);
3342
+ - `:extended_locks_count` - `integer` - the number of extended locks (`0` when nothing is extended);
3343
+ - `:ext_time` - `float`/`milliseconds` - time spent on the extension;
3344
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
3345
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#extend_lock_ttl` method as `:instrument` attribute;
3346
+
3347
+ ##### `"redis_queued_locks.clear_dead_requests"`
3348
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3349
+ - an event signalizes about the dropped dead lock requests;
3350
+ - raised from `#clear_dead_requests`;
3351
+ - payload:
3352
+ - `:dead_ttl` - `integer`/`milliseconds` - lock requests older than `dead_ttl` are dropped;
3353
+ - `:prc_queue_cnt` - `integer` - the number of processed lock request queues (write and read);
3354
+ - `:rel_time` - `float`/`milliseconds` - time spent on the operation;
3355
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
3356
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#clear_dead_requests` method as `:instrument` attribute;
3357
+
3358
+ ##### `"redis_queued_locks.reindex_locks"`
3359
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3360
+ - an event signalizes about the lock keys added to the [Lock Index](#lock-index);
3361
+ - raised from `#reindex_locks`;
3362
+ - payload:
3363
+ - `:idx_key_cnt` - `integer` - the number of indexed keys;
3364
+ - `:idx_time` - `float`/`milliseconds` - time spent on the operation;
3365
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
3366
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#reindex_locks` method as `:instrument` attribute;
3014
3367
 
3015
3368
  ---
3016
3369
 
@@ -3028,7 +3381,6 @@ Detalized event semantics and payload structure:
3028
3381
  - isolated timeouts which are independent of internal Ruby's timeout implementation (where all timeouts are hostend inside
3029
3382
  the global "timeout request" queue and managed by a single global "timeout wathcer" thread). it should prevent any logic and timeout intersections,
3030
3383
  some GVL-related things and problem situations when the global watcher thread is "dead";
3031
- - lock request prioritization;
3032
3384
  - **strict redlock algorithm support** (support for many `RedisClient` instances that are fully independent (distributed redis instances));
3033
3385
  - #lock_series` - acquire a series of locks:
3034
3386
  - (NOTE: "Proof of Concept" implementation is ready to use);
@@ -3046,6 +3398,7 @@ Detalized event semantics and payload structure:
3046
3398
  Mutex (instantiated inside the main Ractor) from the our non-main Ractor (swar-mode works under the non-main ractor) and this process fails\
3047
3399
  on non-sharable memory error);
3048
3400
  - **Minor**:
3401
+ - `*_status` aliases for `*_info` methods;
3049
3402
  - an ability to return all insturmentation metrics from the `lock` invocation (and after block `yield`ing);
3050
3403
  - think about a PG-like `stats`-data/table, that should be helpful for lock history acquirement analyzation :thinking: (suitable for cases when we want
3051
3404
  to check if the lock was acquired by some different parallel process durign the some period of time in the past or not);
@@ -3094,6 +3447,17 @@ Detalized event semantics and payload structure:
3094
3447
  bundle exec rake rspec
3095
3448
  ```
3096
3449
 
3450
+ - Tests in parallel processes (`RSpec`, ~6x faster): each process (worker) works with its own pair of Redis databases
3451
+ (the specs use db 0 and db 1), so `WORKERS=n` flushes db `0..2n-1` of the local Redis (the default number of workers:
3452
+ the Redis `databases` / 2, up to the CPU count); the examples are distributed by their durations of the previous runs
3453
+ (`tmp/rspec_parallel/durations.json`), the output of each worker is in `tmp/rspec_parallel/worker-<n>.log`, the coverage
3454
+ report of the whole run is collated into `coverage/` after all workers:
3455
+ ```shell
3456
+ bundle exec rake rspec:parallel
3457
+ WORKERS=4 bundle exec rake rspec:parallel
3458
+ bundle exec rake "rspec:parallel[spec/redis_queued_locks_spec.rb]" # spec files/filters
3459
+ ```
3460
+
3097
3461
  - Linting (`Rubocop`+`RBS`):
3098
3462
  ```shell
3099
3463
  bundle exec rake rubocop
@@ -3106,7 +3470,8 @@ bundle exec rake steep:check
3106
3470
 
3107
3471
  - `Runtime` TypeChecking (`RBS`):
3108
3472
  ```shell
3109
- bundle exec rbs collection install && RBS_TEST_RAISE=true RUBYOPT='-rrbs/test/setup' RBS_TEST_OPT='-I sig' RBS_TEST_LOGLEVEL=error RBS_TEST_TARGET='RedisQueuedLocks::*' bundle exec rspec --failure-exit-code=0
3473
+ bundle exec rbs collection install && RBS_TEST_RAISE=true RUBYOPT='-rbundler/setup -rrbs/test/setup' RBS_TEST_OPT='-I sig' RBS_TEST_LOGLEVEL=error RBS_TEST_TARGET='RedisQueuedLocks::*' RBS_TEST_SKIP='::RedisQueuedLocks::Swarm::FlushZombies,::RedisQueuedLocks::Swarm::SwarmElement::Isolated,::RedisQueuedLocks::Swarm::RedisClientBuilder' bundle exec rspec
3474
+ # or in parallel processes: the same environment with `bundle exec rake rspec:parallel` instead of `bundle exec rspec`
3110
3475
  ```
3111
3476
 
3112
3477
  ---