redis_queued_locks 1.4.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +178 -10
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +34 -19
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +10 -2
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +72 -14
- data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +21 -1
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +23 -1
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +57 -4
- data/lib/redis_queued_locks/acquier/release_lock.rb +57 -3
- data/lib/redis_queued_locks/client.rb +186 -18
- data/lib/redis_queued_locks/instrument/sampler.rb +27 -0
- data/lib/redis_queued_locks/instrument.rb +47 -0
- data/lib/redis_queued_locks/logging/sampler.rb +28 -0
- data/lib/redis_queued_locks/logging.rb +47 -0
- data/lib/redis_queued_locks/utilities.rb +1 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd9e0ee49d4c08d6ba572a825da792468926b2f676e872a2337b1ce2eff60836
|
4
|
+
data.tar.gz: ae635ac450f966539581c4b5a53098b92ba29562bb0641049571ca0ceb1461e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b678b3fbccd5273d17d07120c144b0eb1c6be32db73ff1aa2f9fb19f485d30499645f3c4592fad0cb4cdeab1ebcd37f437b62740e01a46dce999eaaa2814f146
|
7
|
+
data.tar.gz: b8cb79c79767cd1d3b831992454c84c8399f6bf7ac313b8a2d0a7527711f555cb2196a172ae435103df2fc4c3e483b0a5b35775fba4baee7b7bad6234103cb0a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.6.0] - 2024-05-25
|
4
|
+
### Added
|
5
|
+
- New Feature: **Instrumentation Sampling** - configurable instrumentation sampling based on `weight` algorithm (where the weight is a percentage of RQL cases that should be logged);
|
6
|
+
- Missing instrumenter customization in public `RedisQueuedLocks::Client` methods;
|
7
|
+
- Documentation updates;
|
8
|
+
|
9
|
+
## [1.5.0] - 2024-05-23
|
10
|
+
### Added
|
11
|
+
- New Feature: **Log sampling** - configurable log sampling based on `weight` algorithm (where the weight is a percentage of RQL cases that should be logged);
|
12
|
+
|
3
13
|
## [1.4.0] - 2024-05-13
|
4
14
|
### Added
|
5
15
|
- `#lock`/`#lock!`: reduced memory allocaiton during `:meta` attribute type checking;
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
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.
|
6
6
|
|
7
|
-
Provides flexible invocation flow, parametrized limits (lock request ttl, lock
|
7
|
+
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), logging and instrumentation.
|
8
8
|
|
9
9
|
---
|
10
10
|
|
@@ -182,10 +182,10 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
182
182
|
|
183
183
|
# (default: RedisQueuedLocks::Instrument::VoidNotifier)
|
184
184
|
# - instrumentation layer;
|
185
|
-
# - you can
|
185
|
+
# - you can provide your own instrumenter that should realize `#notify(event, payload = {})` interface:
|
186
186
|
# - event: <string> requried;
|
187
187
|
# - payload: <hash> requried;
|
188
|
-
# - disabled by default via VoidNotifier
|
188
|
+
# - disabled by default via `VoidNotifier`;
|
189
189
|
config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport
|
190
190
|
|
191
191
|
# (default: -> { RedisQueuedLocks::Resource.calc_uniq_identity })
|
@@ -232,6 +232,48 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
232
232
|
# - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
|
233
233
|
# - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id");
|
234
234
|
config.log_lock_try = false
|
235
|
+
|
236
|
+
# (default: false)
|
237
|
+
# - enables <log sampling>: only the configured percent of RQL cases will be logged;
|
238
|
+
# - disabled by default;
|
239
|
+
# - works in tandem with <config.log_sampling_percent> and <log.sampler> configs;
|
240
|
+
config.log_sampling_enabled = false
|
241
|
+
|
242
|
+
# (default: 15)
|
243
|
+
# - the percent of cases that should be logged;
|
244
|
+
# - take an effect when <config.log_sampling_enalbed> is true;
|
245
|
+
# - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
|
246
|
+
config.log_sampling_percent = 15
|
247
|
+
|
248
|
+
# (default: RedisQueuedLocks::Logging::Sampler)
|
249
|
+
# - percent-based log sampler that decides should be RQL case logged or not;
|
250
|
+
# - works in tandem with <config.log_sampling_enabled> and <config.log_sampling_percent> configs;
|
251
|
+
# - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
|
252
|
+
# method so the algorithm error is ~(0%..13%);
|
253
|
+
# - you can provide your own log sampler with bettter algorithm that should realize
|
254
|
+
# `sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);
|
255
|
+
config.log_sampler = RedisQueuedLocks::Logging::Sampler
|
256
|
+
|
257
|
+
# (default: false)
|
258
|
+
# - enables <instrumentaion sampling>: only the configured percent of RQL cases will be instrumented;
|
259
|
+
# - disabled by default;
|
260
|
+
# - works in tandem with <config.instr_sampling_percent and <log.instr_sampler>;
|
261
|
+
config.instr_sampling_enabled = false
|
262
|
+
|
263
|
+
# (default: 15)
|
264
|
+
# - the percent of cases that should be instrumented;
|
265
|
+
# - take an effect when <config.instr_sampling_enalbed> is true;
|
266
|
+
# - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampler> configs;
|
267
|
+
config.instr_sampling_percent = 15
|
268
|
+
|
269
|
+
# (default: RedisQueuedLocks::Instrument::Sampler)
|
270
|
+
# - percent-based log sampler that decides should be RQL case instrumented or not;
|
271
|
+
# - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampling_percent> configs;
|
272
|
+
# - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
|
273
|
+
# method so the algorithm error is ~(0%..13%);
|
274
|
+
# - you can provide your own log sampler with bettter algorithm that should realize
|
275
|
+
# `sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Instrument::Sampler` for example);
|
276
|
+
config.instr_sampler = RedisQueuedLocks::Instrument::Sampler
|
235
277
|
end
|
236
278
|
```
|
237
279
|
|
@@ -288,8 +330,15 @@ def lock(
|
|
288
330
|
identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
|
289
331
|
meta: nil,
|
290
332
|
instrument: nil,
|
333
|
+
instrumenter: config[:instrumenter],
|
291
334
|
logger: config[:logger],
|
292
335
|
log_lock_try: config[:log_lock_try],
|
336
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
337
|
+
log_sampling_percent: config[:log_sampling_percent],
|
338
|
+
log_sampler: config[:log_sampler],
|
339
|
+
instr_sampling_enabled: config[:instr_sampling_enabled],
|
340
|
+
instr_sampling_percent: config[:instr_sampling_percent],
|
341
|
+
instr_sampler: config[:instr_sampler],
|
293
342
|
&block
|
294
343
|
)
|
295
344
|
```
|
@@ -322,6 +371,9 @@ def lock(
|
|
322
371
|
- See RedisQueuedLocks::Instrument::ActiveSupport for example;
|
323
372
|
- See [Instrumentation](#instrumentation) section of docs;
|
324
373
|
- pre-configured in `config[:isntrumenter]` with void notifier (`RedisQueuedLocks::Instrumenter::VoidNotifier`);
|
374
|
+
- `instrument` - (optional) `[NilClass,Any]`
|
375
|
+
- Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
376
|
+
- `nil` by default (means "no custom instrumentation data");
|
325
377
|
- `raise_errors` - (optional) `[Boolean]`
|
326
378
|
- Raise errors on library-related limits (such as timeout or retry count limit) and on lock conflicts (such as same-process dead locks);
|
327
379
|
- `false` by default;
|
@@ -353,9 +405,6 @@ def lock(
|
|
353
405
|
- A custom metadata wich will be passed to the lock data in addition to the existing data;
|
354
406
|
- Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`);
|
355
407
|
- `nil` by default (means "no metadata");
|
356
|
-
- `instrument` - (optional) `[NilClass,Any]`
|
357
|
-
- Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
358
|
-
- `nil` by default (means "no custom instrumentation data");
|
359
408
|
- `logger` - (optional) `[::Logger,#debug]`
|
360
409
|
- Logger object used for loggin internal mutation oeprations and opertioan results / process progress;
|
361
410
|
- pre-configured in `config[:logger]` with void logger `RedisQueuedLocks::Logging::VoidLogger`;
|
@@ -363,6 +412,42 @@ def lock(
|
|
363
412
|
- should be logged the each try of lock acquiring (a lot of logs can be generated depending on your retry configurations);
|
364
413
|
- pre-configured in `config[:log_lock_try]`;
|
365
414
|
- `false` by default;
|
415
|
+
- `log_sampling_enabled` - (optional) `[Boolean]`
|
416
|
+
- enables **log sampling**: only the configured percent of RQL cases will be logged;
|
417
|
+
- disabled by default;
|
418
|
+
- works in tandem with `log_sampling_percent` and `log_sampler` options;
|
419
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
420
|
+
- `log_sampling_percent` - (optional) `[Integer]`
|
421
|
+
- the percent of cases that should be logged;
|
422
|
+
- take an effect when `log_sampling_enalbed` is true;
|
423
|
+
- works in tandem with `log_sampling_enabled` and `log_sampler` options;
|
424
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
425
|
+
- `log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
426
|
+
- percent-based log sampler that decides should be RQL case logged or not;
|
427
|
+
- works in tandem with `log_sampling_enabled` and `log_sampling_percent` options;
|
428
|
+
- based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
|
429
|
+
method so the algorithm error is ~(0%..13%);
|
430
|
+
- you can provide your own log sampler with bettter algorithm that should realize
|
431
|
+
`sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);
|
432
|
+
- pre-configured in `config[:log_sampler]`;
|
433
|
+
- `instr_sampling_enabled` - (optional) `[Boolean]`
|
434
|
+
- enables **instrumentaion sampling**: only the configured percent of RQL cases will be instrumented;
|
435
|
+
- disabled by default;
|
436
|
+
- works in tandem with `instr_sampling_percent` and `instr_sampler` options;
|
437
|
+
- pre-configured in `config[:instr_sampling_enabled]`;
|
438
|
+
- `instr_sampling_percent` - (optional) `[Integer]`
|
439
|
+
- the percent of cases that should be instrumented;
|
440
|
+
- take an effect when `instr_sampling_enalbed` is true;
|
441
|
+
- works in tandem with `instr_sampling_enabled` and `instr_sampler` options;
|
442
|
+
- pre-configured in `config[:instr_sampling_percent]`;
|
443
|
+
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
444
|
+
- percent-based log sampler that decides should be RQL case instrumented or not;
|
445
|
+
- works in tandem with `instr_sampling_enabled` and `instr_sampling_percent` options;
|
446
|
+
- based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
|
447
|
+
method so the algorithm error is ~(0%..13%);
|
448
|
+
- you can provide your own log sampler with bettter algorithm that should realize
|
449
|
+
`sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Instrument::Sampler` for example);
|
450
|
+
- pre-configured in `config[:instr_sampler]`;
|
366
451
|
- `block` - (optional) `[Block]`
|
367
452
|
- A block of code that should be executed after the successfully acquired lock.
|
368
453
|
- If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
|
@@ -516,7 +601,7 @@ rql.lock("my_lock", queue_ttl: 5, timeout: 10_000, retry_count: nil)
|
|
516
601
|
|
517
602
|
# lock queue: =>
|
518
603
|
[
|
519
|
-
"rql:acq:123/456/567/676/374dd74324",
|
604
|
+
"rql:acq:123/456/567/676/374dd74324",
|
520
605
|
"rql:acq:123/456/567/677/374dd74322", # <- long living lock
|
521
606
|
"rql:acq:123/456/567/679/374dd74321",
|
522
607
|
"rql:acq:123/456/567/683/374dd74322", # <== we are here
|
@@ -571,7 +656,14 @@ def lock!(
|
|
571
656
|
logger: config[:logger],
|
572
657
|
log_lock_try: config[:log_lock_try],
|
573
658
|
instrument: nil,
|
659
|
+
instrumenter: config[:instrumenter],
|
574
660
|
conflict_strategy: config[:default_conflict_strategy],
|
661
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
662
|
+
log_sampling_percent: config[:log_sampling_percent],
|
663
|
+
log_sampler: config[:log_sampler],
|
664
|
+
instr_sampling_enabled: config[:instr_sampling_enabled],
|
665
|
+
instr_sampling_percent: config[:instr_sampling_percent],
|
666
|
+
instr_sampler: config[:instr_sampler],
|
575
667
|
&block
|
576
668
|
)
|
577
669
|
```
|
@@ -743,6 +835,24 @@ rql.queued?("your_lock_name") # => true/false
|
|
743
835
|
- `:instrument` - (optional) `[NilClass,Any]`;
|
744
836
|
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
745
837
|
- `nil` by default (no additional data);
|
838
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
839
|
+
- enables **log sampling**;
|
840
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
841
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
842
|
+
- **log sampling**:the percent of cases that should be logged;
|
843
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
844
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
845
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
846
|
+
- pre-configured in `config[:log_sampler]`;
|
847
|
+
- `:instr_sampling_enabled` - (optional) `[Boolean]`
|
848
|
+
- enables **instrumentaion sampling**;
|
849
|
+
- pre-configured in `config[:instr_sampling_enabled]`;
|
850
|
+
- `instr_sampling_percent` - (optional) `[Integer]`
|
851
|
+
- the percent of cases that should be instrumented;
|
852
|
+
- pre-configured in `config[:instr_sampling_percent]`;
|
853
|
+
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
854
|
+
- percent-based log sampler that decides should be RQL case instrumented or not;
|
855
|
+
- pre-configured in `config[:instr_sampler]`;
|
746
856
|
- if you try to unlock non-existent lock you will receive `ok: true` result with operation timings
|
747
857
|
and `:nothing_to_release` result factor inside;
|
748
858
|
|
@@ -793,6 +903,24 @@ rql.unlock("your_lock_name")
|
|
793
903
|
- pre-configured value in `config[:isntrumenter]`;
|
794
904
|
- `:instrument` - (optional) `[NilClass,Any]`
|
795
905
|
- custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
|
906
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
907
|
+
- enables **log sampling**;
|
908
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
909
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
910
|
+
- **log sampling**:the percent of cases that should be logged;
|
911
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
912
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
913
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
914
|
+
- pre-configured in `config[:log_sampler]`;
|
915
|
+
- `:instr_sampling_enabled` - (optional) `[Boolean]`
|
916
|
+
- enables **instrumentaion sampling**;
|
917
|
+
- pre-configured in `config[:instr_sampling_enabled]`;
|
918
|
+
- `instr_sampling_percent` - (optional) `[Integer]`
|
919
|
+
- the percent of cases that should be instrumented;
|
920
|
+
- pre-configured in `config[:instr_sampling_percent]`;
|
921
|
+
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
922
|
+
- percent-based log sampler that decides should be RQL case instrumented or not;
|
923
|
+
- pre-configured in `config[:instr_sampler]`;
|
796
924
|
- returns:
|
797
925
|
- `[Hash<Symbol,Numeric>]` - Format: `{ ok: true, result: Hash<Symbol,Numeric> }`;
|
798
926
|
- result data:
|
@@ -825,9 +953,33 @@ rql.clear_locks
|
|
825
953
|
- the lock name which ttl should be extended;
|
826
954
|
- `milliseconds` - (required) `[Integer]`
|
827
955
|
- how many milliseconds should be added to the lock's TTL;
|
956
|
+
- `:instrumenter` - (optional) `[#notify]`
|
957
|
+
- custom instrumenter object;
|
958
|
+
- pre-configured in `config[:instrumetner]`;
|
959
|
+
- `:instrument` - (optional) `[NilClass,Any]`;
|
960
|
+
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
961
|
+
- `nil` by default (no additional data);
|
828
962
|
- `:logger` - (optional) `[::Logger,#debug]`
|
829
963
|
- custom logger object;
|
830
964
|
- pre-configured in `config[:logger]`;
|
965
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
966
|
+
- enables **log sampling**;
|
967
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
968
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
969
|
+
- **log sampling**:the percent of cases that should be logged;
|
970
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
971
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
972
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
973
|
+
- pre-configured in `config[:log_sampler]`;
|
974
|
+
- `:instr_sampling_enabled` - (optional) `[Boolean]`
|
975
|
+
- enables **instrumentaion sampling**;
|
976
|
+
- pre-configured in `config[:instr_sampling_enabled]`;
|
977
|
+
- `instr_sampling_percent` - (optional) `[Integer]`
|
978
|
+
- the percent of cases that should be instrumented;
|
979
|
+
- pre-configured in `config[:instr_sampling_percent]`;
|
980
|
+
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
981
|
+
- percent-based log sampler that decides should be RQL case instrumented or not;
|
982
|
+
- pre-configured in `config[:instr_sampler]`;
|
831
983
|
- returns `{ ok: true, result: :ttl_extended }` when ttl is extended;
|
832
984
|
- returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
|
833
985
|
some steps of invocation (see **Important** section below);
|
@@ -1056,6 +1208,24 @@ Accepts:
|
|
1056
1208
|
- `:instrument` - (optional) `[NilClass,Any]`
|
1057
1209
|
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
1058
1210
|
- `nil` by default (no additional data);
|
1211
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
1212
|
+
- enables **log sampling**;
|
1213
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
1214
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
1215
|
+
- **log sampling**:the percent of cases that should be logged;
|
1216
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
1217
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
1218
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
1219
|
+
- pre-configured in `config[:log_sampler]`;
|
1220
|
+
- `:instr_sampling_enabled` - (optional) `[Boolean]`
|
1221
|
+
- enables **instrumentaion sampling**;
|
1222
|
+
- pre-configured in `config[:instr_sampling_enabled]`;
|
1223
|
+
- `instr_sampling_percent` - (optional) `[Integer]`
|
1224
|
+
- the percent of cases that should be instrumented;
|
1225
|
+
- pre-configured in `config[:instr_sampling_percent]`;
|
1226
|
+
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
1227
|
+
- percent-based log sampler that decides should be RQL case instrumented or not;
|
1228
|
+
- pre-configured in `config[:instr_sampler]`;
|
1059
1229
|
|
1060
1230
|
Returns: `{ ok: true, processed_queues: Set<String> }` returns the list of processed lock queues;
|
1061
1231
|
|
@@ -1087,7 +1257,7 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
|
|
1087
1257
|
- by default behavior (`:wait_for_lock`) your lock obtaining process will work in a classic way (limits, retries, etc);
|
1088
1258
|
- `:work_through`, `:extendable_work_through` works with limits too (timeouts, delays, etc), but the decision of
|
1089
1259
|
"is your lock are obtained or not" is made as you work with **reentrant locks** (your process continues to use the lock without/with
|
1090
|
-
lock's TTL accordingly);
|
1260
|
+
lock's TTL extension accordingly);
|
1091
1261
|
- for current implementation details check:
|
1092
1262
|
- [Configuration](#configuration) documentation: see `config.default_conflict_strategy` config docs;
|
1093
1263
|
- [#lock](#lock---obtain-a-lock) method documentation: see `conflict_strategy` attribute docs and the method result data;
|
@@ -1264,8 +1434,6 @@ Detalized event semantics and payload structure:
|
|
1264
1434
|
- support for `Dragonfly` database backend (https://github.com/dragonflydb/dragonfly) (https://www.dragonflydb.io/);
|
1265
1435
|
- **Minor**:
|
1266
1436
|
- Semantic error objects for unexpected Redis errors;
|
1267
|
-
- change all `::Process.clock_gettime(::Process::CLOCK_MONOTONIC)` milliseconds-related invocations to
|
1268
|
-
`::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)` in order to reduce time-related math operations count;
|
1269
1437
|
- **Experimental feature**: (non-`timed` locks): per-ruby-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
|
1270
1438
|
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
1271
1439
|
whose ttl may expire before the block execution completes). It makes sense for non-`timed` locks *only*;
|
@@ -28,11 +28,13 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
28
28
|
# @param fail_fast [Boolean]
|
29
29
|
# @param conflict_strategy [Symbol]
|
30
30
|
# @param meta [NilClass,Hash<String|Symbol,Any>]
|
31
|
+
# @param log_sampled [Boolean]
|
32
|
+
# @param instr_sampled [Boolean]
|
31
33
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol|Hash<Symbol,Any> }
|
32
34
|
#
|
33
35
|
# @api private
|
34
36
|
# @since 1.0.0
|
35
|
-
# @version 1.
|
37
|
+
# @version 1.6.0
|
36
38
|
# rubocop:disable Metrics/MethodLength
|
37
39
|
def try_to_lock(
|
38
40
|
redis,
|
@@ -46,14 +48,16 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
46
48
|
queue_ttl,
|
47
49
|
fail_fast,
|
48
50
|
conflict_strategy,
|
49
|
-
meta
|
51
|
+
meta,
|
52
|
+
log_sampled,
|
53
|
+
instr_sampled
|
50
54
|
)
|
51
55
|
# Step X: intermediate invocation results
|
52
56
|
inter_result = nil
|
53
57
|
timestamp = nil
|
54
58
|
spc_processed_timestamp = nil
|
55
59
|
|
56
|
-
if log_lock_try
|
60
|
+
if log_sampled && log_lock_try
|
57
61
|
run_non_critical do
|
58
62
|
logger.debug do
|
59
63
|
"[redis_queued_locks.try_lock.start] " \
|
@@ -66,7 +70,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
66
70
|
|
67
71
|
# Step X: start to work with lock acquiring
|
68
72
|
result = redis.with do |rconn|
|
69
|
-
if log_lock_try
|
73
|
+
if log_sampled && log_lock_try
|
70
74
|
run_non_critical do
|
71
75
|
logger.debug do
|
72
76
|
"[redis_queued_locks.try_lock.rconn_fetched] " \
|
@@ -88,7 +92,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
88
92
|
|
89
93
|
# SP-Conflict Step X1: calculate the current deadlock status
|
90
94
|
if current_lock_obtainer != nil && acquier_id == current_lock_obtainer
|
91
|
-
if log_lock_try
|
95
|
+
if log_sampled && log_lock_try
|
92
96
|
run_non_critical do
|
93
97
|
logger.debug do
|
94
98
|
"[redis_queued_locks.try_lock.same_process_conflict_detected] " \
|
@@ -124,7 +128,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
124
128
|
end
|
125
129
|
# rubocop:enable Lint/DuplicateBranch
|
126
130
|
|
127
|
-
if log_lock_try
|
131
|
+
if log_sampled && log_lock_try
|
128
132
|
run_non_critical do
|
129
133
|
logger.debug do
|
130
134
|
"[redis_queued_locks.try_lock.same_process_conflict_analyzed] " \
|
@@ -172,7 +176,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
172
176
|
)
|
173
177
|
inter_result = :extendable_conflict_work_through
|
174
178
|
|
175
|
-
if log_lock_try
|
179
|
+
if log_sampled && log_lock_try
|
176
180
|
run_non_critical do
|
177
181
|
logger.debug do
|
178
182
|
"[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through] " \
|
@@ -205,7 +209,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
205
209
|
'l_spc_ts', (spc_processed_timestamp = Time.now.to_f)
|
206
210
|
)
|
207
211
|
|
208
|
-
if log_lock_try
|
212
|
+
if log_sampled && log_lock_try
|
209
213
|
run_non_critical do
|
210
214
|
logger.debug do
|
211
215
|
"[redis_queued_locks.try_lock.reentrant_lock__work_through] " \
|
@@ -222,7 +226,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
222
226
|
inter_result = :conflict_dead_lock
|
223
227
|
spc_processed_timestamp = Time.now.to_f
|
224
228
|
|
225
|
-
if log_lock_try
|
229
|
+
if log_sampled && log_lock_try
|
226
230
|
logger.debug do
|
227
231
|
"[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock] " \
|
228
232
|
"lock_key => '#{lock_key}' " \
|
@@ -242,7 +246,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
242
246
|
# Step 1: add an acquier to the lock acquirement queue
|
243
247
|
rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
244
248
|
|
245
|
-
if log_lock_try
|
249
|
+
if log_sampled && log_lock_try
|
246
250
|
run_non_critical do
|
247
251
|
logger.debug do
|
248
252
|
"[redis_queued_locks.try_lock.acq_added_to_queue] " \
|
@@ -261,7 +265,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
261
265
|
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
262
266
|
)
|
263
267
|
|
264
|
-
if log_lock_try
|
268
|
+
if log_sampled && log_lock_try
|
265
269
|
run_non_critical do
|
266
270
|
logger.debug do
|
267
271
|
"[redis_queued_locks.try_lock.remove_expired_acqs] " \
|
@@ -275,7 +279,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
275
279
|
# Step 3: get the actual acquier waiting in the queue
|
276
280
|
waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
|
277
281
|
|
278
|
-
if log_lock_try
|
282
|
+
if log_sampled && log_lock_try
|
279
283
|
run_non_critical do
|
280
284
|
logger.debug do
|
281
285
|
"[redis_queued_locks.try_lock.get_first_from_queue] " \
|
@@ -290,7 +294,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
290
294
|
# Step PRE-4.x: check if the request time limit is reached
|
291
295
|
# (when the current try self-removes itself from queue (queue ttl has come))
|
292
296
|
if waiting_acquier == nil
|
293
|
-
if log_lock_try
|
297
|
+
if log_sampled && log_lock_try
|
294
298
|
run_non_critical do
|
295
299
|
logger.debug do
|
296
300
|
"[redis_queued_locks.try_lock.exit__queue_ttl_reached] " \
|
@@ -306,7 +310,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
306
310
|
elsif waiting_acquier != acquier_id
|
307
311
|
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
308
312
|
|
309
|
-
if log_lock_try
|
313
|
+
if log_sampled && log_lock_try
|
310
314
|
run_non_critical do
|
311
315
|
logger.debug do
|
312
316
|
"[redis_queued_locks.try_lock.exit__no_first] " \
|
@@ -329,7 +333,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
329
333
|
if locked_by_acquier
|
330
334
|
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
331
335
|
|
332
|
-
if log_lock_try
|
336
|
+
if log_sampled && log_lock_try
|
333
337
|
run_non_critical do
|
334
338
|
logger.debug do
|
335
339
|
"[redis_queued_locks.try_lock.exit__lock_still_obtained] " \
|
@@ -363,7 +367,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
363
367
|
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
364
368
|
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
365
369
|
|
366
|
-
if log_lock_try
|
370
|
+
if log_sampled && log_lock_try
|
367
371
|
run_non_critical do
|
368
372
|
logger.debug do
|
369
373
|
"[redis_queued_locks.try_lock.obtain__free_to_acquire] " \
|
@@ -482,12 +486,23 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
482
486
|
# @param lock_key_queue [String]
|
483
487
|
# @param queue_ttl [Integer]
|
484
488
|
# @param acquier_id [String]
|
489
|
+
# @param log_sampled [Boolean]
|
490
|
+
# @param instr_sampled [Boolean]
|
485
491
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
486
492
|
#
|
487
493
|
# @api private
|
488
494
|
# @since 1.0.0
|
489
|
-
# @version 1.
|
490
|
-
def dequeue_from_lock_queue(
|
495
|
+
# @version 1.6.0
|
496
|
+
def dequeue_from_lock_queue(
|
497
|
+
redis,
|
498
|
+
logger,
|
499
|
+
lock_key,
|
500
|
+
lock_key_queue,
|
501
|
+
queue_ttl,
|
502
|
+
acquier_id,
|
503
|
+
log_sampled,
|
504
|
+
instr_sampled
|
505
|
+
)
|
491
506
|
result = redis.call('ZREM', lock_key_queue, acquier_id)
|
492
507
|
|
493
508
|
run_non_critical do
|
@@ -497,7 +512,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
497
512
|
"queue_ttl => '#{queue_ttl}' " \
|
498
513
|
"acq_id => '#{acquier_id}'"
|
499
514
|
end
|
500
|
-
end
|
515
|
+
end if log_sampled
|
501
516
|
|
502
517
|
RedisQueuedLocks::Data[ok: true, result: result]
|
503
518
|
end
|
@@ -24,6 +24,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
24
24
|
# @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
|
25
25
|
# @param queue_ttl [Integer] Lock request lifetime.
|
26
26
|
# @param block [Block] Custom logic that should be invoked unter the obtained lock.
|
27
|
+
# @param log_sampled [Boolean] Should the logic be logged or not.
|
28
|
+
# @param instr_sampled [Boolean] Should the logic be instrumented or not.
|
27
29
|
# @param should_expire [Boolean] Should the lock be expired after the block invocation.
|
28
30
|
# @param should_decrease [Boolean]
|
29
31
|
# - Should decrease the lock TTL after the lock invocation;
|
@@ -32,6 +34,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
32
34
|
#
|
33
35
|
# @api private
|
34
36
|
# @since 1.3.0
|
37
|
+
# @version 1.6.0
|
35
38
|
# rubocop:disable Metrics/MethodLength
|
36
39
|
def yield_expire(
|
37
40
|
redis,
|
@@ -42,6 +45,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
42
45
|
ttl_shift,
|
43
46
|
ttl,
|
44
47
|
queue_ttl,
|
48
|
+
log_sampled,
|
49
|
+
instr_sampled,
|
45
50
|
should_expire,
|
46
51
|
should_decrease,
|
47
52
|
&block
|
@@ -69,12 +74,14 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
69
74
|
"queue_ttl => #{queue_ttl} " \
|
70
75
|
"acq_id => '#{acquier_id}'"
|
71
76
|
end
|
72
|
-
end
|
77
|
+
end if log_sampled
|
78
|
+
|
73
79
|
redis.call('EXPIRE', lock_key, '0')
|
74
80
|
elsif should_decrease
|
75
81
|
finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
76
82
|
spent_time = (finish_time - initial_time)
|
77
83
|
decreased_ttl = ttl - spent_time - RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
|
84
|
+
|
78
85
|
if decreased_ttl > 0
|
79
86
|
run_non_critical do
|
80
87
|
logger.debug do
|
@@ -84,7 +91,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
84
91
|
"queue_ttl => #{queue_ttl} " \
|
85
92
|
"acq_id => '#{acquier_id}' " \
|
86
93
|
end
|
87
|
-
end
|
94
|
+
end if log_sampled
|
95
|
+
|
88
96
|
# NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
|
89
97
|
redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
|
90
98
|
# TODO: upload scripts to the redis
|