redis_queued_locks 1.20.0 → 1.21.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +35 -13
  3. data/.claude/rules/acquirer.md +10 -9
  4. data/.claude/rules/logic.md +28 -5
  5. data/.claude/rules/swarm.md +2 -2
  6. data/.claude/rules/tests.md +6 -3
  7. data/.claude/rules/visitors.md +9 -10
  8. data/.claude/skills/rql-code-review/SKILL.md +24 -10
  9. data/CHANGELOG.md +126 -0
  10. data/CLAUDE.md +2 -1
  11. data/README.md +245 -70
  12. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
  13. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
  14. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
  15. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
  16. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +12 -1
  17. data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +5 -5
  20. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +111 -24
  21. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +86 -8
  22. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +122 -12
  23. data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
  24. data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
  25. data/lib/redis_queued_locks/acquirer/keys.rb +10 -1
  26. data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
  27. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
  28. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
  29. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +55 -15
  30. data/lib/redis_queued_locks/acquirer/locks.rb +26 -9
  31. data/lib/redis_queued_locks/acquirer/queue_info.rb +0 -1
  32. data/lib/redis_queued_locks/acquirer/queues.rb +10 -1
  33. data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
  34. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +31 -4
  35. data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
  36. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +39 -4
  37. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
  38. data/lib/redis_queued_locks/client.rb +82 -40
  39. data/lib/redis_queued_locks/config.rb +12 -13
  40. data/lib/redis_queued_locks/errors.rb +34 -4
  41. data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
  42. data/lib/redis_queued_locks/logging/sampler.rb +4 -8
  43. data/lib/redis_queued_locks/resource.rb +14 -4
  44. data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
  45. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
  46. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
  47. data/lib/redis_queued_locks/swarm.rb +86 -5
  48. data/lib/redis_queued_locks/utilities.rb +1 -1
  49. data/lib/redis_queued_locks/version.rb +2 -2
  50. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
  51. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
  52. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
  53. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +1 -1
  54. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +9 -0
  55. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +15 -2
  56. data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
  57. data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
  58. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
  59. data/sig/redis_queued_locks/client.rbs +14 -3
  60. data/sig/redis_queued_locks/errors.rbs +5 -2
  61. data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
  62. data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
  63. data/sig/redis_queued_locks/resource.rbs +1 -1
  64. data/sig/vendor/redis_client.rbs +1 -0
  65. metadata +1 -1
