redis_queued_locks 1.13.0 → 1.15.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.
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # RedisQueuedLocks · [![Gem Version](https://badge.fury.io/rb/redis_queued_locks.svg)](https://badge.fury.io/rb/redis_queued_locks)
1
+ # RedisQueuedLocks · ![Gem Version](https://img.shields.io/gem/v/redis_queued_locks)
2
2
 
3
- [![Tests (RSpec)](https://github.com/0exp/redis_queued_locks/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![Lint (Rubocop/RBS)](https://github.com/0exp/redis_queued_locks/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![TypeCheck (Runtime) [RBS]](https://github.com/0exp/redis_queued_locks/actions/workflows/typecheck-runtime.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![TypeCheck (Static) [Steep]](https://github.com/0exp/redis_queued_locks/actions/workflows/typecheck-static.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions)
3
+ [![Tests (RSpec)](https://github.com/0exp/redis_queued_locks/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![Lint (Rubocop)](https://github.com/0exp/redis_queued_locks/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![TypeCheck (Runtime/RBS)](https://github.com/0exp/redis_queued_locks/actions/workflows/typecheck-runtime.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions) [![TypeCheck (Static/Steep)](https://github.com/0exp/redis_queued_locks/actions/workflows/typecheck-static.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions)
4
4
 
5
- <a href="https://redis.io/docs/manual/patterns/distributed-locks/">Distributed locks</a> with "prioritized lock acquisition queue" capabilities based on the Redis Database.
5
+ <a href="https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/">Distributed locks</a> with "prioritized lock acquisition queue" capabilities based on the Redis Database.
6
6
 
7
- Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) (with requeue capabilities) which guarantees the request queue will never be stacked.
7
+ Each lock request is put into the request queue (each lock is hosted by its own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) (with requeue capabilities) which guarantees the request queue will never be stacked.
8
8
 
9
9
  In addition to the classic `queued` (FIFO) strategy RQL supports `random` (RANDOM) lock obtaining strategy when any acquirer from the lock queue can obtain the lock regardless the position in the queue.
10
10
 
@@ -27,8 +27,10 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
27
27
  - [queue_info](#queue_info)
28
28
  - [locked?](#locked)
29
29
  - [queued?](#queued)
30
- - [unlock](#unlock---release-a-lock)
31
- - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
30
+ - [unlock](#unlock---release-a-lock) (aka `release_lock`)
31
+ - [clear_locks](#clear_locks---release-all-locks-and-lock-queues) (aka `release_locks`)
32
+ - [clear_locks_of](#clear_locks_of) (aka `release_locks_of`)
33
+ - [clear_current_locks](#clear_current_locks) (aka `release_current_locks`)
32
34
  - [extend_lock_ttl](#extend_lock_ttl)
33
35
  - [locks](#locks---get-list-of-obtained-locks)
34
36
  - [queues](#queues---get-list-of-lock-request-queues)
@@ -62,7 +64,16 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
62
64
  - [Instrumentation](#instrumentation)
63
65
  - [Instrumentation Configuration](#instrumentation-configuration)
64
66
  - [Instrumentation Events](#instrumentation-events)
67
+ - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_hold_and_release)
68
+ - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
69
+ - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
70
+ - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
71
+ - ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
72
+ - ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
73
+ - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
74
+ - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
65
75
  - [Roadmap](#roadmap)
76
+ - [Build and Develop](#build-and-develop)
66
77
  - [Contributing](#contributing)
67
78
  - [License](#license)
68
79
  - [Authors](#authors)
@@ -93,7 +104,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
93
104
 
94
105
  <sup>\[[back to top](#table-of-contents)\]</sup>
95
106
 
96
- > Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
107
+ > Each lock request is put into the request queue (each lock is hosted by its own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
97
108
 
98
109
  > 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.
99
110
 
@@ -151,7 +162,7 @@ rq_lock_client.lock("some-lock") { puts "Hello, lock!" }
151
162
  ```ruby
152
163
  redis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance
153
164
 
154
- clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
165
+ client = RedisQueuedLocks::Client.new(redis_client) do |config|
155
166
  # (default: 3) (supports nil)
156
167
  # - nil means "infinite retries" and you are only limited by the "try_to_lock_timeout" config;
157
168
  config['retry_count'] = 3
@@ -223,7 +234,7 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
223
234
  # - `:extendable_work_through` - continue working under the lock <with> lock's TTL extension;
224
235
  # - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
225
236
  # - `:dead_locking` - fail with deadlock exception;
226
- # - See "Dead locks and Reentrant Locks" documentation section in REDME.md for details;
237
+ # - See "Dead locks and Reentrant Locks" documentation section in README.md for details;
227
238
  config['default_conflict_strategy'] = :wait_for_lock
228
239
 
229
240
  # (default: 100)
@@ -231,10 +242,21 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
231
242
  # - affects the performance of your Redis and Ruby Application (configure thoughtfully);
232
243
  config['lock_release_batch_size'] = 100
233
244
 
245
+ # (default: 300)
246
+ # - how many items will be released at a time in #clear_locks_of and in #clear_current_locks methods (uses SCAN for batch extraction and DEL for batch deletion);
247
+ # - affects the ruby's memory (cuz this batch will be stored in Set object in first, and then will be splatted to the DEL redis method invocation) (configure thoughtfully);
248
+ # - affects the performance of your Redis (configure thoughtfully);
249
+ config['clear_locks_of__lock_scan_size'] = 300
250
+
251
+ # (default: 300)
252
+ # - how many queues will be SCAN'ned at a time in #clear_locks_of and in #clear_current_locks methods (uses SCAN);
253
+ # - affects the performance of your Redis (configure thoughtfully);
254
+ config['clear_locks_of__queue_scan_size'] = 300
255
+
234
256
  # (default: 500)
235
257
  # - how many items should be extracted from redis during the #locks, #queues, #keys
236
258
  # #locks_info, and #queues_info operations (uses SCAN);
237
- # - affects the performance of your Redis and Ruby Application (configure thoughtfully;)
259
+ # - affects the performance of your Redis and Ruby Application (configure thoughtfully);
238
260
  config['key_extraction_batch_size'] = 500
239
261
 
240
262
  # (default: 1 day)
@@ -555,8 +577,8 @@ def lock(
555
577
  - `false` by default;
556
578
  - `block` - (optional) `[Block]`
557
579
  - A block of code that should be executed after the successfully acquired lock.
558
- - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
559
- - If block is **not passed** the obtained lock will be released after it's ttl;
580
+ - If block is **passed** the obtained lock will be released after the block execution or its ttl (what will happen first);
581
+ - If block is **not passed** the obtained lock will be released after its ttl;
560
582
  - If you want the block to have a TTL too and this TTL to be the same as TTL of the lock
561
583
  use `timed: true` option (`rql.lock("my_lock", timed: true, ttl: 5_000) { ... }`)
562
584
 
@@ -700,7 +722,7 @@ rql.lock_info("my_lock")
700
722
 
701
723
  - (`:queue_ttl`) setting a short limit of time to the lock request queue position (if a process fails to acquire
702
724
  the lock within this period of time (and before timeout/retry_count limits occurs of course) -
703
- it's lock request will be moved to the end of queue):
725
+ its lock request will be moved to the end of queue):
704
726
 
705
727
  ```ruby
706
728
  rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
@@ -735,7 +757,7 @@ rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
735
757
  ```
736
758
 
737
759
  - obtain a lock in `:random` way (with `:random` strategy): in `:random` strategy
738
- any acquirer from the lcok queue can obtain the lock regardless of the position in the lock queue;
760
+ any acquirer from the lock queue can obtain the lock regardless of the position in the lock queue;
739
761
 
740
762
  ```ruby
741
763
  # Current Process (process#1)
@@ -1120,6 +1142,197 @@ rql.clear_locks
1120
1142
 
1121
1143
  ---
1122
1144
 
1145
+ #### #clear_locks_of
1146
+
1147
+ <sup>\[[back to top](#usage)\]</sup>
1148
+
1149
+ - release all locks of the passed acquirer/host and remove this acquirer/host from all queues (locks will be released first)
1150
+ - this is a cleanup helper intended for operational and debugging scenarios (for example: your
1151
+ current puma request thread is killed by Rack::Timeout and you need to cleanup all zombie RQL
1152
+ locks and lock reuqests obtained during the request processing);
1153
+ - acquirer/host dentifiers can be extracted via:
1154
+ - `#current_host_id`
1155
+ - `#current_acquirer_id`
1156
+ - `#possible_host_ids`
1157
+ - from lock data (extracted via `#lock_info`, `#locks_info`, `#queue_info`, `#queues_info`);
1158
+ - produces `"redis_queued_locks.release_locks_of"` instrumentation event;
1159
+ - accepts:
1160
+ - `:host_id` - (required) `[String]`
1161
+ - host indentifier whose locks we want to release and which we want to remove from all locks queues;
1162
+ - `:acquirer_id` - (required) `[String]`
1163
+ - acquirer indentifier whose locks we want to release and which we want to remove from all locks queues;
1164
+ - `:lock_scan_size` - (optional) `[Integer]`
1165
+ - how many items will be released at a time (uses `SCAN `for batch extraction and `DEL` for batch deletion);
1166
+ - pre-configuerd value in `config['clear_locks_of__lock_scan_size']`;
1167
+ - `:queue_scan_size` - (optional) `[Integer]`
1168
+ - how many queues will be SCAN'ned at a time (uses `SCAN)`;
1169
+ - pre-configuerd value in `config['clear_locks_of__queue_scan_size']`;
1170
+ - `:logger` - (optional) `[::Logger,#debug]`
1171
+ - custom logger object;
1172
+ - pre-configured value in `config['logger']`;
1173
+ - `:instrumenter` - (optional) `[#notify]`
1174
+ - custom instrumenter object;
1175
+ - pre-configured value in `config['isntrumenter']`;
1176
+ - `:instrument` - (optional) `[NilClass,Any]`
1177
+ - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
1178
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
1179
+ - enables **log sampling**;
1180
+ - pre-configured in `config['log_sampling_enabled']`;
1181
+ - `:log_sampling_percent` - (optional) `[Integer]`
1182
+ - **log sampling**:the percent of cases that should be logged;
1183
+ - pre-configured in `config['log_sampling_percent']`;
1184
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
1185
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
1186
+ - pre-configured in `config['log_sampler']`;
1187
+ - `log_sample_this` - (optional) `[Boolean]`
1188
+ - marks the method that everything should be logged despite the enabled log sampling;
1189
+ - makes sense when log sampling is enabled;
1190
+ - `false` by default;
1191
+ - `:instr_sampling_enabled` - (optional) `[Boolean]`
1192
+ - enables **instrumentaion sampling**;
1193
+ - pre-configured in `config['instr_sampling_enabled']`;
1194
+ - `instr_sampling_percent` - (optional) `[Integer]`
1195
+ - the percent of cases that should be instrumented;
1196
+ - pre-configured in `config['instr_sampling_percent']`;
1197
+ - `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
1198
+ - percent-based log sampler that decides should be RQL case instrumented or not;
1199
+ - pre-configured in `config['instr_sampler']`;
1200
+ - `instr_sample_this` - (optional) `[Boolean]`
1201
+ - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;
1202
+ - makes sense when instrumentation sampling is enabled;
1203
+ - `false` by default;
1204
+ - returns:
1205
+ - `[Hash<Symbol,Numeric>]` - Format: `{ ok: true, result: Hash<Symbol,Numeric> }`;
1206
+ - result data:
1207
+ - `:rel_time` - `Numeric` - time spent;
1208
+ - `:rel_key_cnt` - `Integer` - the count of released locks;
1209
+ - `:tch_queue_cnt` - `Integer` - the count of touched (modified) queues (the number of queues from which the acquirer/host was removed);
1210
+
1211
+ ```ruby
1212
+ rql.clear_locks_of(
1213
+ host_id: "rql:hst:41478/4320/4360/848818f09d8c3420",
1214
+ acquirer_id: "rql:acq:41478/4320/4340/4360/848818f09d8c3420"
1215
+ )
1216
+ # -- or (alias) --
1217
+ rql.release_locks_of(
1218
+ host_id: "rql:hst:41478/4320/4360/848818f09d8c3420",
1219
+ acquirer_id: "rql:acq:41478/4320/4340/4360/848818f09d8c3420"
1220
+ )
1221
+
1222
+ # -> result
1223
+ {
1224
+ ok: true,
1225
+ result: {
1226
+ rel_key_cnt: 123,
1227
+ tch_queue_cnt: 2,
1228
+ rel_time: 0.1
1229
+ }
1230
+ }
1231
+ ```
1232
+
1233
+ ```ruby
1234
+ # clear locks and queues of the current acquirer:
1235
+ rql.clear_locks_of(
1236
+ host_id: rql.current_host_id,
1237
+ acquirer_id: rql.current_acquirer_id
1238
+ )
1239
+
1240
+ # equivalent:
1241
+ rql.clear_current_locks
1242
+
1243
+ # -> result
1244
+ {
1245
+ ok: true,
1246
+ result: {
1247
+ rel_key_cnt: 43,
1248
+ tch_queue_cnt: 3,
1249
+ rel_time: 0.2
1250
+ }
1251
+ }
1252
+ ```
1253
+
1254
+ ---
1255
+
1256
+ #### #clear_current_locks
1257
+
1258
+ <sup>\[[back to top](#usage)\]</sup>
1259
+
1260
+ - release all locks of the current acquirer/host and remove the current acquirer/host from all queues (locks will be released first);
1261
+ - this is a cleanup helper intended for operational and debugging scenarios (for example: your
1262
+ current puma request thread is killed by Rack::Timeout and you need to cleanup all zombie RQL
1263
+ locks and lock reuqests obtained during the request processing);
1264
+ - representes an equivalent invocation:
1265
+ ```ruby
1266
+ rql.clear_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acquirer_id)
1267
+ ```
1268
+ - produces `"redis_queued_locks.release_locks_of"` instrumentation event;
1269
+ - `:lock_scan_size` - (optional) `[Integer]`
1270
+ - how many items will be released at a time (uses `SCAN `for batch extraction and `DEL` for batch deletion);
1271
+ - pre-configuerd value in `config['clear_locks_of__lock_scan_size']`;
1272
+ - `:queue_scan_size` - (optional) `[Integer]`
1273
+ - how many queues will be SCAN'ned at a time (uses `SCAN)`;
1274
+ - pre-configuerd value in `config['clear_locks_of__queue_scan_size']`;
1275
+ - `:logger` - (optional) `[::Logger,#debug]`
1276
+ - custom logger object;
1277
+ - pre-configured value in `config['logger']`;
1278
+ - `:instrumenter` - (optional) `[#notify]`
1279
+ - custom instrumenter object;
1280
+ - pre-configured value in `config['isntrumenter']`;
1281
+ - `:instrument` - (optional) `[NilClass,Any]`
1282
+ - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
1283
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
1284
+ - enables **log sampling**;
1285
+ - pre-configured in `config['log_sampling_enabled']`;
1286
+ - `:log_sampling_percent` - (optional) `[Integer]`
1287
+ - **log sampling**:the percent of cases that should be logged;
1288
+ - pre-configured in `config['log_sampling_percent']`;
1289
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
1290
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
1291
+ - pre-configured in `config['log_sampler']`;
1292
+ - `log_sample_this` - (optional) `[Boolean]`
1293
+ - marks the method that everything should be logged despite the enabled log sampling;
1294
+ - makes sense when log sampling is enabled;
1295
+ - `false` by default;
1296
+ - `:instr_sampling_enabled` - (optional) `[Boolean]`
1297
+ - enables **instrumentaion sampling**;
1298
+ - pre-configured in `config['instr_sampling_enabled']`;
1299
+ - `instr_sampling_percent` - (optional) `[Integer]`
1300
+ - the percent of cases that should be instrumented;
1301
+ - pre-configured in `config['instr_sampling_percent']`;
1302
+ - `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
1303
+ - percent-based log sampler that decides should be RQL case instrumented or not;
1304
+ - pre-configured in `config['instr_sampler']`;
1305
+ - `instr_sample_this` - (optional) `[Boolean]`
1306
+ - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;
1307
+ - makes sense when instrumentation sampling is enabled;
1308
+ - `false` by default;
1309
+ - returns:
1310
+ - `[Hash<Symbol,Numeric>]` - Format: `{ ok: true, result: Hash<Symbol,Numeric> }`;
1311
+ - result data:
1312
+ - `:rel_time` - `Numeric` - time spent;
1313
+ - `:rel_key_cnt` - `Integer` - the count of released locks;
1314
+ - `:tch_queue_cnt` - `Integer` - the count of touched (modified) queues (the number of queues from which the acquirer/host was removed);
1315
+
1316
+ ```ruby
1317
+ rql.clear_current_locks
1318
+ # -- or (alias) --
1319
+ rql.release_current_locks
1320
+ # equivalent:
1321
+ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acquirer_id)
1322
+
1323
+ # -> result
1324
+ {
1325
+ ok: true,
1326
+ result: {
1327
+ rel_key_cnt: 43,
1328
+ tch_queue_cnt: 3,
1329
+ rel_time: 0.2
1330
+ }
1331
+ }
1332
+ ```
1333
+
1334
+ ---
1335
+
1123
1336
  #### #extend_lock_ttl
1124
1337
 
1125
1338
  <sup>\[[back to top](#usage)\]</sup>
@@ -1320,7 +1533,7 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
1320
1533
  :status=>:alive,
1321
1534
  :info=>{
1322
1535
  "acq_id"=>"rql:acq:41478/4320/4340/4360/848818f09d8c3420",
1323
- "hst_id"=>"rql:hst:41478/4320/4360/848818f09d8c3420"
1536
+ "hst_id"=>"rql:hst:41478/4320/4360/848818f09d8c3420",
1324
1537
  "ts"=>1711607112.670343,
1325
1538
  "ini_ttl"=>15000,
1326
1539
  "rem_ttl"=>13998}},
@@ -1383,7 +1596,7 @@ Accepts:
1383
1596
  - `:dead_ttl` - (optional) `[Integer]`
1384
1597
  - lock request ttl after which a lock request is considered dead;
1385
1598
  - has a preconfigured value in `config['dead_request_ttl']` (1 day by default);
1386
- - `:sacn_size` - (optional) `[Integer]`
1599
+ - `:scan_size` - (optional) `[Integer]`
1387
1600
  - the batch of scanned keys for Redis'es SCAN command;
1388
1601
  - has a preconfigured valie in `config['lock_release_batch_size']`;
1389
1602
  - `:logger` - (optional) `[::Logger,#debug]`
@@ -1445,6 +1658,7 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
1445
1658
 
1446
1659
  <sup>\[[back to top](#usage)\]</sup>
1447
1660
 
1661
+ - (aliases: `#current_acq_id`, `#acq_id`);
1448
1662
  - get the current acquirer identifier in RQL notation that you can use for debugging purposes during the lock analyzation;
1449
1663
  - acquirer identifier format:
1450
1664
  ```ruby
@@ -1482,6 +1696,7 @@ rql.current_acquirer_id
1482
1696
 
1483
1697
  <sup>\[[back to top](#usage)\]</sup>
1484
1698
 
1699
+ - (aliases: `#current_hst_id`, `#hst_id`);
1485
1700
  - get a current host identifier in RQL notation that you can use for debugging purposes during the lock analyzis;
1486
1701
  - the host is a ruby worker (a combination of process/thread/ractor/identity) that is alive and can obtain locks;
1487
1702
  - the host is limited to `process`/`thread`/`ractor` (without `fiber`) combination cuz we have no abilities to extract
@@ -1907,17 +2122,16 @@ config['log_sampler'] = RedisQueuedLocks::Logging::Sampler
1907
2122
 
1908
2123
  An instrumentation layer is incapsulated in `instrumenter` object stored in [config](#configuration) (`RedisQueuedLocks::Client#config['instrumenter']`).
1909
2124
 
1910
- Instrumentation can be sampled. See [Instrumentation Configuration](#instrumentation-configuration) section for details.
2125
+ Instrumentation can be **sampled**. See [Instrumentation Configuration](#instrumentation-configuration) section for details.
1911
2126
 
1912
2127
  Instrumenter object should provide `notify(event, payload)` method with the following signarue:
1913
2128
 
1914
2129
  - `event` - `string`;
1915
2130
  - `payload` - `hash<Symbol,Any>`;
1916
2131
 
1917
- `redis_queued_locks` provides two instrumenters:
2132
+ **RQL** provides two instrumenters:
1918
2133
 
1919
- - `RedisQueuedLocks::Instrument::ActiveSupport` - **ActiveSupport::Notifications** instrumenter
1920
- that instrument events via **ActiveSupport::Notifications** API;
2134
+ - `RedisQueuedLocks::Instrument::ActiveSupport` - **ActiveSupport::Notifications** instrumenter that instrument events via **ActiveSupport::Notifications** API;
1921
2135
  - `RedisQueuedLocks::Instrument::VoidNotifier` - instrumenter that does nothing;
1922
2136
 
1923
2137
  By default `RedisQueuedLocks::Client` is configured with the void notifier (which means "instrumentation is disabled").
@@ -1972,95 +2186,115 @@ config['instr_sampler'] = RedisQueuedLocks::Instrument::Sampler
1972
2186
 
1973
2187
  List of instrumentation events
1974
2188
 
1975
- - `redis_queued_locks.lock_obtained`;
1976
- - `redis_queued_locks.extendable_reentrant_lock_obtained`;
1977
- - `redis_queued_locks.reentrant_lock_obtained`;
1978
- - `redis_queued_locks.lock_hold_and_release`;
1979
- - `redis_queued_locks.reentrant_lock_hold_completes`;
1980
- - `redis_queued_locks.explicit_lock_release`;
1981
- - `redis_queued_locks.explicit_all_locks_release`;
2189
+ - ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_hold_and_release)
2190
+ - ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
2191
+ - ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
2192
+ - ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
2193
+ - ["redis_queued_locks.reentrant_lock_hold_completes"](#redis_queued_locksreentrant_lock_hold_completes)
2194
+ - ["redis_queued_locks.explicit_lock_release"](#redis_queued_locksexplicit_lock_release)
2195
+ - ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
2196
+ - ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
1982
2197
 
1983
2198
  Detalized event semantics and payload structure:
1984
2199
 
1985
- - `"redis_queued_locks.lock_obtained"`
1986
- - a moment when the lock was obtained;
1987
- - raised from `#lock`/`#lock!`;
1988
- - payload:
1989
- - `:ttl` - `integer`/`milliseconds` - lock ttl;
1990
- - `:acq_id` - `string` - lock acquirer identifier;
1991
- - `:hst_id` - `string` - lock's host identifier;
1992
- - `:lock_key` - `string` - lock name;
1993
- - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend;
1994
- - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
1995
- - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
1996
-
1997
- - `"redis_queued_locks.extendable_reentrant_lock_obtained"`
1998
- - an event signalizes about the "extendable reentrant lock" obtaining is happened;
1999
- - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2000
- - payload:
2001
- - `:lock_key` - `string` - lock name;
2002
- - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2003
- - `:acq_id` - `string` - lock acquirer identifier;
2004
- - `:hst_id` - `string` - lock's host identifier;
2005
- - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as extendable reentrant lock;
2006
- - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2007
- - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2008
-
2009
- - `"redis_queued_locks.reentrant_lock_obtained"`
2010
- - an event signalizes about the "reentrant lock" obtaining is happened (without TTL extension);
2011
- - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2012
- - payload:
2013
- - `:lock_key` - `string` - lock name;
2014
- - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2015
- - `:acq_id` - `string` - lock acquirer identifier;
2016
- - `:hst_id` - `string` - lock's host identifier;
2017
- - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;
2018
- - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2019
- - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2020
-
2021
- - `"redis_queued_locks.lock_hold_and_release"`
2022
- - an event signalizes about the "hold+and+release" process is finished;
2023
- - raised from `#lock`/`#lock!` when invoked with a block of code;
2024
- - payload:
2025
- - `:hold_time` - `float`/`milliseconds` - lock hold time;
2026
- - `:ttl` - `integer`/`milliseconds` - lock ttl;
2027
- - `:acq_id` - `string` - lock acquirer identifier;
2028
- - `:hst_id` - `string` - lock's host identifier;
2029
- - `:lock_key` - `string` - lock name;
2030
- - `:ts` - `numeric`/`epoch` - the time when lock was obtained;
2031
- - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2032
- - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2033
-
2034
- - `"redis_queued_locks.reentrant_lock_hold_completes"`
2035
- - an event signalizes about the "reentrant lock hold" is complete (both extendable and non-extendable);
2036
- - lock re-entering can happen many times and this event happens for each of them separately;
2037
- - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2038
- - payload:
2039
- - `:hold_time` - `float`/`milliseconds` - lock hold time;
2040
- - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2041
- - `:acq_id` - `string` - lock acquirer identifier;
2042
- - `:hst_id` - `string` - lock's host identifier;
2043
- - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;
2044
- - `:lock_key` - `string` - lock name;
2045
- - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2046
- - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2047
-
2048
- - `"redis_queued_locks.explicit_lock_release"`
2049
- - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
2050
- - raised from `#unlock`;
2051
- - payload:
2052
- - `:at` - `float`/`epoch` - the time when the lock was released;
2053
- - `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
2054
- - `:lock_key` - `string` - released lock (lock name);
2055
- - `:lock_key_queue` - `string` - released lock queue (lock queue name);
2056
-
2057
- - `"redis_queued_locks.explicit_all_locks_release"`
2058
- - an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
2059
- - raised from `#clear_locks`;
2060
- - payload:
2061
- - `:rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
2062
- - `:at` - `float`/`epoch` - the time when the operation has ended;
2063
- - `:rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
2200
+ ##### `"redis_queued_locks.lock_obtained"`
2201
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2202
+ - a moment when the lock was obtained;
2203
+ - raised from `#lock`/`#lock!`;
2204
+ - payload:
2205
+ - `:ttl` - `integer`/`milliseconds` - lock ttl;
2206
+ - `:acq_id` - `string` - lock acquirer identifier;
2207
+ - `:hst_id` - `string` - lock's host identifier;
2208
+ - `:lock_key` - `string` - lock name;
2209
+ - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend;
2210
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2211
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2212
+
2213
+ ##### `"redis_queued_locks.extendable_reentrant_lock_obtained"`
2214
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2215
+ - an event signalizes about the "extendable reentrant lock" obtaining is happened;
2216
+ - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2217
+ - payload:
2218
+ - `:lock_key` - `string` - lock name;
2219
+ - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2220
+ - `:acq_id` - `string` - lock acquirer identifier;
2221
+ - `:hst_id` - `string` - lock's host identifier;
2222
+ - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as extendable reentrant lock;
2223
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2224
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2225
+
2226
+ ##### `"redis_queued_locks.reentrant_lock_obtained"`
2227
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2228
+ - an event signalizes about the "reentrant lock" obtaining is happened (without TTL extension);
2229
+ - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2230
+ - payload:
2231
+ - `:lock_key` - `string` - lock name;
2232
+ - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2233
+ - `:acq_id` - `string` - lock acquirer identifier;
2234
+ - `:hst_id` - `string` - lock's host identifier;
2235
+ - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;
2236
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2237
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2238
+
2239
+ ##### `"redis_queued_locks.lock_hold_and_release"`
2240
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2241
+ - an event signalizes about the "hold+and+release" process is finished;
2242
+ - raised from `#lock`/`#lock!` when invoked with a block of code;
2243
+ - payload:
2244
+ - `:hold_time` - `float`/`milliseconds` - lock hold time;
2245
+ - `:ttl` - `integer`/`milliseconds` - lock ttl;
2246
+ - `:acq_id` - `string` - lock acquirer identifier;
2247
+ - `:hst_id` - `string` - lock's host identifier;
2248
+ - `:lock_key` - `string` - lock name;
2249
+ - `:ts` - `numeric`/`epoch` - the time when lock was obtained;
2250
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2251
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2252
+
2253
+ ##### `"redis_queued_locks.reentrant_lock_hold_completes"`
2254
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2255
+ - an event signalizes about the "reentrant lock hold" is complete (both extendable and non-extendable);
2256
+ - lock re-entering can happen many times and this event happens for each of them separately;
2257
+ - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;
2258
+ - payload:
2259
+ - `:hold_time` - `float`/`milliseconds` - lock hold time;
2260
+ - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;
2261
+ - `:acq_id` - `string` - lock acquirer identifier;
2262
+ - `:hst_id` - `string` - lock's host identifier;
2263
+ - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;
2264
+ - `:lock_key` - `string` - lock name;
2265
+ - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
2266
+ - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
2267
+
2268
+ ##### `"redis_queued_locks.explicit_lock_release"`
2269
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2270
+ - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
2271
+ - raised from `#unlock`;
2272
+ - payload:
2273
+ - `:at` - `float`/`epoch` - the time when the lock was released;
2274
+ - `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
2275
+ - `:lock_key` - `string` - released lock (lock name);
2276
+ - `:lock_key_queue` - `string` - released lock queue (lock queue name);
2277
+
2278
+ ##### `"redis_queued_locks.explicit_all_locks_release"`
2279
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2280
+ - an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
2281
+ - raised from `#clear_locks`;
2282
+ - payload:
2283
+ - `:rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
2284
+ - `:at` - `float`/`epoch` - the time when the operation has ended;
2285
+ - `:rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
2286
+
2287
+ ##### `"redis_queued_locks.release_locks_of"`
2288
+ - <sup>\[[back to the list](#instrumentation-events)\]</sup>
2289
+ - an event signalizes about the released locks (and removement from lock queues) of the concrete host and acquirer;
2290
+ - raised from `#clear_locks_of` and `#clear_current_locks` (`#release_locks_of` and `#release_current_locks` respectively);
2291
+ - payload:
2292
+ - `:rel_time` - `float`/`milliseconds` - time spent on "release locks of" operation;
2293
+ - `:at` - `float`/`epoch` - the time when the opertaion has ended;
2294
+ - `:acq_id` - `string` - refused acquirer identifier;
2295
+ - `:hst_id` - `string` - refused host identifier;
2296
+ - `:rel_key_cnt` - `integer` - released locks count;
2297
+ - `:tch_queue_cnt` - `:integer` - the number of queues from which the concrete host/acquirer was removed;
2064
2298
 
2065
2299
  ---
2066
2300
 
@@ -2094,6 +2328,9 @@ Detalized event semantics and payload structure:
2094
2328
  - `write` - waits - `write`;
2095
2329
  - **write** mode is a default behavior for all RQL locks;
2096
2330
  - **Minor**:
2331
+ - add `hst_id` to all methods that works with queues info;
2332
+ - try to return the `fiber object id` to the lock host identifier (we cant use fiber object id cuz `ObjectSpace` has no access to the fiber object space after the any ractor object initialization)
2333
+ - named RQL's threads (`Thread#name`) and RQL's ractors (`Ractor#name`) in order to have an ability to find and work with RQL's threads and ractors outside of RQL logic (stop threads before process forking, for example);
2097
2334
  - `#lock`/`#lock!` - `timeout:` option: support for granular periods (it supports only `seconds` at the moment, but we need `milliseconds`);
2098
2335
  - Add `after timeout hook` ability for timeout errors with an ability to access the binding context (if it possibly) of the thread where it failed;
2099
2336
  - Add `ignore timeout errors` ability (for debug purposes);
@@ -2120,7 +2357,31 @@ Detalized event semantics and payload structure:
2120
2357
  - `unlock_on:`-option in lock/lock! logic in order to support auto-unlocking on any exception rasaied under the invoked block (invoked under the lock);
2121
2358
  - **Research**: support for `Valkey` database backend (https://github.com/valkey-io/valkey) (https://valkey.io/);
2122
2359
  - **Research**: support for `Garnet` database backend (https://microsoft.github.io/) (https://github.com/microsoft/garnet);
2123
- - add a library-level exception, when RQL-related key (required for it's logic) has incompatible type (means: some other program uses our key with their own type and logic and RQL can't work properly);
2360
+ - 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);
2361
+ ---
2362
+
2363
+ ## Build and Develop
2364
+
2365
+ - Tests (`RSpec`):
2366
+ ```shell
2367
+ bundle exec rake rspec
2368
+ ```
2369
+
2370
+ - Linting (`Rubocop`+`RBS`):
2371
+ ```shell
2372
+ bundle exec rake rubocop
2373
+ ```
2374
+
2375
+ - `Static` TypeChecking (`Steep`):
2376
+ ```shell
2377
+ bundle exec rake steep:check
2378
+ ```
2379
+
2380
+ - `Runtime` TypeChecking (`RBS`):
2381
+ ```shell
2382
+ bundle exec rbs collection install && RBS_TEST_RAISE=true RUBYOPT='-rrbs/test/setup' RBS_TEST_OPT='-I sig' RBS_TEST_LOGLEVEL=error RBS_TEST_TARGET='RedisQueuedLocks::*' bundle exec rspec --failure-exit-code=0
2383
+ ```
2384
+
2124
2385
  ---
2125
2386
 
2126
2387
  ## Contributing
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
- require "steep/rake_task"
5
+ require 'steep/rake_task'
6
6
  require 'rubocop'
7
7
  require 'rubocop/rake_task'
8
8
  require 'rubocop-performance'
data/Steepfile CHANGED
@@ -9,7 +9,6 @@ target :lib do
9
9
  library 'timeout'
10
10
  library 'securerandom'
11
11
  library 'logger'
12
- library 'objspace'
13
12
  library 'monitor'
14
13
 
15
14
  configure_code_diagnostics(Steep::Diagnostic::Ruby.strict)