redis_queued_locks 1.4.0 → 1.5.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 +4 -0
- data/README.md +86 -6
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +30 -19
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +5 -2
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +34 -8
- data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +14 -1
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +13 -1
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +20 -3
- data/lib/redis_queued_locks/acquier/release_lock.rb +24 -2
- data/lib/redis_queued_locks/client.rb +105 -13
- data/lib/redis_queued_locks/logging/sampler.rb +21 -0
- data/lib/redis_queued_locks/logging.rb +46 -0
- data/lib/redis_queued_locks/utilities.rb +1 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5be49c89b572553d2de7af37b8bbc38439bcefc5b2b33cd4d8e542fb3c9dd3e3
|
4
|
+
data.tar.gz: 8a3a5509e82bd45ff9576677e2c2017c670fdd6a825da8ad0281f0fc1b6e7e69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f151272fadc4be25fd1344f9b29a5b263bc8ab63d17e8d09c55178232e4e57acac456c5987c0040172643e0a479976f9c12d0ff81549ba223ed742adb0dfcd1
|
7
|
+
data.tar.gz: c862d4040d1dbe421a7a8e2a10c8e24c8e22cd74c65d64746cb74ad96c2d31b3c1ec7a55f22cc0149ea0a65014f4d891d8a13f4336b07d6f56130673203ff384
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.5.0] - 2024-05-23
|
4
|
+
### Added
|
5
|
+
- New Feature: **Log sampling** - configurable log sampling based on `weight` algorithm (where the weight is a percentage);
|
6
|
+
|
3
7
|
## [1.4.0] - 2024-05-13
|
4
8
|
### Added
|
5
9
|
- `#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,27 @@ 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
|
235
256
|
end
|
236
257
|
```
|
237
258
|
|
@@ -290,6 +311,9 @@ def lock(
|
|
290
311
|
instrument: nil,
|
291
312
|
logger: config[:logger],
|
292
313
|
log_lock_try: config[:log_lock_try],
|
314
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
315
|
+
log_sampling_percent: config[:log_sampling_percent],
|
316
|
+
log_sampler: config[:log_sampler],
|
293
317
|
&block
|
294
318
|
)
|
295
319
|
```
|
@@ -363,6 +387,24 @@ def lock(
|
|
363
387
|
- should be logged the each try of lock acquiring (a lot of logs can be generated depending on your retry configurations);
|
364
388
|
- pre-configured in `config[:log_lock_try]`;
|
365
389
|
- `false` by default;
|
390
|
+
- `log_sampling_enabled` - (optional) `[Boolean]`
|
391
|
+
- enables **log sampling**: only the configured percent of RQL cases will be logged;
|
392
|
+
- disabled by default;
|
393
|
+
- works in tandem with `log_sampling_percent` and `log_sampler` options;
|
394
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
395
|
+
- `log_sampling_percent` - (optional) `[Integer]`
|
396
|
+
- the percent of cases that should be logged;
|
397
|
+
- take an effect when `log_sampling_enalbed` is true;
|
398
|
+
- works in tandem with `log_sampling_enabled` and `log_sampler` options;
|
399
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
400
|
+
- `log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
401
|
+
- percent-based log sampler that decides should be RQL case logged or not;
|
402
|
+
- works in tandem with `log_sampling_enabled` and `log_sampling_percent` options;
|
403
|
+
- based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
|
404
|
+
method so the algorithm error is ~(0%..13%);
|
405
|
+
- you can provide your own log sampler with bettter algorithm that should realize
|
406
|
+
`sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);
|
407
|
+
- pre-configured in `config[:log_sampler]`;
|
366
408
|
- `block` - (optional) `[Block]`
|
367
409
|
- A block of code that should be executed after the successfully acquired lock.
|
368
410
|
- If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
|
@@ -572,6 +614,9 @@ def lock!(
|
|
572
614
|
log_lock_try: config[:log_lock_try],
|
573
615
|
instrument: nil,
|
574
616
|
conflict_strategy: config[:default_conflict_strategy],
|
617
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
618
|
+
log_sampling_percent: config[:log_sampling_percent],
|
619
|
+
log_sampler: config[:log_sampler],
|
575
620
|
&block
|
576
621
|
)
|
577
622
|
```
|
@@ -743,6 +788,15 @@ rql.queued?("your_lock_name") # => true/false
|
|
743
788
|
- `:instrument` - (optional) `[NilClass,Any]`;
|
744
789
|
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
745
790
|
- `nil` by default (no additional data);
|
791
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
792
|
+
- enables **log sampling**;
|
793
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
794
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
795
|
+
- **log sampling**:the percent of cases that should be logged;
|
796
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
797
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
798
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
799
|
+
- pre-configured in `config[:log_sampler]`;
|
746
800
|
- if you try to unlock non-existent lock you will receive `ok: true` result with operation timings
|
747
801
|
and `:nothing_to_release` result factor inside;
|
748
802
|
|
@@ -793,6 +847,15 @@ rql.unlock("your_lock_name")
|
|
793
847
|
- pre-configured value in `config[:isntrumenter]`;
|
794
848
|
- `:instrument` - (optional) `[NilClass,Any]`
|
795
849
|
- custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
|
850
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
851
|
+
- enables **log sampling**;
|
852
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
853
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
854
|
+
- **log sampling**:the percent of cases that should be logged;
|
855
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
856
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
857
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
858
|
+
- pre-configured in `config[:log_sampler]`;
|
796
859
|
- returns:
|
797
860
|
- `[Hash<Symbol,Numeric>]` - Format: `{ ok: true, result: Hash<Symbol,Numeric> }`;
|
798
861
|
- result data:
|
@@ -828,6 +891,15 @@ rql.clear_locks
|
|
828
891
|
- `:logger` - (optional) `[::Logger,#debug]`
|
829
892
|
- custom logger object;
|
830
893
|
- pre-configured in `config[:logger]`;
|
894
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
895
|
+
- enables **log sampling**;
|
896
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
897
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
898
|
+
- **log sampling**:the percent of cases that should be logged;
|
899
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
900
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
901
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
902
|
+
- pre-configured in `config[:log_sampler]`;
|
831
903
|
- returns `{ ok: true, result: :ttl_extended }` when ttl is extended;
|
832
904
|
- returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
|
833
905
|
some steps of invocation (see **Important** section below);
|
@@ -1056,6 +1128,15 @@ Accepts:
|
|
1056
1128
|
- `:instrument` - (optional) `[NilClass,Any]`
|
1057
1129
|
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
1058
1130
|
- `nil` by default (no additional data);
|
1131
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
1132
|
+
- enables **log sampling**;
|
1133
|
+
- pre-configured in `config[:log_sampling_enabled]`;
|
1134
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
1135
|
+
- **log sampling**:the percent of cases that should be logged;
|
1136
|
+
- pre-configured in `config[:log_sampling_percent]`;
|
1137
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
1138
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
1139
|
+
- pre-configured in `config[:log_sampler]`;
|
1059
1140
|
|
1060
1141
|
Returns: `{ ok: true, processed_queues: Set<String> }` returns the list of processed lock queues;
|
1061
1142
|
|
@@ -1087,7 +1168,7 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
|
|
1087
1168
|
- by default behavior (`:wait_for_lock`) your lock obtaining process will work in a classic way (limits, retries, etc);
|
1088
1169
|
- `:work_through`, `:extendable_work_through` works with limits too (timeouts, delays, etc), but the decision of
|
1089
1170
|
"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);
|
1171
|
+
lock's TTL extension accordingly);
|
1091
1172
|
- for current implementation details check:
|
1092
1173
|
- [Configuration](#configuration) documentation: see `config.default_conflict_strategy` config docs;
|
1093
1174
|
- [#lock](#lock---obtain-a-lock) method documentation: see `conflict_strategy` attribute docs and the method result data;
|
@@ -1254,6 +1335,7 @@ Detalized event semantics and payload structure:
|
|
1254
1335
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
1255
1336
|
|
1256
1337
|
- **Major**:
|
1338
|
+
- precent-based instrumentation sampling (instrument the concrete % of cases via "sampling" strategy);
|
1257
1339
|
- support for Random Access strategy (non-queued behavior);
|
1258
1340
|
- lock request prioritization;
|
1259
1341
|
- **strict redlock algorithm support** (support for many `RedisClient` instances);
|
@@ -1264,8 +1346,6 @@ Detalized event semantics and payload structure:
|
|
1264
1346
|
- support for `Dragonfly` database backend (https://github.com/dragonflydb/dragonfly) (https://www.dragonflydb.io/);
|
1265
1347
|
- **Minor**:
|
1266
1348
|
- 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
1349
|
- **Experimental feature**: (non-`timed` locks): per-ruby-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
|
1270
1350
|
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
1271
1351
|
whose ttl may expire before the block execution completes). It makes sense for non-`timed` locks *only*;
|
@@ -28,11 +28,12 @@ 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]
|
31
32
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol|Hash<Symbol,Any> }
|
32
33
|
#
|
33
34
|
# @api private
|
34
35
|
# @since 1.0.0
|
35
|
-
# @version 1.
|
36
|
+
# @version 1.5.0
|
36
37
|
# rubocop:disable Metrics/MethodLength
|
37
38
|
def try_to_lock(
|
38
39
|
redis,
|
@@ -46,14 +47,15 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
46
47
|
queue_ttl,
|
47
48
|
fail_fast,
|
48
49
|
conflict_strategy,
|
49
|
-
meta
|
50
|
+
meta,
|
51
|
+
log_sampled
|
50
52
|
)
|
51
53
|
# Step X: intermediate invocation results
|
52
54
|
inter_result = nil
|
53
55
|
timestamp = nil
|
54
56
|
spc_processed_timestamp = nil
|
55
57
|
|
56
|
-
if log_lock_try
|
58
|
+
if log_sampled && log_lock_try
|
57
59
|
run_non_critical do
|
58
60
|
logger.debug do
|
59
61
|
"[redis_queued_locks.try_lock.start] " \
|
@@ -66,7 +68,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
66
68
|
|
67
69
|
# Step X: start to work with lock acquiring
|
68
70
|
result = redis.with do |rconn|
|
69
|
-
if log_lock_try
|
71
|
+
if log_sampled && log_lock_try
|
70
72
|
run_non_critical do
|
71
73
|
logger.debug do
|
72
74
|
"[redis_queued_locks.try_lock.rconn_fetched] " \
|
@@ -88,7 +90,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
88
90
|
|
89
91
|
# SP-Conflict Step X1: calculate the current deadlock status
|
90
92
|
if current_lock_obtainer != nil && acquier_id == current_lock_obtainer
|
91
|
-
if log_lock_try
|
93
|
+
if log_sampled && log_lock_try
|
92
94
|
run_non_critical do
|
93
95
|
logger.debug do
|
94
96
|
"[redis_queued_locks.try_lock.same_process_conflict_detected] " \
|
@@ -124,7 +126,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
124
126
|
end
|
125
127
|
# rubocop:enable Lint/DuplicateBranch
|
126
128
|
|
127
|
-
if log_lock_try
|
129
|
+
if log_sampled && log_lock_try
|
128
130
|
run_non_critical do
|
129
131
|
logger.debug do
|
130
132
|
"[redis_queued_locks.try_lock.same_process_conflict_analyzed] " \
|
@@ -172,7 +174,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
172
174
|
)
|
173
175
|
inter_result = :extendable_conflict_work_through
|
174
176
|
|
175
|
-
if log_lock_try
|
177
|
+
if log_sampled && log_lock_try
|
176
178
|
run_non_critical do
|
177
179
|
logger.debug do
|
178
180
|
"[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through] " \
|
@@ -205,7 +207,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
205
207
|
'l_spc_ts', (spc_processed_timestamp = Time.now.to_f)
|
206
208
|
)
|
207
209
|
|
208
|
-
if log_lock_try
|
210
|
+
if log_sampled && log_lock_try
|
209
211
|
run_non_critical do
|
210
212
|
logger.debug do
|
211
213
|
"[redis_queued_locks.try_lock.reentrant_lock__work_through] " \
|
@@ -222,7 +224,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
222
224
|
inter_result = :conflict_dead_lock
|
223
225
|
spc_processed_timestamp = Time.now.to_f
|
224
226
|
|
225
|
-
if log_lock_try
|
227
|
+
if log_sampled && log_lock_try
|
226
228
|
logger.debug do
|
227
229
|
"[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock] " \
|
228
230
|
"lock_key => '#{lock_key}' " \
|
@@ -242,7 +244,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
242
244
|
# Step 1: add an acquier to the lock acquirement queue
|
243
245
|
rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
244
246
|
|
245
|
-
if log_lock_try
|
247
|
+
if log_sampled && log_lock_try
|
246
248
|
run_non_critical do
|
247
249
|
logger.debug do
|
248
250
|
"[redis_queued_locks.try_lock.acq_added_to_queue] " \
|
@@ -261,7 +263,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
261
263
|
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
262
264
|
)
|
263
265
|
|
264
|
-
if log_lock_try
|
266
|
+
if log_sampled && log_lock_try
|
265
267
|
run_non_critical do
|
266
268
|
logger.debug do
|
267
269
|
"[redis_queued_locks.try_lock.remove_expired_acqs] " \
|
@@ -275,7 +277,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
275
277
|
# Step 3: get the actual acquier waiting in the queue
|
276
278
|
waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
|
277
279
|
|
278
|
-
if log_lock_try
|
280
|
+
if log_sampled && log_lock_try
|
279
281
|
run_non_critical do
|
280
282
|
logger.debug do
|
281
283
|
"[redis_queued_locks.try_lock.get_first_from_queue] " \
|
@@ -290,7 +292,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
290
292
|
# Step PRE-4.x: check if the request time limit is reached
|
291
293
|
# (when the current try self-removes itself from queue (queue ttl has come))
|
292
294
|
if waiting_acquier == nil
|
293
|
-
if log_lock_try
|
295
|
+
if log_sampled && log_lock_try
|
294
296
|
run_non_critical do
|
295
297
|
logger.debug do
|
296
298
|
"[redis_queued_locks.try_lock.exit__queue_ttl_reached] " \
|
@@ -306,7 +308,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
306
308
|
elsif waiting_acquier != acquier_id
|
307
309
|
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
308
310
|
|
309
|
-
if log_lock_try
|
311
|
+
if log_sampled && log_lock_try
|
310
312
|
run_non_critical do
|
311
313
|
logger.debug do
|
312
314
|
"[redis_queued_locks.try_lock.exit__no_first] " \
|
@@ -329,7 +331,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
329
331
|
if locked_by_acquier
|
330
332
|
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
331
333
|
|
332
|
-
if log_lock_try
|
334
|
+
if log_sampled && log_lock_try
|
333
335
|
run_non_critical do
|
334
336
|
logger.debug do
|
335
337
|
"[redis_queued_locks.try_lock.exit__lock_still_obtained] " \
|
@@ -363,7 +365,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
363
365
|
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
364
366
|
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
365
367
|
|
366
|
-
if log_lock_try
|
368
|
+
if log_sampled && log_lock_try
|
367
369
|
run_non_critical do
|
368
370
|
logger.debug do
|
369
371
|
"[redis_queued_locks.try_lock.obtain__free_to_acquire] " \
|
@@ -482,12 +484,21 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
482
484
|
# @param lock_key_queue [String]
|
483
485
|
# @param queue_ttl [Integer]
|
484
486
|
# @param acquier_id [String]
|
487
|
+
# @param log_sampled [Boolean]
|
485
488
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
486
489
|
#
|
487
490
|
# @api private
|
488
491
|
# @since 1.0.0
|
489
|
-
# @version 1.
|
490
|
-
def dequeue_from_lock_queue(
|
492
|
+
# @version 1.5.0
|
493
|
+
def dequeue_from_lock_queue(
|
494
|
+
redis,
|
495
|
+
logger,
|
496
|
+
lock_key,
|
497
|
+
lock_key_queue,
|
498
|
+
queue_ttl,
|
499
|
+
acquier_id,
|
500
|
+
log_sampled
|
501
|
+
)
|
491
502
|
result = redis.call('ZREM', lock_key_queue, acquier_id)
|
492
503
|
|
493
504
|
run_non_critical do
|
@@ -497,7 +508,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
497
508
|
"queue_ttl => '#{queue_ttl}' " \
|
498
509
|
"acq_id => '#{acquier_id}'"
|
499
510
|
end
|
500
|
-
end
|
511
|
+
end if log_sampled
|
501
512
|
|
502
513
|
RedisQueuedLocks::Data[ok: true, result: result]
|
503
514
|
end
|
@@ -24,6 +24,7 @@ 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 (is log sample happened?).
|
27
28
|
# @param should_expire [Boolean] Should the lock be expired after the block invocation.
|
28
29
|
# @param should_decrease [Boolean]
|
29
30
|
# - Should decrease the lock TTL after the lock invocation;
|
@@ -32,6 +33,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
32
33
|
#
|
33
34
|
# @api private
|
34
35
|
# @since 1.3.0
|
36
|
+
# @version 1.5.0
|
35
37
|
# rubocop:disable Metrics/MethodLength
|
36
38
|
def yield_expire(
|
37
39
|
redis,
|
@@ -42,6 +44,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
42
44
|
ttl_shift,
|
43
45
|
ttl,
|
44
46
|
queue_ttl,
|
47
|
+
log_sampled,
|
45
48
|
should_expire,
|
46
49
|
should_decrease,
|
47
50
|
&block
|
@@ -69,7 +72,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
69
72
|
"queue_ttl => #{queue_ttl} " \
|
70
73
|
"acq_id => '#{acquier_id}'"
|
71
74
|
end
|
72
|
-
end
|
75
|
+
end if log_sampled
|
73
76
|
redis.call('EXPIRE', lock_key, '0')
|
74
77
|
elsif should_decrease
|
75
78
|
finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
@@ -84,7 +87,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
|
|
84
87
|
"queue_ttl => #{queue_ttl} " \
|
85
88
|
"acq_id => '#{acquier_id}' " \
|
86
89
|
end
|
87
|
-
end
|
90
|
+
end if log_sampled
|
88
91
|
# NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
|
89
92
|
redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
|
90
93
|
# TODO: upload scripts to the redis
|
@@ -86,6 +86,20 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
86
86
|
# - `:extendable_work_through`;
|
87
87
|
# - `:wait_for_lock`;
|
88
88
|
# - `:dead_locking`;
|
89
|
+
# @option log_sampling_enabled [Boolean]
|
90
|
+
# - The percent of cases that should be logged;
|
91
|
+
# - Sampling algorithm is super simple and works via SecureRandom.rand method
|
92
|
+
# on the base of "weight" algorithm;
|
93
|
+
# - You can provide your own sampler via config[:log_sampler] config and :sampler option
|
94
|
+
# (see `RedisQueuedLocks::Logging::Sampler` for examples);
|
95
|
+
# - The spread of guaranteed percent is approximately +13% (rand method spread);
|
96
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
97
|
+
# (when log sampling is enabled);
|
98
|
+
# @option log_sampling_percent [Integer]
|
99
|
+
# - The percent of cases that should be logged;
|
100
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
101
|
+
# (when log sampling is enabled);
|
102
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
89
103
|
# @param [Block]
|
90
104
|
# A block of code that should be executed after the successfully acquired lock.
|
91
105
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -118,6 +132,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
118
132
|
logger:,
|
119
133
|
log_lock_try:,
|
120
134
|
conflict_strategy:,
|
135
|
+
log_sampling_enabled:,
|
136
|
+
log_sampling_percent:,
|
137
|
+
log_sampler:,
|
121
138
|
&block
|
122
139
|
)
|
123
140
|
# Step 0: Prevent argument type incompatabilities
|
@@ -165,6 +182,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
165
182
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
166
183
|
acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
|
167
184
|
|
185
|
+
log_sampled = RedisQueuedLocks::Logging.should_log?(
|
186
|
+
log_sampling_enabled,
|
187
|
+
log_sampling_percent,
|
188
|
+
log_sampler
|
189
|
+
)
|
190
|
+
|
168
191
|
# Step X: intermediate result observer
|
169
192
|
acq_process = {
|
170
193
|
lock_info: {},
|
@@ -183,7 +206,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
183
206
|
lock_key,
|
184
207
|
lock_key_queue,
|
185
208
|
queue_ttl,
|
186
|
-
acquier_id
|
209
|
+
acquier_id,
|
210
|
+
log_sampled
|
187
211
|
)
|
188
212
|
end
|
189
213
|
|
@@ -194,7 +218,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
194
218
|
"queue_ttl => #{queue_ttl} " \
|
195
219
|
"acq_id => '#{acquier_id}'"
|
196
220
|
end
|
197
|
-
end
|
221
|
+
end if log_sampled
|
198
222
|
|
199
223
|
# Step 2: try to lock with timeout
|
200
224
|
with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
|
@@ -209,7 +233,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
209
233
|
"queue_ttl => #{queue_ttl} " \
|
210
234
|
"acq_id => '{#{acquier_id}'"
|
211
235
|
end
|
212
|
-
end
|
236
|
+
end if log_sampled
|
213
237
|
|
214
238
|
# Step 2.X: check the actual score: is it in queue ttl limit or not?
|
215
239
|
if RedisQueuedLocks::Resource.dead_score_reached?(acquier_position, queue_ttl)
|
@@ -223,7 +247,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
223
247
|
"queue_ttl => #{queue_ttl} " \
|
224
248
|
"acq_id => '#{acquier_id}'"
|
225
249
|
end
|
226
|
-
end
|
250
|
+
end if log_sampled
|
227
251
|
end
|
228
252
|
|
229
253
|
try_to_lock(
|
@@ -238,7 +262,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
238
262
|
queue_ttl,
|
239
263
|
fail_fast,
|
240
264
|
conflict_strategy,
|
241
|
-
meta
|
265
|
+
meta,
|
266
|
+
log_sampled
|
242
267
|
) => { ok:, result: }
|
243
268
|
|
244
269
|
acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
@@ -261,7 +286,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
261
286
|
"acq_id => '#{acquier_id}' " \
|
262
287
|
"acq_time => #{acq_time} (ms)"
|
263
288
|
end
|
264
|
-
end
|
289
|
+
end if log_sampled
|
265
290
|
|
266
291
|
run_non_critical do
|
267
292
|
instrumenter.notify('redis_queued_locks.extendable_reentrant_lock_obtained', {
|
@@ -283,7 +308,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
283
308
|
"acq_id => '#{acquier_id}' " \
|
284
309
|
"acq_time => #{acq_time} (ms)"
|
285
310
|
end
|
286
|
-
end
|
311
|
+
end if log_sampled
|
287
312
|
|
288
313
|
run_non_critical do
|
289
314
|
instrumenter.notify('redis_queued_locks.reentrant_lock_obtained', {
|
@@ -306,7 +331,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
306
331
|
"acq_id => '#{acquier_id}' " \
|
307
332
|
"acq_time => #{acq_time} (ms)"
|
308
333
|
end
|
309
|
-
end
|
334
|
+
end if log_sampled
|
310
335
|
|
311
336
|
# Step X (instrumentation): lock obtained
|
312
337
|
run_non_critical do
|
@@ -426,6 +451,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
426
451
|
ttl_shift,
|
427
452
|
ttl,
|
428
453
|
queue_ttl,
|
454
|
+
log_sampled,
|
429
455
|
should_expire, # NOTE: should expire the lock after the block execution
|
430
456
|
should_decrease, # NOTE: should decrease the lock ttl in reentrant locks?
|
431
457
|
&block
|
@@ -10,11 +10,24 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
|
|
10
10
|
# @param logger [::Logger,#debug]
|
11
11
|
# @param instrumenter [#notify]
|
12
12
|
# @param instrument [NilClass,Any]
|
13
|
+
# @param log_sampling_enabled [Boolean]
|
14
|
+
# @param log_sampling_percent [Integer]
|
15
|
+
# @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
13
16
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
|
14
17
|
#
|
15
18
|
# @api private
|
16
19
|
# @since 1.0.0
|
17
|
-
def clear_dead_requests(
|
20
|
+
def clear_dead_requests(
|
21
|
+
redis_client,
|
22
|
+
scan_size,
|
23
|
+
dead_ttl,
|
24
|
+
logger,
|
25
|
+
instrumenter,
|
26
|
+
instrument,
|
27
|
+
log_sampling_enabled,
|
28
|
+
log_sampling_percent,
|
29
|
+
log_sampler
|
30
|
+
)
|
18
31
|
dead_score = RedisQueuedLocks::Resource.acquier_dead_score(dead_ttl / 1_000.0)
|
19
32
|
|
20
33
|
result = Set.new.tap do |processed_queues|
|
@@ -17,11 +17,23 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
|
|
17
17
|
# @param lock_name [String]
|
18
18
|
# @param milliseconds [Integer]
|
19
19
|
# @param logger [::Logger,#debug]
|
20
|
+
# @param log_sampling_enabled [Boolean]
|
21
|
+
# @param log_sampling_percent [Integer]
|
22
|
+
# @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
20
23
|
# @return [Hash<Symbol,Boolean|Symbol>]
|
21
24
|
#
|
22
25
|
# @api private
|
23
26
|
# @since 1.0.0
|
24
|
-
|
27
|
+
# @version 1.5.0
|
28
|
+
def extend_lock_ttl(
|
29
|
+
redis_client,
|
30
|
+
lock_name,
|
31
|
+
milliseconds,
|
32
|
+
logger,
|
33
|
+
log_sampling_enabled,
|
34
|
+
log_sampling_percent,
|
35
|
+
log_sampler
|
36
|
+
)
|
25
37
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
26
38
|
|
27
39
|
# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
|
@@ -20,16 +20,33 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
20
20
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
21
21
|
# @param isntrumenter [#notify]
|
22
22
|
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
23
|
-
# @
|
23
|
+
# @param instrument [NilClass,Any]
|
24
24
|
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
25
25
|
# with :instrument key;
|
26
|
+
# @param log_sampling_enabled [Boolean]
|
27
|
+
# - Enables <log sampling>: only the configured percent of cases will be logged;
|
28
|
+
# - Works in tandem with <log_samplng_percent> option;
|
29
|
+
# @param log_sampling_percent [Integer]
|
30
|
+
# - The percent of cases that should be logged;
|
31
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
32
|
+
# (when log sampling is enabled);
|
33
|
+
# @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
26
34
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
|
27
35
|
# Format: { ok: true, result: Hash<Symbol,Numeric> }
|
28
36
|
#
|
29
37
|
# @api private
|
30
38
|
# @since 1.0.0
|
31
|
-
# @version 1.
|
32
|
-
def release_all_locks(
|
39
|
+
# @version 1.5.0
|
40
|
+
def release_all_locks(
|
41
|
+
redis,
|
42
|
+
batch_size,
|
43
|
+
logger,
|
44
|
+
instrumenter,
|
45
|
+
instrument,
|
46
|
+
log_sampling_enabled,
|
47
|
+
log_sampling_percent,
|
48
|
+
log_sampler
|
49
|
+
)
|
33
50
|
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
34
51
|
fully_release_all_locks(redis, batch_size) => { ok:, result: }
|
35
52
|
time_at = Time.now.to_f
|
@@ -23,13 +23,35 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
23
23
|
# @param logger [::Logger,#debug]
|
24
24
|
# - Logger object used from `configuration` layer (see config[:logger]);
|
25
25
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
26
|
+
# @param log_sampling_enabled [Boolean]
|
27
|
+
# - The percent of cases that should be logged;
|
28
|
+
# - Sampling algorithm is super simple and works via SecureRandom.rand method
|
29
|
+
# on the base of "weight" algorithm;
|
30
|
+
# - You can provide your own sampler via config[:log_sampler] config and :sampler option
|
31
|
+
# (see `RedisQueuedLocks::Logging::Sampler` for examples);
|
32
|
+
# - The spread of guaranteed percent is approximately +13% (rand method spread);
|
33
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
34
|
+
# (when log sampling is enabled);
|
35
|
+
# @param log_sampling_percent [Integer]
|
36
|
+
# - The percent of cases that should be logged;
|
37
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
38
|
+
# (when log sampling is enabled);
|
39
|
+
# @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
26
40
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean<Hash<Symbol,Numeric|String|Symbol>>]
|
27
41
|
# Format: { ok: true/false, result: Hash<Symbol,Numeric|String|Symbol> }
|
28
42
|
#
|
29
43
|
# @api private
|
30
44
|
# @since 1.0.0
|
31
|
-
# @version 1.
|
32
|
-
def release_lock(
|
45
|
+
# @version 1.5.0
|
46
|
+
def release_lock(
|
47
|
+
redis,
|
48
|
+
lock_name,
|
49
|
+
instrumenter,
|
50
|
+
logger,
|
51
|
+
log_sampling_enabled,
|
52
|
+
log_sampling_percent,
|
53
|
+
log_sampler
|
54
|
+
)
|
33
55
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
34
56
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
35
57
|
|
@@ -23,6 +23,9 @@ class RedisQueuedLocks::Client
|
|
23
23
|
setting :dead_request_ttl, (1 * 24 * 60 * 60 * 1000) # NOTE: 1 day in milliseconds
|
24
24
|
setting :is_timed_by_default, false
|
25
25
|
setting :default_conflict_strategy, :wait_for_lock
|
26
|
+
setting :log_sampling_enabled, false
|
27
|
+
setting :log_sampling_percent, 15
|
28
|
+
setting :log_sampler, RedisQueuedLocks::Logging::Sampler
|
26
29
|
|
27
30
|
validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
|
28
31
|
validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
|
@@ -37,6 +40,9 @@ class RedisQueuedLocks::Client
|
|
37
40
|
validate('log_lock_try', :boolean)
|
38
41
|
validate('dead_request_ttl') { |val| val.is_a?(::Integer) && val > 0 }
|
39
42
|
validate('is_timed_by_default', :boolean)
|
43
|
+
validate('log_sampler') { |val| RedisQueuedLocks::Logging.valid_sampler?(val) }
|
44
|
+
validate('log_sampling_enabled', :boolean)
|
45
|
+
validate('log_sampling_percent') { |val| val.is_a?(::Integer) && val >= 0 && val <= 100 }
|
40
46
|
validate('default_conflict_strategy') do |val|
|
41
47
|
# rubocop:disable Layout/MultilineOperationIndentation
|
42
48
|
val == :work_through ||
|
@@ -130,6 +136,23 @@ class RedisQueuedLocks::Client
|
|
130
136
|
# @option instrument [NilClass,Any]
|
131
137
|
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
132
138
|
# with :instrument key;
|
139
|
+
# @option log_sampling_enabled [Boolean]
|
140
|
+
# - enables <log sampling>: only the configured percent of RQL cases will be logged;
|
141
|
+
# - disabled by default;
|
142
|
+
# - works in tandem with <config.log_sampling_percent and <log.sampler>;
|
143
|
+
# @option log_sampling_percent [Integer]
|
144
|
+
# - the percent of cases that should be logged;
|
145
|
+
# - take an effect when <config.log_sampling_enalbed> is true;
|
146
|
+
# - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
|
147
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
148
|
+
# - percent-based log sampler that decides should be RQL case logged or not;
|
149
|
+
# - works in tandem with <config.log_sampling_enabled> and
|
150
|
+
# <config.log_sampling_percent> configs;
|
151
|
+
# - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
|
152
|
+
# method so the algorithm error is ~(0%..13%);
|
153
|
+
# - you can provide your own log sampler with bettter algorithm that should realize
|
154
|
+
# `sampling_happened?(percent) => boolean` interface
|
155
|
+
# (see `RedisQueuedLocks::Logging::Sampler` for example);
|
133
156
|
# @param block [Block]
|
134
157
|
# A block of code that should be executed after the successfully acquired lock.
|
135
158
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -138,7 +161,8 @@ class RedisQueuedLocks::Client
|
|
138
161
|
#
|
139
162
|
# @api public
|
140
163
|
# @since 1.0.0
|
141
|
-
# @version 1.
|
164
|
+
# @version 1.5.0
|
165
|
+
# rubocop:disable Metrics/MethodLength
|
142
166
|
def lock(
|
143
167
|
lock_name,
|
144
168
|
ttl: config[:default_lock_ttl],
|
@@ -156,6 +180,9 @@ class RedisQueuedLocks::Client
|
|
156
180
|
logger: config[:logger],
|
157
181
|
log_lock_try: config[:log_lock_try],
|
158
182
|
instrument: nil,
|
183
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
184
|
+
log_sampling_percent: config[:log_sampling_percent],
|
185
|
+
log_sampler: config[:log_sampler],
|
159
186
|
&block
|
160
187
|
)
|
161
188
|
RedisQueuedLocks::Acquier::AcquireLock.acquire_lock(
|
@@ -178,18 +205,22 @@ class RedisQueuedLocks::Client
|
|
178
205
|
fail_fast:,
|
179
206
|
conflict_strategy:,
|
180
207
|
meta:,
|
181
|
-
logger
|
182
|
-
log_lock_try
|
208
|
+
logger:,
|
209
|
+
log_lock_try:,
|
183
210
|
instrument:,
|
211
|
+
log_sampling_enabled:,
|
212
|
+
log_sampling_percent:,
|
213
|
+
log_sampler:,
|
184
214
|
&block
|
185
215
|
)
|
186
216
|
end
|
217
|
+
# rubocop:enable Metrics/MethodLength
|
187
218
|
|
188
219
|
# @note See #lock method signature.
|
189
220
|
#
|
190
221
|
# @api public
|
191
222
|
# @since 1.0.0
|
192
|
-
# @version 1.
|
223
|
+
# @version 1.5.0
|
193
224
|
def lock!(
|
194
225
|
lock_name,
|
195
226
|
ttl: config[:default_lock_ttl],
|
@@ -206,6 +237,9 @@ class RedisQueuedLocks::Client
|
|
206
237
|
logger: config[:logger],
|
207
238
|
log_lock_try: config[:log_lock_try],
|
208
239
|
instrument: nil,
|
240
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
241
|
+
log_sampling_percent: config[:log_sampling_percent],
|
242
|
+
log_sampler: config[:log_sampler],
|
209
243
|
&block
|
210
244
|
)
|
211
245
|
lock(
|
@@ -225,6 +259,9 @@ class RedisQueuedLocks::Client
|
|
225
259
|
meta:,
|
226
260
|
instrument:,
|
227
261
|
conflict_strategy:,
|
262
|
+
log_sampling_enabled:,
|
263
|
+
log_sampling_percent:,
|
264
|
+
log_sampler:,
|
228
265
|
&block
|
229
266
|
)
|
230
267
|
end
|
@@ -233,6 +270,9 @@ class RedisQueuedLocks::Client
|
|
233
270
|
# @option logger [::Logger,#debug]
|
234
271
|
# @option instrumenter [#notify]
|
235
272
|
# @option instrument [NilClass,Any]
|
273
|
+
# @option log_sampling_enabled [Boolean]
|
274
|
+
# @option log_sampling_percent [Integer]
|
275
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
236
276
|
# @return [RedisQueuedLocks::Data, Hash<Symbol,Any>]
|
237
277
|
# Format: {
|
238
278
|
# ok: true/false,
|
@@ -247,17 +287,24 @@ class RedisQueuedLocks::Client
|
|
247
287
|
#
|
248
288
|
# @api public
|
249
289
|
# @since 1.0.0
|
290
|
+
# @version 1.5.0
|
250
291
|
def unlock(
|
251
292
|
lock_name,
|
252
293
|
logger: config[:logger],
|
253
294
|
instrumenter: config[:instrumenter],
|
254
|
-
instrument: nil
|
295
|
+
instrument: nil,
|
296
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
297
|
+
log_sampling_percent: config[:log_sampling_percent],
|
298
|
+
log_sampler: config[:log_sampler]
|
255
299
|
)
|
256
300
|
RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
|
257
301
|
redis_client,
|
258
302
|
lock_name,
|
259
303
|
config[:instrumenter],
|
260
|
-
config[:logger]
|
304
|
+
config[:logger],
|
305
|
+
log_sampling_enabled,
|
306
|
+
log_sampling_percent,
|
307
|
+
log_sampler
|
261
308
|
)
|
262
309
|
end
|
263
310
|
|
@@ -310,18 +357,44 @@ class RedisQueuedLocks::Client
|
|
310
357
|
#
|
311
358
|
# @param lock_name [String]
|
312
359
|
# @param milliseconds [Integer] How many milliseconds should be added.
|
360
|
+
# @option logger [::Logger,#debug]
|
361
|
+
# @option log_sampling_enabled [Boolean]
|
362
|
+
# - The percent of cases that should be logged;
|
363
|
+
# - Sampling algorithm is super simple and works via SecureRandom.rand method
|
364
|
+
# on the base of "weight" algorithm;
|
365
|
+
# - You can provide your own sampler via config[:log_sampler] config and :sampler option
|
366
|
+
# (see `RedisQueuedLocks::Logging::Sampler` for examples);
|
367
|
+
# - The spread of guaranteed percent is approximately +13% (rand method spread);
|
368
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
369
|
+
# (when log sampling is enabled);
|
370
|
+
# @option log_sampling_percent [Integer]
|
371
|
+
# - The percent of cases that should be logged;
|
372
|
+
# - Take an effect when <log_sampling_enabled> parameter has <true> value
|
373
|
+
# (when log sampling is enabled);
|
374
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
313
375
|
# @return [Hash<Symbol,Boolean|Symbol>]
|
314
376
|
# - { ok: true, result: :ttl_extended }
|
315
377
|
# - { ok: false, result: :async_expire_or_no_lock }
|
316
378
|
#
|
317
379
|
# @api public
|
318
380
|
# @since 1.0.0
|
319
|
-
|
381
|
+
# @version 1.5.0
|
382
|
+
def extend_lock_ttl(
|
383
|
+
lock_name,
|
384
|
+
milliseconds,
|
385
|
+
logger: config[:logger],
|
386
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
387
|
+
log_sampling_percent: config[:log_sampling_percent],
|
388
|
+
log_sampler: config[:log_sampler]
|
389
|
+
)
|
320
390
|
RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
|
321
391
|
redis_client,
|
322
392
|
lock_name,
|
323
393
|
milliseconds,
|
324
|
-
logger
|
394
|
+
logger,
|
395
|
+
log_sampling_enabled,
|
396
|
+
log_sampling_percent,
|
397
|
+
log_sampler
|
325
398
|
)
|
326
399
|
end
|
327
400
|
|
@@ -334,23 +407,33 @@ class RedisQueuedLocks::Client
|
|
334
407
|
# @option logger [::Logger,#debug]
|
335
408
|
# @option instrumenter [#notify]
|
336
409
|
# @option instrument [NilClass,Any]
|
410
|
+
# @option log_sampling_enabled [Boolean]
|
411
|
+
# @option log_sampling_percent [Integer]
|
412
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
337
413
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Numeric>>]
|
338
414
|
# Example: { ok: true, result { rel_key_cnt: 100, rel_time: 0.01 } }
|
339
415
|
#
|
340
416
|
# @api public
|
341
417
|
# @since 1.0.0
|
418
|
+
# @version 1.5.0
|
342
419
|
def clear_locks(
|
343
420
|
batch_size: config[:lock_release_batch_size],
|
344
421
|
logger: config[:logger],
|
345
422
|
instrumenter: config[:instrumenter],
|
346
|
-
instrument: nil
|
423
|
+
instrument: nil,
|
424
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
425
|
+
log_sampling_percent: config[:log_sampling_percent],
|
426
|
+
log_sampler: config[:log_sampler]
|
347
427
|
)
|
348
428
|
RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
|
349
429
|
redis_client,
|
350
430
|
batch_size,
|
351
431
|
logger,
|
352
432
|
instrumenter,
|
353
|
-
instrument
|
433
|
+
instrument,
|
434
|
+
log_sampling_enabled,
|
435
|
+
log_sampling_percent,
|
436
|
+
log_sampler
|
354
437
|
)
|
355
438
|
end
|
356
439
|
|
@@ -438,18 +521,24 @@ class RedisQueuedLocks::Client
|
|
438
521
|
# @option logger [::Logger,#debug]
|
439
522
|
# @option instrumenter [#notify]
|
440
523
|
# @option instrument [NilClass,Any]
|
524
|
+
# @option log_sampling_enabled [Boolean]
|
525
|
+
# @option log_sampling_percent [Integer]
|
526
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
441
527
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
|
442
528
|
# Format: { ok: true, result: { processed_queus: Set<String> } }
|
443
529
|
#
|
444
530
|
# @api public
|
445
531
|
# @since 1.0.0
|
446
|
-
# @version 1.
|
532
|
+
# @version 1.5.0
|
447
533
|
def clear_dead_requests(
|
448
534
|
dead_ttl: config[:dead_request_ttl],
|
449
535
|
scan_size: config[:lock_release_batch_size],
|
450
536
|
logger: config[:logger],
|
451
537
|
instrumenter: config[:instrumenter],
|
452
|
-
instrument: nil
|
538
|
+
instrument: nil,
|
539
|
+
log_sampling_enabled: config[:log_sampling_enabled],
|
540
|
+
log_sampling_percent: config[:log_sampling_percent],
|
541
|
+
log_sampler: config[:log_sampler]
|
453
542
|
)
|
454
543
|
RedisQueuedLocks::Acquier::ClearDeadRequests.clear_dead_requests(
|
455
544
|
redis_client,
|
@@ -457,7 +546,10 @@ class RedisQueuedLocks::Client
|
|
457
546
|
dead_ttl,
|
458
547
|
logger,
|
459
548
|
instrumenter,
|
460
|
-
instrument
|
549
|
+
instrument,
|
550
|
+
log_sampling_enabled,
|
551
|
+
log_sampling_percent,
|
552
|
+
log_sampler
|
461
553
|
)
|
462
554
|
end
|
463
555
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.5.0
|
5
|
+
module RedisQueuedLocks::Logging::Sampler
|
6
|
+
class << self
|
7
|
+
# Super simple probalistic function based on the `weight` of <true>/<false> values.
|
8
|
+
# Requires the <percent> parameter as the required percent of <true> values sampled.
|
9
|
+
#
|
10
|
+
# @param sampling_percent [Integer]
|
11
|
+
# - percent of <true> values in range of 0..100;
|
12
|
+
# @return [Boolean]
|
13
|
+
# - <true> for <sampling_percent>% of invocations (and <false> for the rest invocations)
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
# @since 1.5.0
|
17
|
+
def sampling_happened?(sampling_percent)
|
18
|
+
sampling_percent >= SecureRandom.rand(0..100)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -4,8 +4,54 @@
|
|
4
4
|
# @since 1.0.0
|
5
5
|
module RedisQueuedLocks::Logging
|
6
6
|
require_relative 'logging/void_logger'
|
7
|
+
require_relative 'logging/sampler'
|
7
8
|
|
8
9
|
class << self
|
10
|
+
# @param log_sampling_enabled [Boolean]
|
11
|
+
# @param log_sampling_percent [Integer]
|
12
|
+
# @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
13
|
+
# @return [Boolean]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
# @since 1.5.0
|
17
|
+
def should_log?(log_sampling_enabled, log_sampling_percent, log_sampler)
|
18
|
+
return true unless log_sampling_enabled
|
19
|
+
log_sampler.sampling_happened?(log_sampling_percent)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
23
|
+
# @return [Boolean]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
# @since 1.5.0
|
27
|
+
def valid_sampler?(sampler)
|
28
|
+
return false unless sampler.respond_to?(:sampling_happened?)
|
29
|
+
|
30
|
+
m_obj = sampler.method(:sampling_happened?)
|
31
|
+
m_sig = m_obj.parameters
|
32
|
+
|
33
|
+
# NOTE:
|
34
|
+
# Required method signature (sampling_percent)
|
35
|
+
# => [[:req, :sampling_percent]]
|
36
|
+
# => [[:opt, :sampling_percent]]
|
37
|
+
# => [[:req, :sampling_percent], [:block, :block]]
|
38
|
+
# => [[:opt, :sampling_percent], [:block, :block]]
|
39
|
+
if m_sig.size == 1
|
40
|
+
prm = m_sig[0][0]
|
41
|
+
prm == :req || prm == :opt
|
42
|
+
elsif m_sig.size == 2
|
43
|
+
f_prm = m_sig[0][0]
|
44
|
+
s_prm = m_sign[1][0]
|
45
|
+
|
46
|
+
# rubocop:disable Layout/MultilineOperationIndentation
|
47
|
+
f_prm == :req && s_prm == :block ||
|
48
|
+
f_prm == :opt && s_prm == :block
|
49
|
+
# rubocop:enable Layout/MultilineOperationIndentation
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
9
55
|
# @param logger [::Logger,#debug,::SemanticLogger::Logger]
|
10
56
|
# @return [Boolean]
|
11
57
|
#
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_queued_locks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rustam Ibragimov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-05-
|
11
|
+
date: 2024-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- lib/redis_queued_locks/instrument/active_support.rb
|
82
82
|
- lib/redis_queued_locks/instrument/void_notifier.rb
|
83
83
|
- lib/redis_queued_locks/logging.rb
|
84
|
+
- lib/redis_queued_locks/logging/sampler.rb
|
84
85
|
- lib/redis_queued_locks/logging/void_logger.rb
|
85
86
|
- lib/redis_queued_locks/resource.rb
|
86
87
|
- lib/redis_queued_locks/utilities.rb
|