data/README.md CHANGED
@@ -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)
@@ -78,6 +79,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
78
79
  - [Instrumentation Configuration](#instrumentation-configuration)
79
80
  - [Instrumentation Events](#instrumentation-events)
80
81
  - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
82
+ - ["redis_queued_locks.lock_obtaining_failed"](#redis_queued_lockslock_obtaining_failed)
81
83
  - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
82
84
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
83
85
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
@@ -88,6 +90,9 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
88
90
  - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
89
91
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
90
92
  - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
93
+ - ["redis_queued_locks.extend_lock_ttl"](#redis_queued_locksextend_lock_ttl)
94
+ - ["redis_queued_locks.clear_dead_requests"](#redis_queued_locksclear_dead_requests)
95
+ - ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
91
96
  - [Roadmap](#roadmap)
92
97
  - [Build and Develop](#build-and-develop)
93
98
  - [Contributing](#contributing)
@@ -191,16 +196,19 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
191
196
 
192
197
  # (seconds) (supports nil)
193
198
  # - nil means "no timeout" and you are only limited by "retry_count" config;
199
+ # - 0 is the same as nil (no timeout);
194
200
  config['try_to_lock_timeout'] = 10
195
201
 
196
202
  # (milliseconds) (default: 5_000)
197
203
  # - lock's time to live
204
+ # - should be a positive Integer
198
205
  config['default_lock_ttl'] = 5_000
199
206
 
200
207
  # (seconds) (default: 15)
201
208
  # - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the queue);
202
209
  # - (the enabled lock index) the lock request queue lives as long as its latest lock request (the queue TTL is refreshed with
203
210
  # the queue ttl on each lock request): queues of dead acquirers expire by themselves;
211
+ # - should be a positive Integer
204
212
  config['default_queue_ttl'] = 15
205
213
 
206
214
  # (boolean) (default: false)
@@ -260,6 +268,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
260
268
  # - (the enabled lock index) how many locks (lock names of the lock index) will be processed at a time
261
269
  # (the ZSCAN count of the lock index pass);
262
270
  # - affects the performance of your Redis and Ruby Application (configure thoughtfully);
271
+ # - should be a positive Integer
263
272
  config['lock_release_batch_size'] = 100
264
273
 
265
274
  # (default: 300)
@@ -268,12 +277,14 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
268
277
  # (the ZSCAN count of the lock index pass, DEL for batch deletion);
269
278
  # - 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
279
  # - affects the performance of your Redis (configure thoughtfully);
280
+ # - should be a positive Integer
271
281
  config['clear_locks_of__lock_scan_size'] = 300
272
282
 
273
283
  # (default: 300)
274
284
  # - how many lock queues will be processed in one round trip in #clear_locks_of and in #clear_current_locks methods
275
285
  # (lock queues are found by the same SCAN pass as locks or derived from the lock names of the same lock index pass);
276
286
  # - affects the performance of your Redis (configure thoughtfully);
287
+ # - should be a positive Integer
277
288
  config['clear_locks_of__queue_scan_size'] = 300
278
289
 
279
290
  # (default: 500)
@@ -282,6 +293,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
282
293
  # - (the enabled lock index) how many locks (lock names of the lock index) should be processed at a time
283
294
  # (the ZSCAN count of the lock index pass);
284
295
  # - affects the performance of your Redis and Ruby Application (configure thoughtfully);
296
+ # - should be a positive Integer
285
297
  config['key_extraction_batch_size'] = 500
286
298
 
287
299
  # (default: 1 day)
@@ -327,15 +339,28 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
327
339
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
328
340
  # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
329
341
  # - 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");
342
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
343
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
344
+ # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
345
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
346
+ # - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time", "result");
347
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
348
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
349
+ # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
350
+ # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
351
+ # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
352
+ # - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
353
+ # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
354
+ # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
355
+ # - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
356
+ # - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
357
+ # - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
358
+ # - "[redis_queued_locks.release_locks_of]" (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
359
+ # - "[redis_queued_locks.extend_lock_ttl]" (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
360
+ # - "[redis_queued_locks.clear_dead_requests]" (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
361
+ # - "[redis_queued_locks.reindex_locks]" (logs "idx_key_cnt", "idx_time", "at");
362
+ # - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
363
+ # the mode of the series for lock series logs;
339
364
  # - by default uses VoidLogger that does nothing;
340
365
  config['logger'] = RedisQueuedLocks::Logging::VoidLogger
341
366
 
@@ -343,19 +368,25 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
343
368
  # - adds additional debug logs;
344
369
  # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
345
370
  # - 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");
371
+ # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
372
+ # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
373
+ # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
374
+ # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status");
375
+ # - "[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");
376
+ # - "[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");
377
+ # - "[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");
378
+ # - "[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");
379
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
380
+ # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
381
+ # - "[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");
382
+ # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
383
+ # - "[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>");
384
+ # - "[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>");
385
+ # - "[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");
386
+ # - "[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");
387
+ # - "[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");
388
+ # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
389
+ # - "rw_mode" is the requested lock mode ('write'/'read');
359
390
  config['log_lock_try'] = false
360
391
 
361
392
  # (default: false)
@@ -366,7 +397,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
366
397
 
367
398
  # (default: 15)
368
399
  # - the percent of cases that should be logged;
369
- # - take an effect when <config['log_sampling_enalbed']> is true;
400
+ # - take an effect when <config['log_sampling_enabled']> is true;
370
401
  # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
371
402
  config['log_sampling_percent'] = 15
372
403
 
@@ -383,11 +414,11 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
383
414
  # - enables <instrumentaion sampling>: only the configured percent of RQL cases will be instrumented;
384
415
  # - disabled by default;
385
416
  # - works in tandem with <config['instr_sampling_percent'] and <config['instr_sampler']>;
386
- config['nstr_sampling_enabled'] = false
417
+ config['instr_sampling_enabled'] = false
387
418
 
388
419
  # (default: 15)
389
420
  # - the percent of cases that should be instrumented;
390
- # - take an effect when <config['instr_sampling_enalbed']> is true;
421
+ # - take an effect when <config['instr_sampling_enabled']> is true;
391
422
  # - works in tandem with <config['instr_sampling_enabled']> and <config['instr_sampler']> configs;
392
423
  config['instr_sampling_percent'] = 15
393
424
 
@@ -409,15 +440,18 @@ end
409
440
  <sup>\[[back to top](#table-of-contents)\]</sup>
410
441
 
411
442
  - [lock](#lock---obtain-a-lock)
412
- - [lock!](#lock---exeptional-lock-obtaining)
443
+ - [lock!](#lock---exceptional-lock-obtaining)
413
444
  - [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
414
445
  - [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
415
446
  - [lock_info](#lock_info)
416
447
  - [queue_info](#queue_info)
417
448
  - [locked?](#locked)
418
449
  - [queued?](#queued)
419
- - [unlock](#unlock---release-a-lock)
420
- - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
450
+ - [unlock](#unlock---release-a-lock) (aka `release_lock`)
451
+ - [unlock_read](#unlock_read---release-the-read-lock-of-the-current-acquirer) (aka `release_read_lock`)
452
+ - [clear_locks](#clear_locks---release-all-locks-and-lock-queues) (aka `release_locks`)
453
+ - [clear_locks_of](#clear_locks_of) (aka `release_locks_of`)
454
+ - [clear_current_locks](#clear_current_locks) (aka `release_current_locks`)
421
455
  - [extend_lock_ttl](#extend_lock_ttl)
422
456
  - [locks](#locks---get-list-of-obtained-locks)
423
457
  - [queues](#queues---get-list-of-lock-request-queues)
@@ -430,6 +464,10 @@ end
430
464
  - [current_host_id](#current_host_id)
431
465
  - [possible_host_ids](#possible_host_ids)
432
466
 
467
+ The numeric options of the methods (ttls, timeouts, retries, scan and batch sizes, zombie ttl) are validated with the same
468
+ types and ranges as their `config` keys: `Integer` values only (`timeout: 0.5` is rejected), `RedisQueuedLocks::ArgumentError`
469
+ (`RedisQueuedLocks::SwarmArgumentError` for the swarm methods) otherwise.
470
+
433
471
  ---
434
472
 
435
473
  #### #lock - obtain a lock
@@ -488,6 +526,7 @@ def lock(
488
526
  - Lock name to be obtained.
489
527
  - `ttl` - (optional) - [Integer]
490
528
  - Lock's time to live (in milliseconds);
529
+ - should be a positive `Integer` (write and read locks);
491
530
  - pre-configured in `config['default_lock_ttl']`;
492
531
  - `queue_ttl` - (optional) `[Integer]`
493
532
  - Lifetime of the acuier's lock request. In seconds.
@@ -502,9 +541,14 @@ def lock(
502
541
  - pre-configured in `config['lock_index_grace_period']`;
503
542
  - `timeout` - (optional) `[Integer,NilClass]`
504
543
  - Time period a client should try to acquire the lock (in seconds). Nil means "without timeout".
544
+ - `0` is the same as `nil` (without timeout);
505
545
  - pre-configured in `config['try_to_lock_timeout']`;
546
+ - the timeout can not be swallowed by `rescue` of your logger/instrumenter (see `timed` below);
506
547
  - `timed` - (optiona) `[Boolean]`
507
548
  - Limit the invocation time period of the passed block of code by the lock's TTL.
549
+ - the block is interrupted by an internal `RedisQueuedLocks::Exception` (an `::Exception`, like `Timeout::ExitException` of Ruby's `Timeout`): `rescue => e`,
550
+ `rescue StandardError`, `rescue Timeout::Error` and `rescue nil` inside the block can not swallow it (only `rescue Exception`),
551
+ `ensure` of the block still runs;
508
552
  - pre-configured in `config['is_timed_by_default']`;
509
553
  - `false` by default;
510
554
  - `retry_count` - (optional) `[Integer,NilClass]`
@@ -603,7 +647,7 @@ def lock(
603
647
  - pre-configured in `config['log_sampling_enabled']`;
604
648
  - `log_sampling_percent` - (optional) `[Integer]`
605
649
  - the percent of cases that should be logged;
606
- - take an effect when `log_sampling_enalbed` is true;
650
+ - take an effect when `log_sampling_enabled` is true;
607
651
  - works in tandem with `log_sampling_enabled` and `log_sampler` options;
608
652
  - pre-configured in `config['log_sampling_percent']`;
609
653
  - `log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
@@ -625,7 +669,7 @@ def lock(
625
669
  - pre-configured in `config['instr_sampling_enabled']`;
626
670
  - `instr_sampling_percent` - (optional) `[Integer]`
627
671
  - the percent of cases that should be instrumented;
628
- - take an effect when `instr_sampling_enalbed` is true;
672
+ - take an effect when `instr_sampling_enabled` is true;
629
673
  - works in tandem with `instr_sampling_enabled` and `instr_sampler` options;
630
674
  - pre-configured in `config['instr_sampling_percent']`;
631
675
  - `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
@@ -663,6 +707,7 @@ Return value:
663
707
  result: {
664
708
  lock_key: "rql:lock:my_lock",
665
709
  acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
710
+ hst_id: "rql:hst:26672/2280/2320/70ea5dbf10ea1056",
666
711
  ts: 1711909612.653696,
667
712
  ttl: 10000,
668
713
  process: :lock_obtaining
@@ -676,7 +721,8 @@ Return value:
676
721
  - `:lock_obtaining` - classic lock obtaining proces. Default behavior (`conflict_strategy: :wait_for_lock`);
677
722
  - `:extendable_conflict_work_through` - reentrant lock acquiring process with lock's TTL extension. Suitable for `conflict_strategy: :extendable_work_through`;
678
723
  - `: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`;
724
+ - (`conflict_strategy: :dead_locking` does not obtain the lock already obtained by the current acquirer:
725
+ it fails with `{ ok: false, result: :conflict_dead_lock }`);
680
726
  - For more details see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) readme section;
681
727
  - For successful lock obtaining:
682
728
  ```ruby
@@ -688,7 +734,7 @@ Return value:
688
734
  hst_id: String, # host identifier ("process_id/thread_id/ractor_id/identity")
689
735
  ts: Float, # time (epoch) when lock was obtained (float, Time#to_f)
690
736
  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)
737
+ process: Symbol # which logical process has acquired the lock (:lock_obtaining, :extendable_conflict_work_through, :conflict_work_through)
692
738
  }
693
739
  }
694
740
  ```
@@ -700,21 +746,21 @@ Return value:
700
746
  result: {
701
747
  lock_key: "rql:lock:my_lock",
702
748
  acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
703
- acq_id: "rql:acq:26672/2280/2320/70ea5dbf10ea1056",
749
+ hst_id: "rql:hst:26672/2280/2320/70ea5dbf10ea1056",
704
750
  ts: 1711909612.653696,
705
751
  ttl: 10000,
706
- process: :lock_obtaining # for custom conflict strategies may be: :conflict_dead_lock, :conflict_work_through, :extendable_conflict_work_through
752
+ process: :lock_obtaining # for reentrant locks (conflict strategies): :conflict_work_through, :extendable_conflict_work_through
707
753
  }
708
754
  }
709
755
  ```
710
756
  - For failed lock obtaining:
711
757
  ```ruby
712
- { ok: false, result: :timeout_reached }
713
- { ok: false, result: :retry_count_reached }
758
+ { ok: false, result: :timeout_reached } # see <timeout> option
759
+ { ok: false, result: :retry_limit_reached } # see <retry_count> option
714
760
  { ok: false, result: :conflict_dead_lock } # see <conflict_strategy> option for details (:dead_locking strategy)
761
+ { ok: false, result: :conflict_lock_upgrade } # read-to-write lock upgrade (see <read_write_mode> and <conflict_strategy> options)
715
762
  { ok: false, result: :fail_fast_no_try } # see <fail_fast> option
716
763
  { ok: false, result: :fail_fast_after_try } # see <fail_fast> option
717
- { ok: false, result: :unknown }
718
764
  ```
719
765
 
720
766
  Examples:
@@ -775,8 +821,9 @@ rql.lock_info("my_lock")
775
821
  # =>
776
822
  {
777
823
  "lock_key" => "rql:lock:my_lock",
824
+ "rw_mode" => "write",
778
825
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
779
- "hst_id" => "rql:acq:123/456/678/374dd74324",
826
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
780
827
  "ts" => 123456789,
781
828
  "ini_ttl" => 123456,
782
829
  "rem_ttl" => 123440,
@@ -1060,9 +1107,9 @@ client.lock_series!("x", "y", "z") { ...some_code... }
1060
1107
  - `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity);
1061
1108
  - `"hst_id"` - `string` - host identifier (process_id/thread_id/ractor_id/identity);
1062
1109
  - `"ts"` - `numeric`/`epoch` - the time when lock was obtained;
1063
- - `"init_ttl"` - `integer` - (milliseconds) initial lock key ttl;
1110
+ - `"ini_ttl"` - `integer` - (milliseconds) initial lock key ttl;
1064
1111
  - `"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);
1112
+ - `<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
1113
  - additional keys for **reentrant locks** and **extendable reentrant locks**:
1067
1114
  - for any type of reentrant locks:
1068
1115
  - `"spc_cnt"` - `integer` - how many times the lock was obtained as reentrant lock;
@@ -1082,7 +1129,7 @@ rql.lock_info("your_lock_name")
1082
1129
  "lock_key" => "rql:lock:your_lock_name",
1083
1130
  "rw_mode" => "write",
1084
1131
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1085
- "hst_id" => "rql:acq:123/456/678/374dd74324",
1132
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
1086
1133
  "ts" => 123456789.12345,
1087
1134
  "ini_ttl" => 5_000,
1088
1135
  "rem_ttl" => 4_999
@@ -1099,7 +1146,7 @@ rql.lock_info("your_lock_name")
1099
1146
  "lock_key" => "rql:lock:your_lock_name",
1100
1147
  "rw_mode" => "write",
1101
1148
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1102
- "hst_id" => "rql:acq:123/456/678/374dd74324",
1149
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
1103
1150
  "ts" => 123456789.12345,
1104
1151
  "ini_ttl" => 5_000,
1105
1152
  "rem_ttl" => 4_999,
@@ -1122,12 +1169,12 @@ rql.lock_info("your_lock_name")
1122
1169
  "lock_key" => "rql:lock:your_lock_name",
1123
1170
  "rw_mode" => "write",
1124
1171
  "acq_id" => "rql:acq:123/456/567/678/374dd74324",
1125
- "hst_id" => "rql:acq:123/456/678/374dd74324",
1172
+ "hst_id" => "rql:hst:123/456/678/374dd74324",
1126
1173
  "ts" => 123456789.12345,
1127
1174
  "ini_ttl" => 5_000,
1128
1175
  "rem_ttl" => 9_444,
1129
1176
  # ==> keys for any type of reentrant lock:
1130
- "spc_count" => 2, # how many times the lock was obtained as reentrant lock
1177
+ "spc_cnt" => 2, # how many times the lock was obtained as reentrant lock
1131
1178
  # ==> keys for extendable reentarnt locks with `:extendable_work_through` strategy:
1132
1179
  "spc_ext_ttl" => 5_000, # sum of TTL of the each <extendable> reentrant lock (3_000 + 2_000)
1133
1180
  "l_spc_ext_ini_ttl" => 2_000, # TTL of the last <extendable> reentrant lock
@@ -1319,7 +1366,7 @@ rql.queued?("your_lock_name") # => true/false
1319
1366
  Return:
1320
1367
  - `[Hash<Symbol,Boolean|Hash<Symbol,Numeric|String|Symbol>>]` (`{ ok: true/false, result: Hasn }`);
1321
1368
  - `:result` format;
1322
- - `:rel_time` - `Float` - time spent to process redis commands (in seconds);
1369
+ - `:rel_time` - `Float` - time spent to process redis commands (in milliseconds);
1323
1370
  - `:rel_key` - `String` - released lock key (RedisQueudLocks-internal lock key name from Redis);
1324
1371
  - `:rel_queue` - `String` - released lock queue key (RedisQueuedLocks-internal queue key name from Redis);
1325
1372
  - `:queue_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required queue does not exist);
@@ -1334,7 +1381,7 @@ rql.unlock("your_lock_name")
1334
1381
  {
1335
1382
  ok: true,
1336
1383
  result: {
1337
- rel_time: 0.02, # time spent to lock release (in seconds)
1384
+ rel_time: 0.02, # time spent to lock release (in milliseconds)
1338
1385
  rel_key: "rql:lock:your_lock_name", # released lock key
1339
1386
  rel_queue: "rql:lock_queue:your_lock_name", # released lock key queue
1340
1387
  queue_res: :released, # or :nothing_to_release
@@ -1684,6 +1731,7 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1684
1731
  - the lock name which ttl should be extended;
1685
1732
  - `milliseconds` - (required) `[Integer]`
1686
1733
  - how many milliseconds should be added to the lock's TTL;
1734
+ - should be a positive `Integer`;
1687
1735
  - `:read_write_mode` - (optional) `[Symbol]`
1688
1736
  - `:write` (default) - extends the write lock (the behavior described below);
1689
1737
  - `:read` - extends the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
@@ -1743,17 +1791,10 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
1743
1791
  and for the read lock of the current acquirer, the number of extended read locks for `all_read_locks: true`;
1744
1792
  - returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
1745
1793
  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);
1794
+ - **Important** (`:write` mode):
1795
+ - the write lock TTL is extended atomically by one Lua script (`PTTL` + `PEXPIRE` with "current pttl + additional milliseconds");
1796
+ - the lock owner is not checked: the current write lock is extended, so when your lock is expired and newly obtained
1797
+ by another acquirer, the lock of that acquirer is extended;
1757
1798
  - use it at your own risk and consider the async nature when calling this method;
1758
1799
 
1759
1800
  ```ruby
@@ -1909,6 +1950,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
1909
1950
  "hst_id"=>"rql:hst:41478/4320/4360/848818f09d8c3420",
1910
1951
  "ts"=>1711607112.670343,
1911
1952
  "ini_ttl"=>15000,
1953
+ "lock_key"=>"rql:lock:some-lock-123",
1954
+ "rw_mode"=>"write",
1912
1955
  "rem_ttl"=>13998}},
1913
1956
  {:lock=>"rql:lock:some-lock-456",
1914
1957
  :status=>:released,
@@ -2017,7 +2060,8 @@ Accepts:
2017
2060
  - makes sense when instrumentation sampling is enabled;
2018
2061
  - `false` by default;
2019
2062
 
2020
- Returns: `{ ok: true, processed_queues: Set<String> }` returns the list of processed lock queues;
2063
+ Returns: `{ ok: true, result: { processed_queues: Set<String> } }` returns the set of processed lock request queues
2064
+ (write and read lock request queues);
2021
2065
 
2022
2066
  ```ruby
2023
2067
  rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
@@ -2025,12 +2069,15 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
2025
2069
  # =>
2026
2070
  {
2027
2071
  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
- ]
2072
+ result: {
2073
+ processed_queues: #<Set: {
2074
+ "rql:lock_queue:some-lock-123",
2075
+ "rql:lock_queue:some-lock-456",
2076
+ "rql:lock_read_queue:some-lock-456",
2077
+ "rql:lock_queue:your-other-lock",
2078
+ ...
2079
+ }>
2080
+ }
2034
2081
  }
2035
2082
  ```
2036
2083
 
@@ -2061,6 +2108,41 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
2061
2108
  - `:lock_index_grace_period` - (optional) `[Integer]`
2062
2109
  - the lock index grace period (in milliseconds);
2063
2110
  - pre-configured in `config['lock_index_grace_period']`;
2111
+ - `:logger` - (optional) `[::Logger,#debug]`
2112
+ - custom logger object;
2113
+ - pre-configured in `config['logger']`;
2114
+ - `:instrumenter` - (optional) `[#notify]`
2115
+ - custom instrumenter object;
2116
+ - pre-configured in `config['instrumetner']`;
2117
+ - `:instrument` - (optional) `[NilClass,Any]`;
2118
+ - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
2119
+ - `nil` by default (no additional data);
2120
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
2121
+ - enables **log sampling**;
2122
+ - pre-configured in `config['log_sampling_enabled']`;
2123
+ - `:log_sampling_percent` - (optional) `[Integer]`
2124
+ - **log sampling**:the percent of cases that should be logged;
2125
+ - pre-configured in `config['log_sampling_percent']`;
2126
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
2127
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
2128
+ - pre-configured in `config['log_sampler']`;
2129
+ - `log_sample_this` - (optional) `[Boolean]`
2130
+ - marks the method that everything should be logged despite the enabled log sampling;
2131
+ - makes sense when log sampling is enabled;
2132
+ - `false` by default;
2133
+ - `:instr_sampling_enabled` - (optional) `[Boolean]`
2134
+ - enables **instrumentaion sampling**;
2135
+ - pre-configured in `config['instr_sampling_enabled']`;
2136
+ - `instr_sampling_percent` - (optional) `[Integer]`
2137
+ - the percent of cases that should be instrumented;
2138
+ - pre-configured in `config['instr_sampling_percent']`;
2139
+ - `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
2140
+ - percent-based log sampler that decides should be RQL case instrumented or not;
2141
+ - pre-configured in `config['instr_sampler']`;
2142
+ - `instr_sample_this` - (optional) `[Boolean]`
2143
+ - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;
2144
+ - makes sense when instrumentation sampling is enabled;
2145
+ - `false` by default;
2064
2146
  - returns `{ ok: true, result: { idx_key_cnt: Integer } }` (the number of indexed keys);
2065
2147
 
2066
2148
  ```ruby
@@ -2235,6 +2317,8 @@ rql.possible_host_ids
2235
2317
  # NOTE: auto-swarm your RQL client after initalization (run swarm elements and their supervisor)
2236
2318
  config['swarm.auto_swarm'] = false
2237
2319
 
2320
+ # NOTE: the periods, the zombie ttl and the scan sizes should be positive Integers
2321
+
2238
2322
  # supervisor configs
2239
2323
  config['swarm.supervisor.liveness_probing_period'] = 2 # NOTE: in seconds
2240
2324
 
@@ -2526,7 +2610,7 @@ rql.possible_host_ids
2526
2610
  <sup>\[[back to top](#table-of-contents)\]</sup>
2527
2611
 
2528
2612
  - `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;
2613
+ - `:write` (**default**) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
2530
2614
  - `:read` - shared lock: waits for the write lock only (read locks of different acquirers work in parallel);
2531
2615
 
2532
2616
  ```ruby
@@ -2535,7 +2619,9 @@ rql.lock('report', read_write_mode: :read) { read_report }
2535
2619
 
2536
2620
  # writer waits for all readers (and readers wait for the writer)
2537
2621
  rql.lock('report', read_write_mode: :write) { rebuild_report }
2538
- rql.lock('report') { rebuild_report } # (write mode is the default mode)
2622
+
2623
+ # write mode is default:
2624
+ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
2539
2625
  ```
2540
2626
 
2541
2627
  - lock request ordering (`access_strategy`):
@@ -2545,7 +2631,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2545
2631
  - `:random` - lock queues are ignored: any free lock is obtained (a continuous flow of readers can starve writers);
2546
2632
  - read locks:
2547
2633
  - 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`);
2634
+ - `ttl` should be a positive `Integer`;
2549
2635
  - each read lock has its own data with the same structure as the write lock data: acquirer, host, timestamp,
2550
2636
  initial ttl, `:meta` (with the same reserved keys) and reentrant lock data (`spc_cnt`, `spc_ext_ttl`, ...);
2551
2637
  the read lock data lives as long as the read lock and is returned by `#lock_info`/`#locks_info` for each reader
@@ -2645,6 +2731,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2645
2731
  "[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2646
2732
  "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2647
2733
  "[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2734
+ "[redis_queued_locks.lock_obtaining_failed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time", "result");
2648
2735
  "[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2649
2736
  "[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2650
2737
  "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
@@ -2683,6 +2770,19 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2683
2770
  "[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2684
2771
  ```
2685
2772
 
2773
+ - operation logs (one log line per operation that changes Redis keys, with the time of the whole operation; the same data as
2774
+ the instrumentation event of the operation without `instrument`):
2775
+
2776
+ ```ruby
2777
+ "[redis_queued_locks.explicit_lock_release]" # (logs "lock_key", "lock_key_queue", "rel_time", "at");
2778
+ "[redis_queued_locks.explicit_read_lock_release]" # (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
2779
+ "[redis_queued_locks.explicit_all_locks_release]" # (logs "at", "rel_time", "rel_key_cnt");
2780
+ "[redis_queued_locks.release_locks_of]" # (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
2781
+ "[redis_queued_locks.extend_lock_ttl]" # (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
2782
+ "[redis_queued_locks.clear_dead_requests]" # (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
2783
+ "[redis_queued_locks.reindex_locks]" # (logs "idx_key_cnt", "idx_time", "at");
2784
+ ```
2785
+
2686
2786
  - read/write try-lock logs (see [Read/Write Locks](#readwrite-locks)):
2687
2787
  - `single_process_lock_conflict__lock_upgrade` - write lock request of the acquirer that holds the read lock
2688
2788
  (read-to-write lock upgrade is not supported for `:work_through` and `:extendable_work_through` conflict strategies);
@@ -2698,7 +2798,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2698
2798
  <sup>\[[back to top](#table-of-contents)\]</sup>
2699
2799
 
2700
2800
  **NOTICE**: logging can be sampled via:
2701
- - `config['log_samplign_enabled'] = true` (**false** by default);
2801
+ - `config['log_sampling_enabled'] = true` (**false** by default);
2702
2802
  - `config['log_sampler'] = RedisQueuedLocks::Logging::Sampler` (used by default);
2703
2803
  - see **RedisQueuedLocks::Logging::Sampler** implementation in source code for customization details;
2704
2804
 
@@ -2712,6 +2812,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2712
2812
  # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2713
2813
  # - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2714
2814
  # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2815
+ # - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time", "result");
2715
2816
  # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2716
2817
  # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2717
2818
  # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
@@ -2720,6 +2821,13 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
2720
2821
  # - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2721
2822
  # - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
2722
2823
  # - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
2824
+ # - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
2825
+ # - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
2826
+ # - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
2827
+ # - "[redis_queued_locks.release_locks_of]" (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
2828
+ # - "[redis_queued_locks.extend_lock_ttl]" (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
2829
+ # - "[redis_queued_locks.clear_dead_requests]" (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
2830
+ # - "[redis_queued_locks.reindex_locks]" (logs "idx_key_cnt", "idx_time", "at");
2723
2831
  # - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
2724
2832
  # the mode of the series for lock series logs;
2725
2833
  # - by default uses VoidLogger that does nothing;
@@ -2758,7 +2866,7 @@ config['log_sampling_enabled'] = false
2758
2866
 
2759
2867
  # (default: 15)
2760
2868
  # - the percent of cases that should be logged;
2761
- # - take an effect when <config['log_sampling_enalbed']> is true;
2869
+ # - take an effect when <config['log_sampling_enabled']> is true;
2762
2870
  # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
2763
2871
  config['log_sampling_percent'] = 15
2764
2872
 
@@ -2825,7 +2933,7 @@ config['instr_sampling_enabled'] = false
2825
2933
 
2826
2934
  # (default: 15)
2827
2935
  # - the percent of cases that should be instrumented;
2828
- # - take an effect when <config['instr_sampling_enalbed']> is true;
2936
+ # - take an effect when <config['instr_sampling_enabled']> is true;
2829
2937
  # - works in tandem with <config['instr_sampling_enabled']> and <config['instr_sampler']> configs;
2830
2938
  config['instr_sampling_percent'] = 15
2831
2939
 
@@ -2848,6 +2956,7 @@ config['instr_sampler'] = RedisQueuedLocks::Instrument::Sampler
2848
2956
  List of instrumentation events
2849
2957
 
2850
2958
  - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
2959
+ - ["redis_queued_locks.lock_obtaining_failed"](#redis_queued_lockslock_obtaining_failed)
2851
2960
  - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
2852
2961
  - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
2853
2962
  - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
@@ -2858,6 +2967,17 @@ List of instrumentation events
2858
2967
  - ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
2859
2968
  - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
2860
2969
  - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
2970
+ - ["redis_queued_locks.extend_lock_ttl"](#redis_queued_locksextend_lock_ttl)
2971
+ - ["redis_queued_locks.clear_dead_requests"](#redis_queued_locksclear_dead_requests)
2972
+ - ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
2973
+
2974
+ What is instrumented (each event has a log line with the same name, see [Logging](#logging)):
2975
+ - the lock acquirement (`#lock`/`#lock!`, `#lock_series`/`#lock_series!`): each meaningful outcome (the lock is obtained,
2976
+ not obtained, held and released);
2977
+ - each operation that changes Redis keys (`#unlock`, `#unlock_read`, `#clear_locks`, `#clear_locks_of`/`#clear_current_locks`,
2978
+ `#extend_lock_ttl`, `#clear_dead_requests`, `#reindex_locks`): one event with the time of the whole operation;
2979
+ - lock information queries (`#lock_info`, `#locks`, `#queues`, `#keys`, ...) are not instrumented;
2980
+ - the swarm (`#probe_hosts`, `#flush_zombies` and the swarm elements) is not instrumented yet;
2861
2981
 
2862
2982
  Detalized event semantics and payload structure:
2863
2983
 
@@ -2875,6 +2995,23 @@ Detalized event semantics and payload structure:
2875
2995
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2876
2996
  - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2877
2997
 
2998
+ ##### `"redis_queued_locks.lock_obtaining_failed"`
2999
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3000
+ - a moment when the lock acquirement is failed (the lock is not obtained);
3001
+ - raised from `#lock`/`#lock!` (with `raise_errors: true` - before the error is raised) and from `#lock_series`/`#lock_series!`
3002
+ (for the lock of the series that is not obtained);
3003
+ - payload:
3004
+ - `:lock_key` - `string` - lock name;
3005
+ - `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
3006
+ - `:ttl` - `integer`/`milliseconds` - requested lock ttl;
3007
+ - `:acq_id` - `string` - lock acquirer identifier;
3008
+ - `:hst_id` - `string` - lock's host identifier;
3009
+ - `:ts` - `numeric`/`epoch` - the time when the lock acquirement is failed;
3010
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
3011
+ - `:result` - `symbol` - the failure result: `:timeout_reached`, `:retry_limit_reached`, `:fail_fast_no_try`,
3012
+ `:fail_fast_after_try`, `:conflict_dead_lock`, `:conflict_lock_upgrade` (see [lock](#lock---obtain-a-lock));
3013
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
3014
+
2878
3015
  ##### `"redis_queued_locks.extendable_reentrant_lock_obtained"`
2879
3016
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2880
3017
  - an event signalizes about the "extendable reentrant lock" obtaining is happened;
@@ -2977,6 +3114,7 @@ Detalized event semantics and payload structure:
2977
3114
  - `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
2978
3115
  - `:lock_key` - `string` - released lock (lock name);
2979
3116
  - `:lock_key_queue` - `string` - released lock queue (the queue of write lock requests);
3117
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#unlock` method as `:instrument` attribute;
2980
3118
 
2981
3119
  ##### `"redis_queued_locks.explicit_read_lock_release"`
2982
3120
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
@@ -2999,6 +3137,7 @@ Detalized event semantics and payload structure:
2999
3137
  - `:at` - `float`/`epoch` - the time when the operation has ended;
3000
3138
  - `:rel_key_cnt` - `integer` - released redis keys count (write locks, readers registries, read lock data,
3001
3139
  write and read lock request queues);
3140
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#clear_locks` method as `:instrument` attribute;
3002
3141
 
3003
3142
  ##### `"redis_queued_locks.release_locks_of"`
3004
3143
  - <sup>\[[back to the list](#instrumentation-events)\]</sup>
@@ -3011,6 +3150,42 @@ Detalized event semantics and payload structure:
3011
3150
  - `:hst_id` - `string` - refused host identifier;
3012
3151
  - `:rel_key_cnt` - `integer` - released locks count (write locks and read locks);
3013
3152
  - `:tch_queue_cnt` - `:integer` - the number of queues (write and read lock request queues) from which the concrete host/acquirer was removed;
3153
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#clear_locks_of`/`#clear_current_locks` method as `:instrument` attribute;
3154
+
3155
+ ##### `"redis_queued_locks.extend_lock_ttl"`
3156
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3157
+ - an event signalizes about the lock TTL extension (successful or not);
3158
+ - raised from `#extend_lock_ttl`;
3159
+ - payload:
3160
+ - `:lock_key` - `string` - lock key (`rql:lock:<lock_name>`);
3161
+ - `:rw_mode` - `symbol` - `:write` (the write lock) or `:read` (the read lock of the acquirer or all read locks);
3162
+ - `:all_read_locks` - `boolean` - all read locks of the lock are extended (`:read` mode);
3163
+ - `:ext_ttl` - `integer`/`milliseconds` - the extension (`milliseconds` argument);
3164
+ - `:extended_locks_count` - `integer` - the number of extended locks (`0` when nothing is extended);
3165
+ - `:ext_time` - `float`/`milliseconds` - time spent on the extension;
3166
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
3167
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#extend_lock_ttl` method as `:instrument` attribute;
3168
+
3169
+ ##### `"redis_queued_locks.clear_dead_requests"`
3170
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3171
+ - an event signalizes about the dropped dead lock requests;
3172
+ - raised from `#clear_dead_requests`;
3173
+ - payload:
3174
+ - `:dead_ttl` - `integer`/`milliseconds` - lock requests older than `dead_ttl` are dropped;
3175
+ - `:prc_queue_cnt` - `integer` - the number of processed lock request queues (write and read);
3176
+ - `:rel_time` - `float`/`milliseconds` - time spent on the operation;
3177
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
3178
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#clear_dead_requests` method as `:instrument` attribute;
3179
+
3180
+ ##### `"redis_queued_locks.reindex_locks"`
3181
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
3182
+ - an event signalizes about the lock keys added to the [Lock Index](#lock-index);
3183
+ - raised from `#reindex_locks`;
3184
+ - payload:
3185
+ - `:idx_key_cnt` - `integer` - the number of indexed keys;
3186
+ - `:idx_time` - `float`/`milliseconds` - time spent on the operation;
3187
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
3188
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#reindex_locks` method as `:instrument` attribute;
3014
3189
 
3015
3190
  ---
3016
3191