redis_queued_locks 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +294 -51
- data/.claude/rules/acquirer.md +16 -11
- data/.claude/rules/arguments.md +2 -2
- data/.claude/rules/changelog.md +27 -0
- data/.claude/rules/git.md +1 -1
- data/.claude/rules/logic.md +14 -15
- data/.claude/rules/swarm.md +3 -4
- data/.claude/rules/tests.md +9 -9
- data/.claude/rules/type-checking.md +14 -16
- data/.claude/rules/visitors.md +11 -9
- data/.claude/skills/rql-code-review/SKILL.md +129 -47
- data/.ruby-version +1 -1
- data/CHANGELOG.md +122 -245
- data/CLAUDE.md +12 -10
- data/README.md +516 -175
- data/Rakefile +11 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +28 -12
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +38 -16
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -15
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +945 -272
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +82 -32
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +15 -13
- data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
- data/lib/redis_queued_locks/acquirer/lock_series/instr_visitor.rb +243 -0
- data/lib/redis_queued_locks/acquirer/lock_series/log_visitor.rb +472 -0
- data/lib/redis_queued_locks/acquirer/lock_series/release_lock_series.rb +181 -0
- data/lib/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rb +1051 -0
- data/lib/redis_queued_locks/acquirer/lock_series.rb +948 -0
- data/lib/redis_queued_locks/acquirer/locks.rb +6 -2
- data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -7
- data/lib/redis_queued_locks/acquirer/queues.rb +17 -6
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +6 -2
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +6 -2
- data/lib/redis_queued_locks/acquirer.rb +1 -1
- data/lib/redis_queued_locks/client.rb +116 -44
- data/lib/redis_queued_locks/config.rb +93 -1
- data/lib/redis_queued_locks/resource.rb +124 -9
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +6 -5
- data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
- data/lib/redis_queued_locks/version.rb +2 -2
- data/rbs_collection.lock.yaml +4 -0
- data/rbs_collection.yaml +2 -1
- data/redis_queued_locks.gemspec +21 -14
- data/sig/manifest.yml +1 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +12 -6
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +21 -14
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +160 -11
- data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +5 -3
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +14 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/lock_series/instr_visitor.rbs +102 -0
- data/sig/redis_queued_locks/acquirer/lock_series/log_visitor.rbs +154 -0
- data/sig/redis_queued_locks/acquirer/lock_series/release_lock_series.rbs +37 -0
- data/sig/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rbs +198 -0
- data/sig/redis_queued_locks/acquirer/lock_series.rbs +106 -0
- data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +77 -7
- data/sig/redis_queued_locks/config.rbs +54 -1
- data/sig/redis_queued_locks/instrument/sampler.rbs +3 -1
- data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
- data/sig/redis_queued_locks/logging/sampler.rbs +3 -1
- data/sig/redis_queued_locks/resource.rbs +22 -12
- data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
- data/sig/redis_queued_locks/utilities.rbs +2 -1
- data/sig/redis_queued_locks.rbs +7 -0
- metadata +35 -13
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -56
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -95
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -484
- /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
|
|
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
|
|
|
@@ -29,8 +29,8 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
29
29
|
- [Usage](#usage)
|
|
30
30
|
- [lock](#lock---obtain-a-lock)
|
|
31
31
|
- [lock!](#lock---exceptional-lock-obtaining)
|
|
32
|
-
- [lock_series](#lock_series---
|
|
33
|
-
- [lock_series!](#lock_series---
|
|
32
|
+
- [lock_series](#lock_series---acquire-a-series-of-locks)
|
|
33
|
+
- [lock_series!](#lock_series---exceptional-lock_series)
|
|
34
34
|
- [lock_info](#lock_info)
|
|
35
35
|
- [queue_info](#queue_info)
|
|
36
36
|
- [locked?](#locked)
|
|
@@ -71,6 +71,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
71
71
|
- [Lock Access Strategies](#lock-access-strategies)
|
|
72
72
|
- [queued](#lock-access-strategies)
|
|
73
73
|
- [random](#lock-access-strategies)
|
|
74
|
+
- [Lock Request Prioritization](#lock-request-prioritization)
|
|
74
75
|
- [Read/Write Locks](#readwrite-locks)
|
|
75
76
|
- [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)
|
|
76
77
|
- [Logging](#logging)
|
|
@@ -84,8 +85,12 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
84
85
|
- ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
|
|
85
86
|
- ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
|
|
86
87
|
- ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
|
|
88
|
+
- ["redis_queued_locks.lock_series_intermediate_lock_obtained"](#redis_queued_lockslock_series_intermediate_lock_obtained)
|
|
89
|
+
- ["redis_queued_locks.lock_series_lock_obtained"](#redis_queued_lockslock_series_lock_obtained)
|
|
90
|
+
- ["redis_queued_locks.lock_series_lock_obtaining_failed"](#redis_queued_lockslock_series_lock_obtaining_failed)
|
|
87
91
|
- ["redis_queued_locks.lock_series_obtained"](#redis_queued_lockslock_series_obtained)
|
|
88
92
|
- ["redis_queued_locks.lock_series_hold_and_release"](#redis_queued_lockslock_series_hold_and_release)
|
|
93
|
+
- ["redis_queued_locks.lock_series_obtaining_failed"](#redis_queued_lockslock_series_obtaining_failed)
|
|
89
94
|
- ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
|
|
90
95
|
- ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
|
|
91
96
|
- ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
|
|
@@ -125,7 +130,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
125
130
|
|
|
126
131
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
127
132
|
|
|
128
|
-
> 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
|
|
133
|
+
> 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.
|
|
129
134
|
|
|
130
135
|
> 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.
|
|
131
136
|
|
|
@@ -205,7 +210,8 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
205
210
|
config['default_lock_ttl'] = 5_000
|
|
206
211
|
|
|
207
212
|
# (seconds) (default: 15)
|
|
208
|
-
# - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the
|
|
213
|
+
# - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the
|
|
214
|
+
# lock requests of its priority);
|
|
209
215
|
# - (the enabled lock index) the lock request queue lives as long as its latest lock request (the queue TTL is refreshed with
|
|
210
216
|
# the queue ttl on each lock request): queues of dead acquirers expire by themselves;
|
|
211
217
|
# - should be a positive Integer
|
|
@@ -249,6 +255,30 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
249
255
|
# retries, timeouts and so on). if lock is free to obtain - it will be obtained;
|
|
250
256
|
config['default_access_strategy'] = :queued
|
|
251
257
|
|
|
258
|
+
# (hash) (default: { lowest: -100, low: -50, normal: 0, high: 50, highest: 100 })
|
|
259
|
+
# - Lock request priority names: a priority name (Symbol) => its priority (Integer from `-100` to `100`);
|
|
260
|
+
# - The names can be used instead of the Integer priorities in the `:priority` attribute and in
|
|
261
|
+
# `config['default_lock_priority']`: `rql.lock('name', priority: :high) { ... }`;
|
|
262
|
+
# - Define the names of your application (keep the default names with `merge`):
|
|
263
|
+
# `config['lock_priorities'] = config['lock_priorities'].merge(payment: 90, email: -10)`;
|
|
264
|
+
# - The default value is frozen (it is shared by all clients): assign a new hash, don't change it in place;
|
|
265
|
+
# - See [Lock Request Prioritization](#lock-request-prioritization) documentation section for details;
|
|
266
|
+
config['lock_priorities'] = { lowest: -100, low: -50, normal: 0, high: 50, highest: 100 }
|
|
267
|
+
|
|
268
|
+
# (integer/symbol) (default: 0)
|
|
269
|
+
# - The default lock request priority: an Integer from `-100` (the lowest) to `100` (the highest)
|
|
270
|
+
# or a priority name of `config['lock_priorities']`;
|
|
271
|
+
# - The priority name should be a key of `config['lock_priorities']` (`RedisQueuedLocks::ConfigValidationError` otherwise):
|
|
272
|
+
# it is checked once after the configuration block (`Client.new { ... }`, `#configure { ... }`: the keys can be assigned
|
|
273
|
+
# in any order; an inconsistent pair is never applied: both keys keep their previous values, also when the block fails)
|
|
274
|
+
# and on a direct `config[...] =` assignment (before the value is applied: change the priority names and the default
|
|
275
|
+
# name together in a configuration block);
|
|
276
|
+
# - Can be customized in methods `#lock`, `#lock!`, `#lock_series` and `#lock_series!` via `:priority` attribute;
|
|
277
|
+
# - `:queued` access strategy: the lock request is placed in the lock request queue after all requests of the same
|
|
278
|
+
# or higher priority and before all requests of the lower priority (FIFO inside the same priority);
|
|
279
|
+
# - See [Lock Request Prioritization](#lock-request-prioritization) documentation section for details;
|
|
280
|
+
config['default_lock_priority'] = 0
|
|
281
|
+
|
|
252
282
|
# (symbol) (default: :wait_for_lock)
|
|
253
283
|
# - Global default conflict strategy mode;
|
|
254
284
|
# - Can be customized in methods `#lock` and `#lock!` via `:conflict_strategy` attribute (see method signatures of #lock and #lock! methods);
|
|
@@ -339,19 +369,27 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
339
369
|
# - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
|
|
340
370
|
# - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
|
|
341
371
|
# - at this moment the only debug logs are realised in following cases:
|
|
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");
|
|
372
|
+
# - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
373
|
+
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
374
|
+
# - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
375
|
+
# - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
376
|
+
# - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
|
|
377
|
+
# - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
378
|
+
# - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
349
379
|
# - "[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
380
|
# - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
351
381
|
# - "[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.
|
|
354
|
-
# - "[redis_queued_locks.
|
|
382
|
+
# - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
383
|
+
# - "[redis_queued_locks.start_try_to_lock_series_cycle]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
384
|
+
# - "[redis_queued_locks.dead_score_reached__reset_series_position]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
385
|
+
# - "[redis_queued_locks.lock_series_intermediate_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
386
|
+
# - "[redis_queued_locks.lock_series_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
387
|
+
# - "[redis_queued_locks.lock_series_lock_obtaining_failed]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
|
|
388
|
+
# - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "reentrant_lock_keys");
|
|
389
|
+
# - "[redis_queued_locks.lock_series_obtaining_failed]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result", "failed_lock_key");
|
|
390
|
+
# - "[redis_queued_locks.dequeue_from_lock_series_queues]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
391
|
+
# - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
392
|
+
# - "[redis_queued_locks.decrease_lock_series]" (logs "lock_keys", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
355
393
|
# - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
|
|
356
394
|
# - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
|
|
357
395
|
# - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
|
|
@@ -376,15 +414,16 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
376
414
|
# - "[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
415
|
# - "[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
416
|
# - "[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");
|
|
417
|
+
# - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
380
418
|
# - "[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.
|
|
419
|
+
# - "[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");
|
|
420
|
+
# - "[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");
|
|
382
421
|
# - "[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");
|
|
422
|
+
# - "[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>");
|
|
423
|
+
# - "[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>");
|
|
424
|
+
# - "[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");
|
|
425
|
+
# - "[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");
|
|
426
|
+
# - "[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");
|
|
388
427
|
# - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
389
428
|
# - "rw_mode" is the requested lock mode ('write'/'read');
|
|
390
429
|
config['log_lock_try'] = false
|
|
@@ -441,8 +480,8 @@ end
|
|
|
441
480
|
|
|
442
481
|
- [lock](#lock---obtain-a-lock)
|
|
443
482
|
- [lock!](#lock---exceptional-lock-obtaining)
|
|
444
|
-
- [lock_series](#lock_series---
|
|
445
|
-
- [lock_series!](#lock_series---
|
|
483
|
+
- [lock_series](#lock_series---acquire-a-series-of-locks)
|
|
484
|
+
- [lock_series!](#lock_series---exceptional-lock_series)
|
|
446
485
|
- [lock_info](#lock_info)
|
|
447
486
|
- [queue_info](#queue_info)
|
|
448
487
|
- [locked?](#locked)
|
|
@@ -501,8 +540,9 @@ def lock(
|
|
|
501
540
|
raise_errors: false,
|
|
502
541
|
fail_fast: false,
|
|
503
542
|
conflict_strategy: config['default_conflict_strategy'],
|
|
504
|
-
|
|
543
|
+
rw_mode: :write,
|
|
505
544
|
access_strategy: config['default_access_strategy'],
|
|
545
|
+
priority: config['default_lock_priority'],
|
|
506
546
|
identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config['uniq_identifier'] proc;
|
|
507
547
|
meta: nil,
|
|
508
548
|
detailed_acq_timeout_error: config['detailed_acq_timeout_error'],
|
|
@@ -563,7 +603,7 @@ def lock(
|
|
|
563
603
|
- `instrumenter` - (optional) `[#notify]`
|
|
564
604
|
- See RedisQueuedLocks::Instrument::ActiveSupport for example;
|
|
565
605
|
- See [Instrumentation](#instrumentation) section of docs;
|
|
566
|
-
- pre-configured in `config['
|
|
606
|
+
- pre-configured in `config['instrumenter']` with void notifier (`RedisQueuedLocks::Instrument::VoidNotifier`);
|
|
567
607
|
- `instrument` - (optional) `[NilClass,Any]`
|
|
568
608
|
- Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
|
569
609
|
- `nil` by default (means "no custom instrumentation data");
|
|
@@ -585,6 +625,19 @@ def lock(
|
|
|
585
625
|
if lock is free to obtain - it will be obtained;
|
|
586
626
|
- pre-configured in `config['default_access_strategy']`;
|
|
587
627
|
- See [Lock Access Strategies](#lock-access-strategies) documentation section for details;
|
|
628
|
+
- `priority` - (optional) - `[Integer,Symbol]`
|
|
629
|
+
- The lock request priority: an Integer from `-100` (the lowest) to `100` (the highest), `0` by default,
|
|
630
|
+
or a priority name of `config['lock_priorities']` (`:lowest`, `:low`, `:normal`, `:high`, `:highest` by default,
|
|
631
|
+
the names of your application can be configured);
|
|
632
|
+
- `:queued` access strategy: the lock request is placed in the lock request queue after all requests of the same or
|
|
633
|
+
higher priority and before all requests of the lower priority (strict priority, FIFO inside the same priority;
|
|
634
|
+
read and write lock requests too);
|
|
635
|
+
- `:random` access strategy: the lock request does not wait for its turn (it ignores the order of the lock request queues
|
|
636
|
+
and the priorities of other requests), but it is queued at the position of its priority like a `:queued` one: `:queued`
|
|
637
|
+
lock requests wait for it;
|
|
638
|
+
- the obtained lock is never taken away: the priority orders the waiting lock requests only;
|
|
639
|
+
- pre-configured in `config['default_lock_priority']`;
|
|
640
|
+
- See [Lock Request Prioritization](#lock-request-prioritization) documentation section for details;
|
|
588
641
|
- `conflict_strategy` - (optional) - `[Symbol]`
|
|
589
642
|
- The conflict strategy mode for cases when the process that obtained the lock
|
|
590
643
|
want to acquire this lock again;
|
|
@@ -596,7 +649,7 @@ def lock(
|
|
|
596
649
|
- `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
|
|
597
650
|
- `:dead_locking` - fail with deadlock exception;
|
|
598
651
|
- See [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) documentation section for details;
|
|
599
|
-
- `
|
|
652
|
+
- `rw_mode` - (optional) - `[Symbol]`
|
|
600
653
|
- The lock mode (read/write lock semantics);
|
|
601
654
|
- `:write` by default;
|
|
602
655
|
- Modes:
|
|
@@ -758,7 +811,7 @@ Return value:
|
|
|
758
811
|
{ ok: false, result: :timeout_reached } # see <timeout> option
|
|
759
812
|
{ ok: false, result: :retry_limit_reached } # see <retry_count> option
|
|
760
813
|
{ 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 <
|
|
814
|
+
{ ok: false, result: :conflict_lock_upgrade } # read-to-write lock upgrade (see <rw_mode> and <conflict_strategy> options)
|
|
762
815
|
{ ok: false, result: :fail_fast_no_try } # see <fail_fast> option
|
|
763
816
|
{ ok: false, result: :fail_fast_after_try } # see <fail_fast> option
|
|
764
817
|
```
|
|
@@ -834,7 +887,7 @@ rql.lock_info("my_lock")
|
|
|
834
887
|
|
|
835
888
|
- (`:queue_ttl`) setting a short limit of time to the lock request queue position (if a process fails to acquire
|
|
836
889
|
the lock within this period of time (and before timeout/retry_count limits occurs of course) -
|
|
837
|
-
its lock request will be moved to the end of queue):
|
|
890
|
+
its lock request will be moved to the end of queue: the end of the lock requests of its priority):
|
|
838
891
|
|
|
839
892
|
```ruby
|
|
840
893
|
rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
|
|
@@ -946,8 +999,9 @@ def lock!(
|
|
|
946
999
|
instrument: nil,
|
|
947
1000
|
instrumenter: config['instrumenter'],
|
|
948
1001
|
access_strategy: config['default_access_strategy'],
|
|
1002
|
+
priority: config['default_lock_priority'],
|
|
949
1003
|
conflict_strategy: config['default_conflict_strategy'],
|
|
950
|
-
|
|
1004
|
+
rw_mode: :write,
|
|
951
1005
|
log_sampling_enabled: config['log_sampling_enabled'],
|
|
952
1006
|
log_sampling_percent: config['log_sampling_percent'],
|
|
953
1007
|
log_sampler: config['log_sampler'],
|
|
@@ -964,38 +1018,56 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
|
|
|
964
1018
|
|
|
965
1019
|
---
|
|
966
1020
|
|
|
967
|
-
#### #lock_series -
|
|
1021
|
+
#### #lock_series - acquire a series of locks
|
|
968
1022
|
|
|
969
1023
|
<sup>\[[back to top](#usage)\]</sup>
|
|
970
1024
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
**
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
-
|
|
992
|
-
|
|
1025
|
+
`lock_series` - obtain a group of locks atomically: all locks of the series are obtained at the same moment with the same TTL
|
|
1026
|
+
and are held together while your block of code is executing (or, if a block is not passed, while the locks live).
|
|
1027
|
+
|
|
1028
|
+
**Guarantees**:
|
|
1029
|
+
- **all or nothing**: one transaction obtains all locks of the series; an attempt that can not obtain some lock obtains nothing,
|
|
1030
|
+
so a failed series leaves no obtained locks (and no lock requests in the lock queues);
|
|
1031
|
+
- **all locks at the same time**: all locks are obtained with the same TTL (`ttl:`) at the same moment, so the block of code
|
|
1032
|
+
never works with a partially held series and `timed: true` limits it by the real lifetime of the locks;
|
|
1033
|
+
- **no hold-and-wait**: nothing is held while the series waits for its locks: the free locks of the series are not locked
|
|
1034
|
+
and their TTL is not spent on the waiting;
|
|
1035
|
+
- **no deadlocks between lock series**: the order of the lock names does not matter (`lock_series("a", "b")` and
|
|
1036
|
+
`lock_series("b", "a")` work in parallel threads): the lock request of the series has one position in all its lock queues,
|
|
1037
|
+
so the lock requests wait only for earlier lock requests. Your own nested logic is out of it: code that holds a lock and
|
|
1038
|
+
then waits for a series still can wait for a lock that is needed by another acquirer (the waiting is limited by the limits
|
|
1039
|
+
of the series);
|
|
1040
|
+
|
|
1041
|
+
**How the options work**:
|
|
1042
|
+
- method options are the same as for the `#lock` method: `ttl`, `queue_ttl`, `retry_count`, `retry_delay`, `retry_jitter`, etc;
|
|
1043
|
+
- limits are the limits of **the whole series**, not of each lock: `timeout:` is one time period for all locks, `retry_count:` is
|
|
1044
|
+
the number of attempts to obtain all locks, `queue_ttl:` is the lifetime of the lock request of the series;
|
|
1045
|
+
- `ttl:` is the TTL of each lock of the series (the same value for all locks);
|
|
1046
|
+
- `rw_mode:` is the same for all locks of the series (see [Read/Write Locks](#readwrite-locks));
|
|
1047
|
+
- `priority:` is the same for all locks of the series (see [Lock Request Prioritization](#lock-request-prioritization)):
|
|
1048
|
+
the lock request of the series has this priority in all its lock queues;
|
|
1049
|
+
- `conflict_strategy:` works per lock: reentrant locks (already obtained by your outer logic, `:work_through`/`:extendable_work_through`)
|
|
1050
|
+
are not obtained and not released by the series: the extended ones (`:extendable_work_through`) get their TTL back after the block,
|
|
1051
|
+
like in the `#lock` method; a lock with a failed conflict (`:dead_locking`, read-to-write lock upgrade) fails the whole series;
|
|
1052
|
+
- `fail_fast: true` fails the series on the first lock that is already obtained;
|
|
1053
|
+
- a duplicated lock name is the same lock (`lock_series("a", "b", "a")` works with two locks); at least one lock name is required
|
|
1054
|
+
(`RedisQueuedLocks::ArgumentError`);
|
|
1055
|
+
|
|
1056
|
+
**Details and limitations**:
|
|
1057
|
+
- when your block of code has completed its execution (with or without an exception) all locks obtained by the series are released;
|
|
1058
|
+
- an interrupted series (`Timeout.timeout` of your logic, `Thread#kill`, a Redis error) releases its locks and drops its lock requests,
|
|
1059
|
+
when it is possible (a dead process or an unavailable Redis: the locks live till their TTL, the lock requests till their `queue_ttl`);
|
|
993
1060
|
- the series releases only locks obtained by itself and only if they are still owned by the current acquirer:
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
-
|
|
997
|
-
|
|
998
|
-
|
|
1061
|
+
a lock that is expired during your block and obtained by another acquirer is kept;
|
|
1062
|
+
- if you did not pass a block - all locks live till their TTL;
|
|
1063
|
+
- locks of the series are regular locks with regular keys: `#lock_info`, `#unlock`, `#extend_lock_ttl`, `#clear_locks` and the swarm
|
|
1064
|
+
work with each of them separately;
|
|
1065
|
+
- `:queued` access strategy: a series that is ahead of other lock requests makes them wait even for its free locks (the waiting
|
|
1066
|
+
series is the next obtainer of these locks): one long-waiting series delays the lock requests of all its locks for its waiting time
|
|
1067
|
+
(no longer than its `queue_ttl`, `timeout` and `retry_count` limits allow);
|
|
1068
|
+
- `:random` access strategy: the series is obtained only when all its locks are free at the same moment, so a big series can
|
|
1069
|
+
wait for a long time under load (lock requests of the `:random` strategy have no reserved turn);
|
|
1070
|
+
- all processes that work with the same locks should use the same RQL version: older versions obtain the series lock by lock;
|
|
999
1071
|
|
|
1000
1072
|
##### How to use:
|
|
1001
1073
|
|
|
@@ -1006,12 +1078,27 @@ applied with the same value to the each lock acquirement process (for each lock
|
|
|
1006
1078
|
client.lock_series("a", "b", "c") { ...some_code... }
|
|
1007
1079
|
|
|
1008
1080
|
# result-oriented way:
|
|
1009
|
-
# - (block is passed)
|
|
1081
|
+
# - (block is not passed)
|
|
1082
|
+
client.lock_series('x', 'y', 'z', ttl: 5_000)
|
|
1083
|
+
# =>
|
|
1084
|
+
{
|
|
1085
|
+
ok: true,
|
|
1086
|
+
result: {
|
|
1087
|
+
lock_series: ["x", "y", "z"], # your locks
|
|
1088
|
+
rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], # internal RQL lock-keys of your locks
|
|
1089
|
+
acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", # the acquirer of the locks
|
|
1090
|
+
hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", # the host of the locks
|
|
1091
|
+
ts: 1769506100.676884, # the time when the locks were obtained, epoch
|
|
1092
|
+
ttl: 5000 # the ttl of each lock, milliseconds
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
# - (detailed result, block is passed)
|
|
1010
1097
|
detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true) { 1 + 2 }
|
|
1011
1098
|
detailed_result # =>
|
|
1012
1099
|
{
|
|
1013
1100
|
yield_result: 3, # result of your block of code
|
|
1014
|
-
|
|
1101
|
+
locks_release_strategy: :immediate_release_after_yield, # (block is passed)
|
|
1015
1102
|
locks_released_at: 1769506100.676884, # (instrumentation) release time, epoch
|
|
1016
1103
|
locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
|
|
1017
1104
|
locks_hold_time: 0.65, # (instrumentation) lock series hold period (hold preiod of all required locks), milliseconds
|
|
@@ -1019,12 +1106,12 @@ detailed_result # =>
|
|
|
1019
1106
|
rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"] # internal RQL lock-keys of your locks
|
|
1020
1107
|
}
|
|
1021
1108
|
|
|
1022
|
-
# - (block is not passed)
|
|
1109
|
+
# - (detailed result, block is not passed)
|
|
1023
1110
|
detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true)
|
|
1024
1111
|
detailed_result # =>
|
|
1025
1112
|
{
|
|
1026
1113
|
yield_result: nil, # block is not given - nothing to return
|
|
1027
|
-
|
|
1114
|
+
locks_release_strategy: :redis_key_ttl, # block is not given - locks are living in redis with their TTL
|
|
1028
1115
|
locks_released_at: nil, # (instrumentation) # block is not given - locks are living in redis with their TTL
|
|
1029
1116
|
locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
|
|
1030
1117
|
locks_hold_time: nil, # (instrumentation) # block is not given - locks are living in redis with their TTL
|
|
@@ -1041,53 +1128,71 @@ detailed_result # =>
|
|
|
1041
1128
|
client.lock('a', ttl: 10_000_000)
|
|
1042
1129
|
|
|
1043
1130
|
# - try to bobtain series of locks with `a` inside the series (`a` can not be obtained for now)
|
|
1044
|
-
client.
|
|
1131
|
+
client.lock_series('c', 'a', 'b') # ... `a` is still obtained
|
|
1045
1132
|
# =>
|
|
1046
1133
|
{
|
|
1047
1134
|
ok: false,
|
|
1048
1135
|
result: {
|
|
1049
1136
|
error: :failed_to_acquire_lock_series,
|
|
1050
|
-
detailed_errors: { 'a' => { ok: false, result: :retry_limit_reached } },
|
|
1137
|
+
detailed_errors: { 'a' => { ok: false, result: :retry_limit_reached } }, # (empty without `failed_on_lock`)
|
|
1051
1138
|
lock_series: ["c", "a", "b"],
|
|
1052
|
-
acquired_locks: [
|
|
1053
|
-
missing_locks: ["a", "b"],
|
|
1054
|
-
failed_on_lock:
|
|
1139
|
+
acquired_locks: [], # always empty: nothing is obtained by the failed series
|
|
1140
|
+
missing_locks: ["c", "a", "b"],
|
|
1141
|
+
failed_on_lock: "a", # the first lock that blocked the last completed attempt (`nil`: unknown)
|
|
1055
1142
|
}
|
|
1056
1143
|
}
|
|
1057
1144
|
```
|
|
1058
1145
|
|
|
1059
|
-
######
|
|
1060
|
-
|
|
1061
|
-
- `rw_mode` is the mode of the series (
|
|
1146
|
+
###### Instrumentation events:
|
|
1147
|
+
|
|
1148
|
+
- `rw_mode` is the mode of the series (the same for all locks of the series);
|
|
1149
|
+
- `#lock` events (`lock_obtained`, `lock_obtaining_failed`, ...) are not raised for the locks of the series, each lock has its own series events
|
|
1150
|
+
with the series context in the name (`lock_key` is the lock, `lock_keys` are all locks of the series):
|
|
1151
|
+
- `lock_series_intermediate_lock_obtained` - intermediate obtaining: the lock passed all checks of an attempt of the series (the lock is
|
|
1152
|
+
free and the lock request of the series is next). Nothing is held yet: the series can still wait for its other locks or fail.
|
|
1153
|
+
Raised once per lock of the series (not per attempt), in the order of the lock names up to the lock that blocks the series;
|
|
1154
|
+
- `lock_series_lock_obtained` - full obtaining: the lock is really obtained, all locks of the series are held;
|
|
1155
|
+
- `lock_series_lock_obtaining_failed` - the lock that blocked the failed series, once per series (not raised when the blocking lock
|
|
1156
|
+
is unknown: no attempt found it, see `failed_lock_key` of `lock_series_obtaining_failed`);
|
|
1157
|
+
- reentrant locks are not obtained by the series: they have no lock events (see `reentrant_lock_keys` of the series events);
|
|
1062
1158
|
- see [Instrumentation Events](#instrumentation-events) for the payload details;
|
|
1063
1159
|
|
|
1064
1160
|
```ruby
|
|
1065
|
-
#
|
|
1066
|
-
"redis_queued_locks.
|
|
1067
|
-
"redis_queued_locks.
|
|
1161
|
+
# instrumentation events (examples)
|
|
1162
|
+
"redis_queued_locks.lock_series_intermediate_lock_obtained" => # {lock_key: "rql:lock:s", lock_keys: ["rql:lock:s", "rql:lock:t"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, instrument: nil}
|
|
1163
|
+
"redis_queued_locks.lock_series_lock_obtained" => # {lock_key: "rql:lock:s", lock_keys: ["rql:lock:s", "rql:lock:t"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, instrument: nil}
|
|
1164
|
+
"redis_queued_locks.lock_series_lock_obtaining_failed" => # {lock_key: "rql:lock:y", lock_keys: ["rql:lock:x", "rql:lock:y"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, result: :retry_limit_reached, instrument: nil}
|
|
1165
|
+
"redis_queued_locks.lock_series_obtained" => # {lock_keys: ["rql:lock:s", "rql:lock:t", "rql:lock:u"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4688692, acq_time: 2.61, reentrant_lock_keys: [], instrument: nil}
|
|
1166
|
+
"redis_queued_locks.lock_series_hold_and_release" => # {lock_keys: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], rw_mode: :write, priority: 0, hold_time: 0.29, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, reentrant_lock_keys: [], instrument: nil}
|
|
1167
|
+
"redis_queued_locks.lock_series_obtaining_failed" => # {lock_keys: ["rql:lock:x", "rql:lock:y"], rw_mode: :write, priority: 0, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, result: :retry_limit_reached, failed_lock_key: "rql:lock:y", instrument: nil}
|
|
1068
1168
|
```
|
|
1069
1169
|
|
|
1070
|
-
######
|
|
1170
|
+
###### Logs:
|
|
1071
1171
|
|
|
1072
|
-
- `rw_mode` is the mode of the series (
|
|
1172
|
+
- `rw_mode` is the mode of the series (the same for all locks of the series);
|
|
1073
1173
|
- see [Logging](#logging) for the logged keys;
|
|
1074
1174
|
|
|
1075
1175
|
```shell
|
|
1076
|
-
#
|
|
1077
|
-
[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'
|
|
1078
|
-
[redis_queued_locks.
|
|
1079
|
-
[redis_queued_locks.
|
|
1176
|
+
# logs (examples)
|
|
1177
|
+
[redis_queued_locks.start_lock_series_obtaining] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
|
|
1178
|
+
[redis_queued_locks.start_try_to_lock_series_cycle] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
|
|
1179
|
+
[redis_queued_locks.lock_series_intermediate_lock_obtained] lock_key => 'rql:lock:a' lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms)
|
|
1180
|
+
[redis_queued_locks.lock_series_lock_obtained] lock_key => 'rql:lock:a' lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms)
|
|
1181
|
+
[redis_queued_locks.lock_series_obtained] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0 acq_time => 2.55 (ms) reentrant_lock_keys => '[]'
|
|
1182
|
+
[redis_queued_locks.expire_lock_series] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' rw_mode => 'write' priority => 0
|
|
1080
1183
|
```
|
|
1081
1184
|
|
|
1082
1185
|
----
|
|
1083
1186
|
|
|
1084
|
-
#### #lock_series! -
|
|
1187
|
+
#### #lock_series! - exceptional `lock_series`
|
|
1085
1188
|
|
|
1086
1189
|
<sup>\[[back to top](#usage)\]</sup>
|
|
1087
1190
|
|
|
1088
|
-
|
|
1191
|
+
Raises the same errors as `#lock!` when the series can not be obtained (`RedisQueuedLocks::LockAlreadyObtainedError`,
|
|
1192
|
+
`RedisQueuedLocks::ConflictLockObtainError`, `RedisQueuedLocks::LockAcquirementRetryLimitError`,
|
|
1193
|
+
`RedisQueuedLocks::LockAcquirementTimeoutError`).
|
|
1089
1194
|
|
|
1090
|
-
For more details see `lock_series` [docs](#lock_series---
|
|
1195
|
+
For more details see `lock_series` [docs](#lock_series---acquire-a-series-of-locks)
|
|
1091
1196
|
|
|
1092
1197
|
```ruby
|
|
1093
1198
|
client.lock_series!("x", "y", "z") { ...some_code... }
|
|
@@ -1186,18 +1291,18 @@ rql.lock_info("your_lock_name")
|
|
|
1186
1291
|
|
|
1187
1292
|
```ruby
|
|
1188
1293
|
# <for read/write locks>
|
|
1189
|
-
# (see `
|
|
1294
|
+
# (see `rw_mode:` kwarg attribute of #lock/#lock! methods and "Read/Write Locks" documentation section)
|
|
1190
1295
|
# (lock requests are not included: use #queue_info to see the queued readers and writers)
|
|
1191
1296
|
|
|
1192
1297
|
# -> case 1: the lock is held by readers, writers and readers are waiting in the lock queues;
|
|
1193
1298
|
|
|
1194
1299
|
# process#1 and process#2 (readers, work in parallel):
|
|
1195
|
-
rql.lock("your_lock_name",
|
|
1196
|
-
rql.lock("your_lock_name",
|
|
1300
|
+
rql.lock("your_lock_name", rw_mode: :read, ttl: 5_000, meta: { "kek" => "pek" }) { ... }
|
|
1301
|
+
rql.lock("your_lock_name", rw_mode: :read, ttl: 3_000) { ... }
|
|
1197
1302
|
# process#3 (writer, waits for all readers in the write lock queue):
|
|
1198
|
-
rql.lock("your_lock_name",
|
|
1303
|
+
rql.lock("your_lock_name", rw_mode: :write) { ... }
|
|
1199
1304
|
# process#4 (reader, waits for the earlier writer in the read lock queue):
|
|
1200
|
-
rql.lock("your_lock_name",
|
|
1305
|
+
rql.lock("your_lock_name", rw_mode: :read) { ... }
|
|
1201
1306
|
|
|
1202
1307
|
rql.lock_info("your_lock_name")
|
|
1203
1308
|
|
|
@@ -1256,15 +1361,18 @@ lock acquirers and their position in queue. The queue of write lock requests is
|
|
|
1256
1361
|
- get the lock queue information;
|
|
1257
1362
|
- queue represents the ordered set of lock key reqests:
|
|
1258
1363
|
- set is ordered by score in ASC manner (inside the Redis Set);
|
|
1259
|
-
- score is represented as a timestamp when the lock request was made
|
|
1364
|
+
- score is represented as a timestamp when the lock request was made shifted by the
|
|
1365
|
+
[priority](#lock-request-prioritization) band (`-priority * 2**32` seconds: the default priority `0` keeps the timestamp);
|
|
1260
1366
|
- represents the acquirer identifier and their score as an array of hashes;
|
|
1261
1367
|
- returns `nil` if lock queues (write and read) do not exist;
|
|
1262
1368
|
- lock queue data (`Hash<String,String|Array<Hash<String|Numeric>>`):
|
|
1263
1369
|
- `"lock_queue"` - `string` - lock queue key in redis (write lock requests);
|
|
1264
1370
|
- `"queue"` - `array` - an array of lock requests (array of hashes):
|
|
1265
1371
|
- `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
|
|
1266
|
-
- `"score"` - `float`/`epoch` - time when the lock request was made (epoch)
|
|
1372
|
+
- `"score"` - `float`/`epoch` - the position of the lock request: time when the lock request was made (epoch) shifted by
|
|
1373
|
+
`-priority * 2**32` seconds (the time itself for the default priority `0`);
|
|
1267
1374
|
- `"rw_mode"` - `string` - the requested lock mode (`"write"`);
|
|
1375
|
+
- `"priority"` - `integer` - the lock request priority (from `-100` to `100`, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
1268
1376
|
- `"read_lock_queue"`, `"read_queue"` - (only when read lock requests exist) the read lock queue key and read lock requests
|
|
1269
1377
|
with the same structure (`"rw_mode"` - `"read"`), see [Read/Write Locks](#readwrite-locks);
|
|
1270
1378
|
|
|
@@ -1275,15 +1383,16 @@ rql.queue_info("your_lock_name")
|
|
|
1275
1383
|
{
|
|
1276
1384
|
"lock_queue" => "rql:lock_queue:your_lock_name",
|
|
1277
1385
|
"queue" => [
|
|
1278
|
-
|
|
1279
|
-
{ "acq_id" => "rql:acq:123/567/
|
|
1280
|
-
{ "acq_id" => "rql:acq:
|
|
1386
|
+
# (a lock request of the high priority: ahead of the requests of the default priority)
|
|
1387
|
+
{ "acq_id" => "rql:acq:123/456/567/678/fa76df9cc2", "score" => -2583360655.459158, "rw_mode" => "write", "priority" => 1 },
|
|
1388
|
+
{ "acq_id" => "rql:acq:123/567/456/679/c7bfcaf4f9", "score" => 1711606640.540906, "rw_mode" => "write", "priority" => 0 },
|
|
1389
|
+
{ "acq_id" => "rql:acq:555/329/523/127/7329553b11", "score" => 1711606640.540963, "rw_mode" => "write", "priority" => 0 },
|
|
1281
1390
|
# ...etc
|
|
1282
1391
|
],
|
|
1283
1392
|
# (when read lock requests exist)
|
|
1284
1393
|
"read_lock_queue" => "rql:lock_read_queue:your_lock_name",
|
|
1285
1394
|
"read_queue" => [
|
|
1286
|
-
{ "acq_id" => "rql:acq:123/456/567/700/fa76df9cc2", "score" => 1711606640.541011, "rw_mode" => "read" },
|
|
1395
|
+
{ "acq_id" => "rql:acq:123/456/567/700/fa76df9cc2", "score" => 1711606640.541011, "rw_mode" => "read", "priority" => 0 },
|
|
1287
1396
|
# ...etc
|
|
1288
1397
|
]
|
|
1289
1398
|
}
|
|
@@ -1398,7 +1507,7 @@ rql.unlock("your_lock_name")
|
|
|
1398
1507
|
|
|
1399
1508
|
- releases the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
|
|
1400
1509
|
other read locks, the write lock and lock requests are not affected (`#unlock` releases the lock entirely);
|
|
1401
|
-
- suitable for read locks obtained without a block of code (`lock(...,
|
|
1510
|
+
- suitable for read locks obtained without a block of code (`lock(..., rw_mode: :read)` without a block);
|
|
1402
1511
|
- the current acquirer is calculated in the same way as for `#lock` (`#current_acquirer_id`: process, thread, fiber, ractor, identity),
|
|
1403
1512
|
so call it from the same thread/fiber that obtained the read lock;
|
|
1404
1513
|
- the read lock of a different acquirer can be released via `:acquirer_id` (for example, extracted from `#lock_info`/`#locks_info`);
|
|
@@ -1416,7 +1525,7 @@ rql.unlock("your_lock_name")
|
|
|
1416
1525
|
- raises the `"redis_queued_locks.explicit_read_lock_release"` instrumentation event;
|
|
1417
1526
|
|
|
1418
1527
|
```ruby
|
|
1419
|
-
rql.lock('report',
|
|
1528
|
+
rql.lock('report', rw_mode: :read, ttl: 30_000) # => obtained without a block
|
|
1420
1529
|
# ...
|
|
1421
1530
|
rql.unlock_read('report')
|
|
1422
1531
|
# =>
|
|
@@ -1471,7 +1580,7 @@ rql.unlock_read('report', acquirer_id: reader['acq_id'])
|
|
|
1471
1580
|
- pre-configured value in `config['logger']`;
|
|
1472
1581
|
- `:instrumenter` - (optional) `[#notify]`
|
|
1473
1582
|
- custom instrumenter object;
|
|
1474
|
-
- pre-configured value in `config['
|
|
1583
|
+
- pre-configured value in `config['instrumenter']`;
|
|
1475
1584
|
- `:instrument` - (optional) `[NilClass,Any]`
|
|
1476
1585
|
- custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
|
|
1477
1586
|
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
|
@@ -1554,7 +1663,7 @@ rql.clear_locks
|
|
|
1554
1663
|
- pre-configured value in `config['logger']`;
|
|
1555
1664
|
- `:instrumenter` - (optional) `[#notify]`
|
|
1556
1665
|
- custom instrumenter object;
|
|
1557
|
-
- pre-configured value in `config['
|
|
1666
|
+
- pre-configured value in `config['instrumenter']`;
|
|
1558
1667
|
- `:instrument` - (optional) `[NilClass,Any]`
|
|
1559
1668
|
- custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
|
|
1560
1669
|
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
|
@@ -1662,7 +1771,7 @@ rql.clear_current_locks
|
|
|
1662
1771
|
- pre-configured value in `config['logger']`;
|
|
1663
1772
|
- `:instrumenter` - (optional) `[#notify]`
|
|
1664
1773
|
- custom instrumenter object;
|
|
1665
|
-
- pre-configured value in `config['
|
|
1774
|
+
- pre-configured value in `config['instrumenter']`;
|
|
1666
1775
|
- `:instrument` - (optional) `[NilClass,Any]`
|
|
1667
1776
|
- custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
|
|
1668
1777
|
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
|
@@ -1732,7 +1841,7 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
|
|
|
1732
1841
|
- `milliseconds` - (required) `[Integer]`
|
|
1733
1842
|
- how many milliseconds should be added to the lock's TTL;
|
|
1734
1843
|
- should be a positive `Integer`;
|
|
1735
|
-
- `:
|
|
1844
|
+
- `:rw_mode` - (optional) `[Symbol]`
|
|
1736
1845
|
- `:write` (default) - extends the write lock (the behavior described below);
|
|
1737
1846
|
- `:read` - extends the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
|
|
1738
1847
|
- the read lock expiration, the read locks registry TTL and the read lock data TTL are extended;
|
|
@@ -1972,8 +2081,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
|
|
|
1972
2081
|
- returns `Set<Hash<Symbol,Any>>` (see [#queue_info](#queue_info) and examples below for details).
|
|
1973
2082
|
- contained data: `{ queue: String, requests: Array<Hash<String,Any>> }`
|
|
1974
2083
|
- `:queue` - `String` - lock key queue in Redis;
|
|
1975
|
-
- `:requests` - `Array<Hash<String,Any>>` - lock requests in the que with their acquirer id, score
|
|
1976
|
-
(`"rw_mode"` - `"write"` for `rql:lock_queue:*`, `"read"` for read lock queues `rql:lock_read_queue:*`).
|
|
2084
|
+
- `:requests` - `Array<Hash<String,Any>>` - lock requests in the que with their acquirer id, score, requested lock mode
|
|
2085
|
+
(`"rw_mode"` - `"write"` for `rql:lock_queue:*`, `"read"` for read lock queues `rql:lock_read_queue:*`) and priority.
|
|
1977
2086
|
|
|
1978
2087
|
```ruby
|
|
1979
2088
|
rql.queues_info # or rql.qeuues_info(scan_size: 123)
|
|
@@ -1981,13 +2090,13 @@ rql.queues_info # or rql.qeuues_info(scan_size: 123)
|
|
|
1981
2090
|
=> #<Set:
|
|
1982
2091
|
{{:queue=>"rql:lock_queue:some-lock-123",
|
|
1983
2092
|
:requests=>
|
|
1984
|
-
[{"acq_id"=>"rql:acq:38529/4500/4520/4360/66093702f24a3129", "score"=>1711606640.540842, "rw_mode"=>"write"},
|
|
1985
|
-
{"acq_id"=>"rql:acq:38529/4580/4600/4360/66093702f24a3129", "score"=>1711606640.540906, "rw_mode"=>"write"},
|
|
1986
|
-
{"acq_id"=>"rql:acq:38529/4620/4640/4360/66093702f24a3129", "score"=>1711606640.5409632, "rw_mode"=>"write"}]},
|
|
2093
|
+
[{"acq_id"=>"rql:acq:38529/4500/4520/4360/66093702f24a3129", "score"=>1711606640.540842, "rw_mode"=>"write", "priority"=>0},
|
|
2094
|
+
{"acq_id"=>"rql:acq:38529/4580/4600/4360/66093702f24a3129", "score"=>1711606640.540906, "rw_mode"=>"write", "priority"=>0},
|
|
2095
|
+
{"acq_id"=>"rql:acq:38529/4620/4640/4360/66093702f24a3129", "score"=>1711606640.5409632, "rw_mode"=>"write", "priority"=>0}]},
|
|
1987
2096
|
{:queue=>"rql:lock_read_queue:some-lock-456",
|
|
1988
2097
|
:requests=>
|
|
1989
|
-
[{"acq_id"=>"rql:acq:38529/4380/4400/4360/66093702f24a3129", "score"=>1711606640.540722, "rw_mode"=>"read"},
|
|
1990
|
-
{"acq_id"=>"rql:acq:38529/4420/4440/4360/66093702f24a3129", "score"=>1711606640.5407748, "rw_mode"=>"read"}]},
|
|
2098
|
+
[{"acq_id"=>"rql:acq:38529/4380/4400/4360/66093702f24a3129", "score"=>1711606640.540722, "rw_mode"=>"read", "priority"=>0},
|
|
2099
|
+
{"acq_id"=>"rql:acq:38529/4420/4440/4360/66093702f24a3129", "score"=>1711606640.5407748, "rw_mode"=>"read", "priority"=>0}]},
|
|
1991
2100
|
...}>
|
|
1992
2101
|
```
|
|
1993
2102
|
---
|
|
@@ -1999,8 +2108,10 @@ rql.queues_info # or rql.qeuues_info(scan_size: 123)
|
|
|
1999
2108
|
In some cases your lock requests may become "dead". It means that your lock request lives in lock queue in Redis without
|
|
2000
2109
|
any processing. It can happen when your processs that are enqueeud to the lock queue is failed unexpectedly (for some reason)
|
|
2001
2110
|
before the lock acquire moment occurs and when no any other process does not need this lock anymore.
|
|
2002
|
-
For this case your lock
|
|
2003
|
-
|
|
2111
|
+
For this case your lock request will be cleared only when another lock request meets it: a lock request of the same
|
|
2112
|
+
[priority](#lock-request-prioritization) (lock acquirement drops the expired requests of its priority) or a `:queued` lock request
|
|
2113
|
+
it blocks (the head of the queue, an earlier request of the other read/write mode); the dead requests of other priorities stay
|
|
2114
|
+
in the queue.
|
|
2004
2115
|
|
|
2005
2116
|
NOTE: with the enabled [Lock Index](#lock-index) the lock request queue lives as long as its latest lock request (the queue TTL
|
|
2006
2117
|
is refreshed with the request `queue_ttl` on each lock request), so the queue of dead requests expires by itself (queues of older
|
|
@@ -2009,7 +2120,12 @@ RQL versions and queues created with the disabled lock index have no TTL: see [#
|
|
|
2009
2120
|
In order to help with these dead requests you may periodically call `#clear_dead_requests`
|
|
2010
2121
|
with corresponding `:dead_ttl` option, that is pre-configured by default via `config['dead_request_ttl']`.
|
|
2011
2122
|
Lock queues are found via the redis `SCAN` (one pass) or via the [Lock Index](#lock-index) (one `ZSCAN` pass, the keyspace is
|
|
2012
|
-
never scanned: the enabled lock index).
|
|
2123
|
+
never scanned: the enabled lock index). Each batch of lock queues takes two round trips: the lock requests are read, then the dead
|
|
2124
|
+
requests of each [priority](#lock-request-prioritization) found in the queues are dropped (no second round trip without dead requests).
|
|
2125
|
+
With `by_priority: true` the lock requests are not read: only the first lock request of each priority is read (the dead requests
|
|
2126
|
+
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
|
|
2127
|
+
priorities + 1 (the dead requests of a priority are dropped in the round trip that reads the next priority), so the transfer does not
|
|
2128
|
+
depend on the length of the lock queues: use it for long lock queues with few priorities.
|
|
2013
2129
|
|
|
2014
2130
|
`:dead_ttl` option is required because of it is no any **fast** and **resource-free** way to understand which request
|
|
2015
2131
|
is dead now and is it really dead cuz each request queue can host their requests with
|
|
@@ -2022,14 +2138,20 @@ Accepts:
|
|
|
2022
2138
|
- `:scan_size` - (optional) `[Integer]`
|
|
2023
2139
|
- the batch of scanned keys for Redis'es SCAN command;
|
|
2024
2140
|
- (the enabled lock index) the `ZSCAN` count of the [Lock Index](#lock-index) pass and the number of locks (both lock queues
|
|
2025
|
-
of each lock) processed in one
|
|
2141
|
+
of each lock) processed in one batch;
|
|
2026
2142
|
- has a preconfigured valie in `config['lock_release_batch_size']`;
|
|
2143
|
+
- `:by_priority` - (optional) `[Boolean]`
|
|
2144
|
+
- read only the first lock request of each [priority](#lock-request-prioritization) in the lock queues instead of all lock
|
|
2145
|
+
requests: a batch of lock queues takes one round trip per priority of its lock queue with the most priorities + 1 (instead
|
|
2146
|
+
of two round trips), the transfer does not depend on the length of the lock queues (for long lock queues with few
|
|
2147
|
+
priorities);
|
|
2148
|
+
- `false` by default (all lock requests are read);
|
|
2027
2149
|
- `:logger` - (optional) `[::Logger,#debug]`
|
|
2028
2150
|
- custom logger object;
|
|
2029
2151
|
- pre-configured in `config['logger']`;
|
|
2030
2152
|
- `:instrumenter` - (optional) `[#notify]`
|
|
2031
2153
|
- custom instrumenter object;
|
|
2032
|
-
- pre-configured in `config['
|
|
2154
|
+
- pre-configured in `config['instrumenter']`;
|
|
2033
2155
|
- `:instrument` - (optional) `[NilClass,Any]`
|
|
2034
2156
|
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
|
2035
2157
|
- `nil` by default (no additional data);
|
|
@@ -2079,6 +2201,9 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
|
|
|
2079
2201
|
}>
|
|
2080
2202
|
}
|
|
2081
2203
|
}
|
|
2204
|
+
|
|
2205
|
+
# long lock queues with few priorities: only the first lock request of each priority is read
|
|
2206
|
+
rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000, by_priority: true)
|
|
2082
2207
|
```
|
|
2083
2208
|
|
|
2084
2209
|
---
|
|
@@ -2598,37 +2723,142 @@ rql.possible_host_ids
|
|
|
2598
2723
|
- can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);
|
|
2599
2724
|
- supports different strategies:
|
|
2600
2725
|
- `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;
|
|
2726
|
+
the queue is ordered by the [lock request priorities](#lock-request-prioritization) (FIFO inside the same priority);
|
|
2601
2727
|
- `: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;
|
|
2728
|
+
(the priorities of other lock requests are ignored too; the lock request itself is queued at the position of its
|
|
2729
|
+
[priority](#lock-request-prioritization) like a `:queued` one: `:queued` lock requests wait for it);
|
|
2602
2730
|
- for current implementation detalis check:
|
|
2603
2731
|
- [Configuration](#configuration) documentation: see `config['default_access_strategy']` config docs;
|
|
2604
2732
|
- [#lock](#lock---obtain-a-lock) method documentation: see `access_strategy` attribute docs;
|
|
2605
2733
|
|
|
2606
2734
|
---
|
|
2607
2735
|
|
|
2736
|
+
## Lock Request Prioritization
|
|
2737
|
+
|
|
2738
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
2739
|
+
|
|
2740
|
+
- `priority:` attribute of `#lock`, `#lock!`, `#lock_series` and `#lock_series!` (`config['default_lock_priority']` by default):
|
|
2741
|
+
an Integer from `-100` (the lowest priority) to `100` (the highest priority), `0` - (**default**) normal priority,
|
|
2742
|
+
or a priority name (Symbol) of `config['lock_priorities']`;
|
|
2743
|
+
- priority names (`config['lock_priorities']`, a priority name => its Integer priority):
|
|
2744
|
+
- default names: `:lowest` (`-100`), `:low` (`-50`), `:normal` (`0`), `:high` (`50`), `:highest` (`100`);
|
|
2745
|
+
- name the priorities of your application (keep the default names with `merge`; the default value is frozen: assign
|
|
2746
|
+
a new hash);
|
|
2747
|
+
- a name is resolved to its Integer priority on each lock acquirement: the lock request queues, `#queue_info`/`#queues_info`,
|
|
2748
|
+
logs and instrumentation have the Integer priority; an unknown name fails with `RedisQueuedLocks::ArgumentError`;
|
|
2749
|
+
- `config['default_lock_priority']` can be a priority name too: an unknown one fails the configuration with
|
|
2750
|
+
`RedisQueuedLocks::ConfigValidationError` (checked after the configuration block, the keys can be assigned in any order,
|
|
2751
|
+
and on a direct `config[...] =` assignment);
|
|
2752
|
+
- `:queued` access strategy (default): the lock request is placed in the lock request queue after all requests of the same
|
|
2753
|
+
or higher priority and before all requests of the lower priority: strict priority between priorities, FIFO inside the same
|
|
2754
|
+
priority (read and write lock requests too, see [Read/Write Locks](#readwrite-locks));
|
|
2755
|
+
|
|
2756
|
+
```ruby
|
|
2757
|
+
# a balance operation goes ahead of the email notifications that are already waiting for the lock
|
|
2758
|
+
rql.lock("user_#{user.id}", priority: 50) { process_balance }
|
|
2759
|
+
|
|
2760
|
+
# a payment goes ahead of the balance operations
|
|
2761
|
+
rql.lock("user_#{user.id}", priority: 90) { process_payment }
|
|
2762
|
+
|
|
2763
|
+
# email notifications wait for all lock requests of higher priorities
|
|
2764
|
+
rql.lock("user_#{user.id}", priority: -10) { send_email }
|
|
2765
|
+
|
|
2766
|
+
# normal priority by default
|
|
2767
|
+
rql.lock("user_#{user.id}") { update_profile } # priority: 0
|
|
2768
|
+
|
|
2769
|
+
# the default priority of the client (for example, in background workers)
|
|
2770
|
+
rql.configure { |config| config['default_lock_priority'] = -10 }
|
|
2771
|
+
|
|
2772
|
+
# priority names: the default ones (:lowest, :low, :normal, :high, :highest) and the names of your application
|
|
2773
|
+
rql.configure do |config|
|
|
2774
|
+
config['lock_priorities'] = config['lock_priorities'].merge(payment: 90, balance: 50, email: -10)
|
|
2775
|
+
end
|
|
2776
|
+
rql.lock("user_#{user.id}", priority: :payment) { process_payment } # priority: 90
|
|
2777
|
+
rql.lock("user_#{user.id}", priority: :email) { send_email } # priority: -10
|
|
2778
|
+
rql.lock("user_#{user.id}", priority: :high) { update_profile } # priority: 50
|
|
2779
|
+
```
|
|
2780
|
+
|
|
2781
|
+
- how it works:
|
|
2782
|
+
- the position of the lock request in the queue (the score) is the request time shifted by the priority band:
|
|
2783
|
+
`request time - priority * 2**32 seconds` (each priority has its own range of positions; the default priority `0` keeps
|
|
2784
|
+
the request time: the positions of RQL versions without priorities);
|
|
2785
|
+
- every lock request drops dead requests (older than `queue_ttl`) of its own priority in both lock queues (in the same round
|
|
2786
|
+
trip as before priorities); dead requests of other priorities that block it (the head of the queue and the earlier request
|
|
2787
|
+
of the other read/write mode: the priority and the request time are decoded from the position) are dropped with all dead
|
|
2788
|
+
requests of their priorities in one more round trip of the same attempt, so a dead request of any priority never blocks the
|
|
2789
|
+
queue and the cost of an attempt does not depend on the number of priorities in use;
|
|
2790
|
+
- dead requests of lower priorities never block: they are dropped by the lock requests of their priority (or lower ones)
|
|
2791
|
+
and by `#clear_dead_requests` (it drops the dead requests of every priority found in the queues);
|
|
2792
|
+
- lock requests at infinite positions (`-inf`/`+inf`: manual writes of the lock queues, RQL never writes them) have the
|
|
2793
|
+
bound priorities (`100`/`-100`) in `#queue_info`/`#queues_info` and logs; a request at `-inf` is a dead one (like before
|
|
2794
|
+
priorities): it is dropped when it blocks a lock request and by `#clear_dead_requests`; a request at `+inf` is behind all
|
|
2795
|
+
requests and is never dropped as a dead one;
|
|
2796
|
+
- `#queue_info`/`#queues_info` return the priority of each lock request (`"priority"`); the lock acquirement logs and
|
|
2797
|
+
instrumentation events (classic, reentrant and lock series ones) have the `priority` of the lock request, the `log_lock_try`
|
|
2798
|
+
logs also have the priorities of the requests ahead (`first_priority_in_queue`, `ahead_priority`, `dead_priority`;
|
|
2799
|
+
`first_priority_in_queue => nil` when the lock queue has no requests);
|
|
2800
|
+
- limitations and recommendations:
|
|
2801
|
+
- the priority orders the waiting lock requests only: the obtained lock is never taken away (no preemption);
|
|
2802
|
+
- strict priority: a continuous flow of requests of a higher priority can starve the requests of lower priorities (they
|
|
2803
|
+
reach their `timeout`/`retry_count` limits): give them longer limits;
|
|
2804
|
+
- the released lock is passed by polling: the next lock request notices the released lock on its next attempt
|
|
2805
|
+
(`retry_delay`): use a shorter `retry_delay` for the important logic;
|
|
2806
|
+
- the default `retry_count` (`3`) gives a short waiting time: the priority matters for the requests that wait longer
|
|
2807
|
+
(`retry_count: nil` with `timeout`);
|
|
2808
|
+
- requests of the same priority made within ~61 µs of each other are ordered by their acquirer ids (the precision of
|
|
2809
|
+
the shifted positions);
|
|
2810
|
+
- each lock request drops dead requests with its own `queue_ttl` (the requests of its priority and the requests that block it):
|
|
2811
|
+
a request that waits longer than the shorter `queue_ttl` of such requests is dropped by their attempts and comes back at its
|
|
2812
|
+
old position on its own attempts (its own `queue_ttl` is not reached yet), so the requests behind it with a shorter
|
|
2813
|
+
`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
|
|
2814
|
+
whose attempt finds the lock free first; use the same `queue_ttl` for the lock requests of a lock (a longer `queue_ttl` does
|
|
2815
|
+
not keep a request in the queue);
|
|
2816
|
+
- `:random` access strategy: the lock request does not wait for its turn (the order of the queues and the priorities of other
|
|
2817
|
+
requests are ignored), but it is queued at the position of its priority: `:queued` lock requests wait for it (a `:random`
|
|
2818
|
+
request of a high priority holds back the waiting `:queued` requests of lower priorities until it obtains the lock or
|
|
2819
|
+
leaves the queue);
|
|
2820
|
+
- dead lock requests are dropped by the lock requests that meet them (the requests of their priority and the requests they
|
|
2821
|
+
block): the dead requests of the priorities that no lock request uses any more (and the dead requests met only by `:random`
|
|
2822
|
+
lock requests) stay in the queues; they never block (an accepted trade-off of the head-driven cleanup: the cost of an
|
|
2823
|
+
attempt does not depend on the number of priorities); without the [Lock Index](#lock-index) (its queues expire) and the
|
|
2824
|
+
[swarm](#swarm-mode-and-zombie-locks) (it drops the requests of dead processes) call `#clear_dead_requests` periodically
|
|
2825
|
+
(e.g. from a scheduled job) to drop them (`by_priority: true` for long lock queues: only the first lock request of each
|
|
2826
|
+
priority is read);
|
|
2827
|
+
- all processes that share locks should use RQL versions with priorities before positive priorities are used (`:high`,
|
|
2828
|
+
`:highest`, any priority above `0`): older versions drop such lock requests as dead ones (the order is broken; the mutual
|
|
2829
|
+
exclusion is not affected: lock queues never take part in it); the default priority (`0`) and negative priorities
|
|
2830
|
+
(`:low`, `:lowest`) work with older versions too: older versions keep such requests behind their own ones and never drop
|
|
2831
|
+
them as dead ones (the dead ones are dropped by the upgraded processes);
|
|
2832
|
+
|
|
2833
|
+
---
|
|
2834
|
+
|
|
2608
2835
|
## Read/Write Locks
|
|
2609
2836
|
|
|
2610
2837
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
2611
2838
|
|
|
2612
|
-
- `
|
|
2839
|
+
- `rw_mode:` attribute of `#lock` and `#lock!`:
|
|
2613
2840
|
- `:write` (**default**) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
|
|
2614
2841
|
- `:read` - shared lock: waits for the write lock only (read locks of different acquirers work in parallel);
|
|
2615
2842
|
|
|
2616
2843
|
```ruby
|
|
2617
2844
|
# readers work in parallel
|
|
2618
|
-
rql.lock('report',
|
|
2845
|
+
rql.lock('report', rw_mode: :read) { read_report }
|
|
2619
2846
|
|
|
2620
2847
|
# writer waits for all readers (and readers wait for the writer)
|
|
2621
|
-
rql.lock('report',
|
|
2848
|
+
rql.lock('report', rw_mode: :write) { rebuild_report }
|
|
2622
2849
|
|
|
2623
2850
|
# write mode is default:
|
|
2624
|
-
rql.lock('report') { rebuild_report } #
|
|
2851
|
+
rql.lock('report') { rebuild_report } # rw_mode: :write by default
|
|
2625
2852
|
```
|
|
2626
2853
|
|
|
2627
2854
|
- lock request ordering (`access_strategy`):
|
|
2628
2855
|
- `:queued` (default) - FIFO between modes: read lock request waits for every earlier write lock request,
|
|
2629
2856
|
write lock request waits for every earlier read and write lock request (so writers are not starved by a
|
|
2630
|
-
continuous flow of readers and consecutive readers obtain the lock together);
|
|
2631
|
-
|
|
2857
|
+
continuous flow of readers and consecutive readers obtain the lock together); with
|
|
2858
|
+
[lock request priorities](#lock-request-prioritization) "earlier" means "ahead in the queue": a request of a higher
|
|
2859
|
+
priority is ahead of all requests of lower priorities in both modes (FIFO inside the same priority);
|
|
2860
|
+
- `:random` - the order of the lock queues is ignored: any free lock is obtained (a continuous flow of readers can starve
|
|
2861
|
+
writers); the lock request itself is queued like any lock request (`:queued` lock requests wait for it);
|
|
2632
2862
|
- read locks:
|
|
2633
2863
|
- each read lock has its own `ttl` and is released (after the block execution) or expired independently of other read locks;
|
|
2634
2864
|
- `ttl` should be a positive `Integer`;
|
|
@@ -2639,11 +2869,11 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
|
|
|
2639
2869
|
- `#unlock` releases the lock entirely: the write lock, all read locks (with their data) and all lock requests;
|
|
2640
2870
|
- `#unlock_read` (aka `#release_read_lock`) releases the read lock of the current acquirer
|
|
2641
2871
|
(suitable for read locks obtained without a block), or the read lock of the acquirer passed via `acquirer_id:`;
|
|
2642
|
-
- `#extend_lock_ttl(lock_name, milliseconds,
|
|
2872
|
+
- `#extend_lock_ttl(lock_name, milliseconds, rw_mode: :read)` extends the read lock of the current acquirer only;
|
|
2643
2873
|
`all_read_locks: true` extends all live read locks of the lock;
|
|
2644
|
-
- `#lock_series` with `
|
|
2874
|
+
- `#lock_series` with `rw_mode: :read` obtains a series of read locks (the mode is the same for all locks of the series):
|
|
2645
2875
|
- read lock series of different acquirers work in parallel, writers wait for them;
|
|
2646
|
-
- after the block
|
|
2876
|
+
- after the block only the read locks of the current acquirer are released;
|
|
2647
2877
|
- reentrant read locks of the series (already obtained by the outer logic of the same acquirer) are not released by the series;
|
|
2648
2878
|
- reentrant locks (the same acquirer, see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)):
|
|
2649
2879
|
- read lock request under the obtained read lock or write lock is processed as a reentrant lock of the obtained lock
|
|
@@ -2727,13 +2957,13 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
|
|
|
2727
2957
|
- default logs (raised from `#lock`/`#lock!`):
|
|
2728
2958
|
|
|
2729
2959
|
```ruby
|
|
2730
|
-
"[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2731
|
-
"[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2732
|
-
"[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
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");
|
|
2735
|
-
"[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2736
|
-
"[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2960
|
+
"[redis_queued_locks.start_lock_obtaining]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2961
|
+
"[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2962
|
+
"[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2963
|
+
"[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
2964
|
+
"[redis_queued_locks.lock_obtaining_failed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
|
|
2965
|
+
"[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
2966
|
+
"[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
2737
2967
|
"[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2738
2968
|
"[redis_queued_locks.expire_lock]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2739
2969
|
"[redis_queued_locks.decrease_lock]" # (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
@@ -2750,24 +2980,34 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
|
|
|
2750
2980
|
"[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");
|
|
2751
2981
|
"[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");
|
|
2752
2982
|
"[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");
|
|
2753
|
-
"[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2983
|
+
"[redis_queued_locks.try_lock.acq_added_to_queue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2754
2984
|
"[redis_queued_locks.try_lock.remove_expired_acqs]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2755
|
-
"[redis_queued_locks.try_lock.
|
|
2985
|
+
"[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");
|
|
2986
|
+
"[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");
|
|
2756
2987
|
"[redis_queued_locks.try_lock.exit__queue_ttl_reached]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2757
|
-
"[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>");
|
|
2758
|
-
"[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>");
|
|
2759
|
-
"[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");
|
|
2760
|
-
"[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");
|
|
2761
|
-
"[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");
|
|
2988
|
+
"[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>");
|
|
2989
|
+
"[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>");
|
|
2990
|
+
"[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");
|
|
2991
|
+
"[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");
|
|
2992
|
+
"[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");
|
|
2762
2993
|
"[redis_queued_locks.try_lock.obtain__free_to_acquire]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2763
2994
|
```
|
|
2764
2995
|
|
|
2765
|
-
- lock series logs (raised from `#lock_series`/`#lock_series
|
|
2996
|
+
- lock series logs (raised from `#lock_series`/`#lock_series!`; `rw_mode` is the mode of the series; with `log_lock_try` each
|
|
2997
|
+
attempt of the series writes the `try_lock.*` logs of the list above with its own `lock_key`, one log per lock of the series):
|
|
2766
2998
|
|
|
2767
2999
|
```ruby
|
|
2768
|
-
"[redis_queued_locks.start_lock_series_obtaining]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2769
|
-
"[redis_queued_locks.
|
|
2770
|
-
"[redis_queued_locks.
|
|
3000
|
+
"[redis_queued_locks.start_lock_series_obtaining]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3001
|
+
"[redis_queued_locks.start_try_to_lock_series_cycle]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3002
|
+
"[redis_queued_locks.dead_score_reached__reset_series_position]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3003
|
+
"[redis_queued_locks.lock_series_intermediate_lock_obtained]" # (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
3004
|
+
"[redis_queued_locks.lock_series_lock_obtained]" # (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
3005
|
+
"[redis_queued_locks.lock_series_lock_obtaining_failed]" # (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
|
|
3006
|
+
"[redis_queued_locks.lock_series_obtained]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "reentrant_lock_keys");
|
|
3007
|
+
"[redis_queued_locks.lock_series_obtaining_failed]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result", "failed_lock_key");
|
|
3008
|
+
"[redis_queued_locks.dequeue_from_lock_series_queues]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3009
|
+
"[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3010
|
+
"[redis_queued_locks.decrease_lock_series]" # (logs "lock_keys", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2771
3011
|
```
|
|
2772
3012
|
|
|
2773
3013
|
- operation logs (one log line per operation that changes Redis keys, with the time of the whole operation; the same data as
|
|
@@ -2808,19 +3048,27 @@ rql.lock('report') { rebuild_report } # read_write_mode: :write by default
|
|
|
2808
3048
|
# - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
|
|
2809
3049
|
# - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
|
|
2810
3050
|
# - at this moment the only debug logs are realised in following cases:
|
|
2811
|
-
# - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2812
|
-
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2813
|
-
# - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
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");
|
|
2816
|
-
# - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2817
|
-
# - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
3051
|
+
# - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3052
|
+
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3053
|
+
# - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3054
|
+
# - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
3055
|
+
# - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
|
|
3056
|
+
# - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
3057
|
+
# - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
2818
3058
|
# - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2819
3059
|
# - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2820
3060
|
# - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2821
|
-
# - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2822
|
-
# - "[redis_queued_locks.
|
|
2823
|
-
# - "[redis_queued_locks.
|
|
3061
|
+
# - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3062
|
+
# - "[redis_queued_locks.start_try_to_lock_series_cycle]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3063
|
+
# - "[redis_queued_locks.dead_score_reached__reset_series_position]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3064
|
+
# - "[redis_queued_locks.lock_series_intermediate_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
3065
|
+
# - "[redis_queued_locks.lock_series_lock_obtained]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time");
|
|
3066
|
+
# - "[redis_queued_locks.lock_series_lock_obtaining_failed]" (logs "lock_key", "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result");
|
|
3067
|
+
# - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "reentrant_lock_keys");
|
|
3068
|
+
# - "[redis_queued_locks.lock_series_obtaining_failed]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority", "acq_time", "result", "failed_lock_key");
|
|
3069
|
+
# - "[redis_queued_locks.dequeue_from_lock_series_queues]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3070
|
+
# - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
3071
|
+
# - "[redis_queued_locks.decrease_lock_series]" (logs "lock_keys", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2824
3072
|
# - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
|
|
2825
3073
|
# - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
|
|
2826
3074
|
# - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
|
|
@@ -2845,15 +3093,16 @@ config['logger'] = RedisQueuedLocks::Logging::VoidLogger
|
|
|
2845
3093
|
# - "[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");
|
|
2846
3094
|
# - "[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");
|
|
2847
3095
|
# - "[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");
|
|
2848
|
-
# - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
3096
|
+
# - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "priority");
|
|
2849
3097
|
# - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2850
|
-
# - "[redis_queued_locks.try_lock.
|
|
3098
|
+
# - "[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");
|
|
3099
|
+
# - "[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");
|
|
2851
3100
|
# - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2852
|
-
# - "[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>");
|
|
2853
|
-
# - "[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>");
|
|
2854
|
-
# - "[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");
|
|
2855
|
-
# - "[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");
|
|
2856
|
-
# - "[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");
|
|
3101
|
+
# - "[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>");
|
|
3102
|
+
# - "[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>");
|
|
3103
|
+
# - "[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");
|
|
3104
|
+
# - "[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");
|
|
3105
|
+
# - "[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");
|
|
2857
3106
|
# - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2858
3107
|
# - "rw_mode" is the requested lock mode ('write'/'read');
|
|
2859
3108
|
config['log_lock_try'] = false
|
|
@@ -2961,8 +3210,12 @@ List of instrumentation events
|
|
|
2961
3210
|
- ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
|
|
2962
3211
|
- ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
|
|
2963
3212
|
- ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
|
|
3213
|
+
- ["redis_queued_locks.lock_series_intermediate_lock_obtained"](#redis_queued_lockslock_series_intermediate_lock_obtained)
|
|
3214
|
+
- ["redis_queued_locks.lock_series_lock_obtained"](#redis_queued_lockslock_series_lock_obtained)
|
|
3215
|
+
- ["redis_queued_locks.lock_series_lock_obtaining_failed"](#redis_queued_lockslock_series_lock_obtaining_failed)
|
|
2964
3216
|
- ["redis_queued_locks.lock_series_obtained"](#redis_queued_lockslock_series_obtained)
|
|
2965
3217
|
- ["redis_queued_locks.lock_series_hold_and_release"](#redis_queued_lockslock_series_hold_and_release)
|
|
3218
|
+
- ["redis_queued_locks.lock_series_obtaining_failed"](#redis_queued_lockslock_series_obtaining_failed)
|
|
2966
3219
|
- ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
|
|
2967
3220
|
- ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
|
|
2968
3221
|
- ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
|
|
@@ -2972,8 +3225,8 @@ List of instrumentation events
|
|
|
2972
3225
|
- ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
|
|
2973
3226
|
|
|
2974
3227
|
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
|
|
2976
|
-
not obtained, held and released);
|
|
3228
|
+
- the lock acquirement (`#lock`/`#lock!`, `#lock_series`/`#lock_series!`): each meaningful outcome (the lock or the whole
|
|
3229
|
+
series is obtained, not obtained, held and released);
|
|
2977
3230
|
- each operation that changes Redis keys (`#unlock`, `#unlock_read`, `#clear_locks`, `#clear_locks_of`/`#clear_current_locks`,
|
|
2978
3231
|
`#extend_lock_ttl`, `#clear_dead_requests`, `#reindex_locks`): one event with the time of the whole operation;
|
|
2979
3232
|
- lock information queries (`#lock_info`, `#locks`, `#queues`, `#keys`, ...) are not instrumented;
|
|
@@ -2991,6 +3244,7 @@ Detalized event semantics and payload structure:
|
|
|
2991
3244
|
- `:hst_id` - `string` - lock's host identifier;
|
|
2992
3245
|
- `:lock_key` - `string` - lock name;
|
|
2993
3246
|
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3247
|
+
- `: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));
|
|
2994
3248
|
- `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend;
|
|
2995
3249
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
|
2996
3250
|
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
|
|
@@ -2998,11 +3252,11 @@ Detalized event semantics and payload structure:
|
|
|
2998
3252
|
##### `"redis_queued_locks.lock_obtaining_failed"`
|
|
2999
3253
|
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3000
3254
|
- 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)
|
|
3002
|
-
(for the lock of the series that is not obtained);
|
|
3255
|
+
- raised from `#lock`/`#lock!` (with `raise_errors: true` - before the error is raised);
|
|
3003
3256
|
- payload:
|
|
3004
3257
|
- `:lock_key` - `string` - lock name;
|
|
3005
3258
|
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3259
|
+
- `: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));
|
|
3006
3260
|
- `:ttl` - `integer`/`milliseconds` - requested lock ttl;
|
|
3007
3261
|
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3008
3262
|
- `:hst_id` - `string` - lock's host identifier;
|
|
@@ -3020,6 +3274,7 @@ Detalized event semantics and payload structure:
|
|
|
3020
3274
|
- `:lock_key` - `string` - lock name;
|
|
3021
3275
|
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3022
3276
|
a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
|
|
3277
|
+
- `: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));
|
|
3023
3278
|
- `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
|
|
3024
3279
|
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3025
3280
|
- `:hst_id` - `string` - lock's host identifier;
|
|
@@ -3035,6 +3290,7 @@ Detalized event semantics and payload structure:
|
|
|
3035
3290
|
- `:lock_key` - `string` - lock name;
|
|
3036
3291
|
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3037
3292
|
a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
|
|
3293
|
+
- `: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));
|
|
3038
3294
|
- `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
|
|
3039
3295
|
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3040
3296
|
- `:hst_id` - `string` - lock's host identifier;
|
|
@@ -3053,6 +3309,7 @@ Detalized event semantics and payload structure:
|
|
|
3053
3309
|
- `:hst_id` - `string` - lock's host identifier;
|
|
3054
3310
|
- `:lock_key` - `string` - lock name;
|
|
3055
3311
|
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3312
|
+
- `: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));
|
|
3056
3313
|
- `:ts` - `numeric`/`epoch` - the time when lock was obtained;
|
|
3057
3314
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
|
3058
3315
|
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
|
|
@@ -3071,37 +3328,114 @@ Detalized event semantics and payload structure:
|
|
|
3071
3328
|
- `:lock_key` - `string` - lock name;
|
|
3072
3329
|
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3073
3330
|
a read lock request under the write lock of the same acquirer works through the write lock (`:rw_mode` is `:read`);
|
|
3331
|
+
- `: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));
|
|
3074
3332
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
|
3075
3333
|
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
|
|
3076
3334
|
|
|
3335
|
+
##### `"redis_queued_locks.lock_series_intermediate_lock_obtained"`
|
|
3336
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3337
|
+
- intermediate obtaining: a moment when the lock of the series passed all checks of an attempt of the series (the lock is free and the lock
|
|
3338
|
+
request of the series is next). Nothing is held yet: the series can still wait for its other locks or fail;
|
|
3339
|
+
- raised from `#lock_series`/`#lock_series!` once per lock of the series (not per attempt), in the order of the lock names up to the lock
|
|
3340
|
+
that blocks the series; reentrant locks of the series are not reported;
|
|
3341
|
+
- payload:
|
|
3342
|
+
- `:lock_key` - `string` - the lock of the series;
|
|
3343
|
+
- `:lock_keys` - `array<string>` - all locks of the series;
|
|
3344
|
+
- `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3345
|
+
- `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
3346
|
+
- `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
|
|
3347
|
+
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3348
|
+
- `:hst_id` - `string` - lock's host identifier;
|
|
3349
|
+
- `:ts` - `float`/`epoch` - the time of the event;
|
|
3350
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring up to this moment;
|
|
3351
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
|
|
3352
|
+
|
|
3353
|
+
##### `"redis_queued_locks.lock_series_lock_obtained"`
|
|
3354
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3355
|
+
- full obtaining: a moment when the lock of the series was really obtained (all locks of the series are held);
|
|
3356
|
+
- raised from `#lock_series`/`#lock_series!` for each lock obtained by the series, right before `lock_series_obtained`; reentrant locks of
|
|
3357
|
+
the series are not reported (see `:reentrant_lock_keys` of `lock_series_obtained`);
|
|
3358
|
+
- payload:
|
|
3359
|
+
- `:lock_key` - `string` - the lock of the series;
|
|
3360
|
+
- `:lock_keys` - `array<string>` - all locks of the series;
|
|
3361
|
+
- `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3362
|
+
- `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
3363
|
+
- `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
|
|
3364
|
+
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3365
|
+
- `:hst_id` - `string` - lock's host identifier;
|
|
3366
|
+
- `:ts` - `float`/`epoch` - the time when the series was obtained;
|
|
3367
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
|
|
3368
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
|
|
3369
|
+
|
|
3370
|
+
##### `"redis_queued_locks.lock_series_lock_obtaining_failed"`
|
|
3371
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3372
|
+
- a moment when the series acquirement is failed: the lock of the series that blocked it;
|
|
3373
|
+
- raised from `#lock_series`/`#lock_series!` once per failed series (with `raise_errors: true` - before the error is raised), right before
|
|
3374
|
+
`lock_series_obtaining_failed`; it is not raised when the blocking lock is unknown (no attempt found it);
|
|
3375
|
+
- payload:
|
|
3376
|
+
- `:lock_key` - `string` - the lock that blocked the series;
|
|
3377
|
+
- `:lock_keys` - `array<string>` - all locks of the series;
|
|
3378
|
+
- `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3379
|
+
- `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
3380
|
+
- `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
|
|
3381
|
+
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3382
|
+
- `:hst_id` - `string` - lock's host identifier;
|
|
3383
|
+
- `:ts` - `float`/`epoch` - the time when the series acquirement was failed;
|
|
3384
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
|
|
3385
|
+
- `:result` - `symbol` - the failure result (see `lock_series_obtaining_failed`);
|
|
3386
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
|
|
3387
|
+
|
|
3077
3388
|
##### `"redis_queued_locks.lock_series_obtained"`
|
|
3078
3389
|
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3079
|
-
-
|
|
3390
|
+
- a moment when all locks of the series were obtained;
|
|
3080
3391
|
- raised from `#lock_series`/`#lock_series!`;
|
|
3081
3392
|
- payload:
|
|
3082
3393
|
- `:lock_keys` - `array<string>` - lock names of the series;
|
|
3083
|
-
- `:rw_mode` - `symbol` - the mode of the series
|
|
3084
|
-
- `:
|
|
3085
|
-
|
|
3394
|
+
- `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3395
|
+
- `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
3396
|
+
- `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series (see [lock_series](#lock_series---acquire-a-series-of-locks));
|
|
3086
3397
|
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3087
3398
|
- `:hst_id` - `string` - lock's host identifier;
|
|
3088
3399
|
- `:ts` - `numeric`/`epoch` - the time when the series was obtained;
|
|
3089
3400
|
- `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
|
|
3401
|
+
- `:reentrant_lock_keys` - `array<string>` - the locks of the series that are obtained by your outer logic (reentrant locks:
|
|
3402
|
+
they are not obtained and not released by the series);
|
|
3090
3403
|
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
|
|
3091
3404
|
|
|
3092
3405
|
##### `"redis_queued_locks.lock_series_hold_and_release"`
|
|
3093
3406
|
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3094
|
-
-
|
|
3407
|
+
- an event signalizes about the "hold+and+release" process of the series is finished;
|
|
3095
3408
|
- raised from `#lock_series`/`#lock_series!` when invoked with a block of code;
|
|
3096
3409
|
- payload:
|
|
3097
3410
|
- `:lock_keys` - `array<string>` - lock names of the series;
|
|
3098
|
-
- `:rw_mode` - `symbol` - the mode of the series
|
|
3411
|
+
- `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3412
|
+
- `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
3099
3413
|
- `:hold_time` - `float`/`milliseconds` - the series hold time;
|
|
3100
|
-
- `:ttl` - `integer`/`milliseconds` - the
|
|
3414
|
+
- `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
|
|
3101
3415
|
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3102
3416
|
- `:hst_id` - `string` - lock's host identifier;
|
|
3103
3417
|
- `:ts` - `float`/`epoch` - the time when the series was released;
|
|
3104
3418
|
- `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
|
|
3419
|
+
- `:reentrant_lock_keys` - `array<string>` - the locks of the series that are obtained by your outer logic (reentrant locks:
|
|
3420
|
+
they are not obtained and not released by the series);
|
|
3421
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
|
|
3422
|
+
|
|
3423
|
+
##### `"redis_queued_locks.lock_series_obtaining_failed"`
|
|
3424
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3425
|
+
- a moment when the series acquirement is failed (no lock of the series is obtained);
|
|
3426
|
+
- raised from `#lock_series`/`#lock_series!` (with `raise_errors: true` - before the error is raised);
|
|
3427
|
+
- payload:
|
|
3428
|
+
- `:lock_keys` - `array<string>` - lock names of the series;
|
|
3429
|
+
- `:rw_mode` - `symbol` - the mode of the series: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3430
|
+
- `:priority` - `integer` - the lock request priority of the series (`priority`): from `-100` to `100` (a priority name is reported as its integer priority, see [Lock Request Prioritization](#lock-request-prioritization));
|
|
3431
|
+
- `:ttl` - `integer`/`milliseconds` - the ttl of each lock of the series;
|
|
3432
|
+
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3433
|
+
- `:hst_id` - `string` - lock's host identifier;
|
|
3434
|
+
- `:ts` - `float`/`epoch` - the time when the series acquirement was failed;
|
|
3435
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on the series acquiring;
|
|
3436
|
+
- `:result` - `symbol` - the failure result: `:timeout_reached`, `:retry_limit_reached`, `:fail_fast_no_try`, `:fail_fast_after_try`, `:conflict_dead_lock`, `:conflict_lock_upgrade`;
|
|
3437
|
+
- `:failed_lock_key` - `string`/`nil` - the first lock that blocked the last completed attempt of the series (`nil`: unknown:
|
|
3438
|
+
no attempt was completed or some lock state was changed during the attempt);
|
|
3105
3439
|
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock_series`/`#lock_series!` method as `:instrument` attribute;
|
|
3106
3440
|
|
|
3107
3441
|
##### `"redis_queued_locks.explicit_lock_release"`
|
|
@@ -3203,13 +3537,7 @@ Detalized event semantics and payload structure:
|
|
|
3203
3537
|
- isolated timeouts which are independent of internal Ruby's timeout implementation (where all timeouts are hostend inside
|
|
3204
3538
|
the global "timeout request" queue and managed by a single global "timeout wathcer" thread). it should prevent any logic and timeout intersections,
|
|
3205
3539
|
some GVL-related things and problem situations when the global watcher thread is "dead";
|
|
3206
|
-
- lock request prioritization;
|
|
3207
3540
|
- **strict redlock algorithm support** (support for many `RedisClient` instances that are fully independent (distributed redis instances));
|
|
3208
|
-
- #lock_series` - acquire a series of locks:
|
|
3209
|
-
- (NOTE: "Proof of Concept" implementation is ready to use);
|
|
3210
|
-
```ruby
|
|
3211
|
-
rql.lock_series('lock_a', 'lock_b', 'lock_c') { puts 'locked' }
|
|
3212
|
-
```
|
|
3213
3541
|
- an ability to release all locks and all requests of the concrete acquirer id or host id (or both in validation-orianted combination);
|
|
3214
3542
|
- detailed lock informotion inside the error object in cases of exceptions (at the moment we have this info inside the error message only that hard to analyze in work);
|
|
3215
3543
|
- a convenient way to mark any `lock` invocation as "non-instrumentable" / "non-loggable" (as an alternative to `VoidNotifier` and to `VoidLogger`);
|
|
@@ -3221,6 +3549,7 @@ Detalized event semantics and payload structure:
|
|
|
3221
3549
|
Mutex (instantiated inside the main Ractor) from the our non-main Ractor (swar-mode works under the non-main ractor) and this process fails\
|
|
3222
3550
|
on non-sharable memory error);
|
|
3223
3551
|
- **Minor**:
|
|
3552
|
+
- `*_status` aliases for `*_info` methods;
|
|
3224
3553
|
- an ability to return all insturmentation metrics from the `lock` invocation (and after block `yield`ing);
|
|
3225
3554
|
- think about a PG-like `stats`-data/table, that should be helpful for lock history acquirement analyzation :thinking: (suitable for cases when we want
|
|
3226
3555
|
to check if the lock was acquired by some different parallel process durign the some period of time in the past or not);
|
|
@@ -3257,7 +3586,7 @@ Detalized event semantics and payload structure:
|
|
|
3257
3586
|
- add a library-level exception, when RQL-related key in Redis (required for its logic) has incompatible type (means: some other program uses our key with their own type and logic and RQL can't work properly);
|
|
3258
3587
|
- yardoc docs with CI check (full doc coverage check);
|
|
3259
3588
|
- split *exception* inheritance to the groups: `lock obtaining errors`, `block invocation errors`, `swarm errors`, and other groups (research possible groups):
|
|
3260
|
-
- in some cases we need to intercept "Lock Obtaining Process Erros", in other cases: "Block Invocation Errors", and so on
|
|
3589
|
+
- in some cases we need to intercept "Lock Obtaining Process Erros", in other cases: "Block Invocation Errors", and so on;
|
|
3261
3590
|
- think about `#debounce` and `#throttle` mechanisms;
|
|
3262
3591
|
|
|
3263
3592
|
---
|
|
@@ -3269,6 +3598,17 @@ Detalized event semantics and payload structure:
|
|
|
3269
3598
|
bundle exec rake rspec
|
|
3270
3599
|
```
|
|
3271
3600
|
|
|
3601
|
+
- Tests in parallel processes (`RSpec`, ~6x faster): each process (worker) works with its own pair of Redis databases
|
|
3602
|
+
(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:
|
|
3603
|
+
the Redis `databases` / 2, up to the CPU count); the examples are distributed by their durations of the previous runs
|
|
3604
|
+
(`tmp/rspec_parallel/durations.json`), the output of each worker is in `tmp/rspec_parallel/worker-<n>.log`, the coverage
|
|
3605
|
+
report of the whole run is collated into `coverage/` after all workers:
|
|
3606
|
+
```shell
|
|
3607
|
+
bundle exec rake rspec:parallel
|
|
3608
|
+
WORKERS=4 bundle exec rake rspec:parallel
|
|
3609
|
+
bundle exec rake "rspec:parallel[spec/redis_queued_locks_spec.rb]" # spec files/filters
|
|
3610
|
+
```
|
|
3611
|
+
|
|
3272
3612
|
- Linting (`Rubocop`+`RBS`):
|
|
3273
3613
|
```shell
|
|
3274
3614
|
bundle exec rake rubocop
|
|
@@ -3281,7 +3621,8 @@ bundle exec rake steep:check
|
|
|
3281
3621
|
|
|
3282
3622
|
- `Runtime` TypeChecking (`RBS`):
|
|
3283
3623
|
```shell
|
|
3284
|
-
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
|
|
3624
|
+
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
|
|
3625
|
+
# or in parallel processes: the same environment with `bundle exec rake rspec:parallel` instead of `bundle exec rspec`
|
|
3285
3626
|
```
|
|
3286
3627
|
|
|
3287
3628
|
---
|