redis_queued_locks 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +35 -13
- data/.claude/rules/acquirer.md +10 -9
- data/.claude/rules/logic.md +28 -5
- data/.claude/rules/swarm.md +2 -2
- data/.claude/rules/tests.md +6 -3
- data/.claude/rules/visitors.md +9 -10
- data/.claude/skills/rql-code-review/SKILL.md +24 -10
- data/CHANGELOG.md +126 -0
- data/CLAUDE.md +2 -1
- data/README.md +245 -70
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +12 -1
- data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +5 -5
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +111 -24
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +86 -8
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +122 -12
- data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
- data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +10 -1
- data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +55 -15
- data/lib/redis_queued_locks/acquirer/locks.rb +26 -9
- data/lib/redis_queued_locks/acquirer/queue_info.rb +0 -1
- data/lib/redis_queued_locks/acquirer/queues.rb +10 -1
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +31 -4
- data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +39 -4
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/client.rb +82 -40
- data/lib/redis_queued_locks/config.rb +12 -13
- data/lib/redis_queued_locks/errors.rb +34 -4
- data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
- data/lib/redis_queued_locks/logging/sampler.rb +4 -8
- data/lib/redis_queued_locks/resource.rb +14 -4
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
- data/lib/redis_queued_locks/swarm.rb +86 -5
- data/lib/redis_queued_locks/utilities.rb +1 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +9 -0
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +15 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +14 -3
- data/sig/redis_queued_locks/errors.rbs +5 -2
- data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
- data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
- data/sig/redis_queued_locks/resource.rbs +1 -1
- data/sig/vendor/redis_client.rbs +1 -0
- metadata +1 -1
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.7.0
|
|
5
|
+
# @version 1.21.0
|
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
|
5
7
|
module RedisQueuedLocks::Acquirer::AcquireLock::InstrVisitor
|
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
|
6
9
|
class << self
|
|
7
10
|
# @param instrumenter [#notify]
|
|
8
11
|
# @param instr_sampled [Boolean]
|
|
@@ -172,5 +175,44 @@ module RedisQueuedLocks::Acquirer::AcquireLock::InstrVisitor
|
|
|
172
175
|
hold_time:, ttl:, acq_id:, hst_id:, ts:, lock_key:, rw_mode:, acq_time:, instrument:
|
|
173
176
|
}) rescue nil
|
|
174
177
|
end
|
|
178
|
+
|
|
179
|
+
# The lock is not obtained (the failure result of `#lock`/`#lock!`: the timeout, the retry
|
|
180
|
+
# limit, `fail_fast`, the dead lock or the lock upgrade conflict).
|
|
181
|
+
#
|
|
182
|
+
# @param instrumenter [#notify]
|
|
183
|
+
# @param instr_sampled [Boolean]
|
|
184
|
+
# @param lock_key [String]
|
|
185
|
+
# @param rw_mode [Symbol]
|
|
186
|
+
# @param ttl [Integer]
|
|
187
|
+
# @param acq_id [String]
|
|
188
|
+
# @param hst_id [String]
|
|
189
|
+
# @param ts [Numeric]
|
|
190
|
+
# @param acq_time [Numeric]
|
|
191
|
+
# @param result [Symbol] The failure result (`:timeout_reached`, `:retry_limit_reached`, ...).
|
|
192
|
+
# @param instrument [NilClass,Any]
|
|
193
|
+
# @return [void]
|
|
194
|
+
#
|
|
195
|
+
# @api private
|
|
196
|
+
# @since 1.21.0
|
|
197
|
+
def lock_obtaining_failed(
|
|
198
|
+
instrumenter,
|
|
199
|
+
instr_sampled,
|
|
200
|
+
lock_key,
|
|
201
|
+
rw_mode,
|
|
202
|
+
ttl,
|
|
203
|
+
acq_id,
|
|
204
|
+
hst_id,
|
|
205
|
+
ts,
|
|
206
|
+
acq_time,
|
|
207
|
+
result,
|
|
208
|
+
instrument
|
|
209
|
+
)
|
|
210
|
+
return unless instr_sampled
|
|
211
|
+
instrumenter.notify('redis_queued_locks.lock_obtaining_failed', {
|
|
212
|
+
lock_key:, rw_mode:, ttl:, acq_id:, hst_id:, ts:, acq_time:, result:, instrument:
|
|
213
|
+
}) rescue nil
|
|
214
|
+
end
|
|
175
215
|
end
|
|
216
|
+
# rubocop:enable Metrics/ClassLength
|
|
176
217
|
end
|
|
218
|
+
# rubocop:enable Metrics/ModuleLength
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.7.0
|
|
5
|
+
# @version 1.21.0
|
|
5
6
|
# rubocop:disable Metrics/ModuleLength
|
|
6
7
|
module RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor
|
|
7
8
|
# rubocop:disable Metrics/ClassLength
|
|
@@ -230,6 +231,50 @@ module RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor
|
|
|
230
231
|
"acq_time => #{acq_time} (ms)"
|
|
231
232
|
end rescue nil
|
|
232
233
|
end
|
|
234
|
+
|
|
235
|
+
# The lock is not obtained (the failure result of `#lock`/`#lock!`: the timeout, the retry
|
|
236
|
+
# limit, `fail_fast`, the dead lock or the lock upgrade conflict).
|
|
237
|
+
#
|
|
238
|
+
# @param logger [::Logger,#debug]
|
|
239
|
+
# @param log_sampled [Boolean]
|
|
240
|
+
# @param lock_key [String]
|
|
241
|
+
# @param queue_ttl [Integer]
|
|
242
|
+
# @param acquirer_id [String]
|
|
243
|
+
# @param host_id [String]
|
|
244
|
+
# @param acq_time [Numeric]
|
|
245
|
+
# @param access_strategy [Symbol]
|
|
246
|
+
# @param rw_mode [Symbol]
|
|
247
|
+
# @param result [Symbol] The failure result (`:timeout_reached`, `:retry_limit_reached`, ...).
|
|
248
|
+
# @return [void]
|
|
249
|
+
#
|
|
250
|
+
# @api private
|
|
251
|
+
# @since 1.21.0
|
|
252
|
+
def lock_obtaining_failed(
|
|
253
|
+
logger,
|
|
254
|
+
log_sampled,
|
|
255
|
+
lock_key,
|
|
256
|
+
queue_ttl,
|
|
257
|
+
acquirer_id,
|
|
258
|
+
host_id,
|
|
259
|
+
acq_time,
|
|
260
|
+
access_strategy,
|
|
261
|
+
rw_mode,
|
|
262
|
+
result
|
|
263
|
+
)
|
|
264
|
+
return unless log_sampled
|
|
265
|
+
|
|
266
|
+
logger.debug do
|
|
267
|
+
"[redis_queued_locks.lock_obtaining_failed] " \
|
|
268
|
+
"lock_key => '#{lock_key}' " \
|
|
269
|
+
"queue_ttl => #{queue_ttl} " \
|
|
270
|
+
"acq_id => '#{acquirer_id}' " \
|
|
271
|
+
"hst_id => '#{host_id}' " \
|
|
272
|
+
"acs_strat => '#{access_strategy}' " \
|
|
273
|
+
"rw_mode => '#{rw_mode}' " \
|
|
274
|
+
"acq_time => #{acq_time} (ms) " \
|
|
275
|
+
"result => '#{result}'"
|
|
276
|
+
end rescue nil
|
|
277
|
+
end
|
|
233
278
|
end
|
|
234
279
|
# rubocop:enable Metrics/ClassLength
|
|
235
280
|
end
|
|
@@ -154,7 +154,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
154
154
|
#
|
|
155
155
|
# @api private
|
|
156
156
|
# @since 1.0.0
|
|
157
|
-
# @version 1.
|
|
157
|
+
# @version 1.21.0
|
|
158
158
|
# rubocop:disable Metrics/MethodLength
|
|
159
159
|
def try_to_lock(
|
|
160
160
|
redis,
|
|
@@ -648,6 +648,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
648
648
|
end
|
|
649
649
|
end
|
|
650
650
|
end
|
|
651
|
+
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
652
|
+
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
653
|
+
::Timeout::ExitException
|
|
654
|
+
# NOTE: (timeouts) the timeout can interrupt the attempt inside the WATCH block:
|
|
655
|
+
# redis-client sends `UNWATCH` only on `StandardError` (the internal timeouts and the
|
|
656
|
+
# `Timeout.timeout` of the user without an exception class are `::Exception`s), and the
|
|
657
|
+
# connection would return to the pool with the active WATCH (an EXEC of the next
|
|
658
|
+
# transaction on it is aborted when a watched key is changed);
|
|
659
|
+
# - the closed connection drops the WATCH state and is reconnected on the next use;
|
|
660
|
+
rconn.close
|
|
661
|
+
raise
|
|
651
662
|
end
|
|
652
663
|
|
|
653
664
|
# Step 7: Analyze the aquirement attempt:
|
|
@@ -6,7 +6,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::WithAcqTimeout
|
|
|
6
6
|
# @param redis [RedisClient]
|
|
7
7
|
# Redis connection manager required for additional data extraction for error message.
|
|
8
8
|
# @param timeout [NilClass,Integer]
|
|
9
|
-
# Time period after which the logic will fail with timeout error.
|
|
9
|
+
# Time period after which the logic will fail with timeout error (`nil` and `0`: no timeout).
|
|
10
10
|
# @param lock_key [String]
|
|
11
11
|
# Lock name in RQL notation (rql:lock:some-lock-name).
|
|
12
12
|
# @param lock_name [String]
|
|
@@ -26,7 +26,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::WithAcqTimeout
|
|
|
26
26
|
#
|
|
27
27
|
# @api private
|
|
28
28
|
# @since 1.0.0
|
|
29
|
-
# @version 1.
|
|
29
|
+
# @version 1.21.0
|
|
30
30
|
def with_acq_timeout(
|
|
31
31
|
redis,
|
|
32
32
|
timeout,
|
|
@@ -30,7 +30,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
|
30
30
|
# @param access_strategy [Symbol] Lock obtaining strategy.
|
|
31
31
|
# @param timed [Boolean] Should the lock be wrapped by Timeout with with lock's ttl
|
|
32
32
|
# @param ttl_shift [Numeric] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
|
|
33
|
-
# @param ttl [Integer
|
|
33
|
+
# @param ttl [Integer] Lock's time to live (in ms). A positive Integer.
|
|
34
34
|
# @param queue_ttl [Integer] Lock request lifetime.
|
|
35
35
|
# @param meta [NilClass,Hash<String|Symbol,Any>] Custom metadata wich is passed to the lock data;
|
|
36
36
|
# @param log_sampled [Boolean] Should the logic be logged or not.
|
|
@@ -45,7 +45,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
|
45
45
|
#
|
|
46
46
|
# @api private
|
|
47
47
|
# @since 1.3.0
|
|
48
|
-
# @version 1.
|
|
48
|
+
# @version 1.21.0
|
|
49
49
|
# rubocop:disable Metrics/MethodLength
|
|
50
50
|
def yield_expire(
|
|
51
51
|
redis,
|
|
@@ -71,7 +71,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
|
71
71
|
initial_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
|
72
72
|
|
|
73
73
|
if block_given?
|
|
74
|
-
if timed
|
|
74
|
+
if timed
|
|
75
75
|
# NOTE:
|
|
76
76
|
# - steep is ignored cuz steep can not recognize `::Integer - ::Integer|::Float`
|
|
77
77
|
# operation here for some mystical reason (it tryes to find overloadd `-` method for
|
|
@@ -150,7 +150,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
|
150
150
|
|
|
151
151
|
# @param timeout [Float]
|
|
152
152
|
# @param lock_key [String]
|
|
153
|
-
# @param lock_ttl [Integer
|
|
153
|
+
# @param lock_ttl [Integer]
|
|
154
154
|
# @param acquirer_id [String]
|
|
155
155
|
# @param host_id [String]
|
|
156
156
|
# @param meta [NilClass,Hash<Symbol|String,Any>]
|
|
@@ -161,7 +161,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
|
161
161
|
#
|
|
162
162
|
# @api private
|
|
163
163
|
# @since 1.3.0
|
|
164
|
-
# @version 1.
|
|
164
|
+
# @version 1.21.0
|
|
165
165
|
def yield_with_timeout(timeout, lock_key, lock_ttl, acquirer_id, host_id, meta, &block)
|
|
166
166
|
::Timeout.timeout(timeout, RedisQueuedLocks::TimedLockIntermediateTimeoutError, &block)
|
|
167
167
|
rescue RedisQueuedLocks::TimedLockIntermediateTimeoutError
|
|
@@ -42,8 +42,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
42
42
|
# A current fiber that want to acquire a lock.
|
|
43
43
|
# @option ractor_id [Integer,String]
|
|
44
44
|
# The current ractor that want to acquire a lock.
|
|
45
|
-
# @option ttl [Integer
|
|
46
|
-
# Lock's time to live (in milliseconds).
|
|
45
|
+
# @option ttl [Integer]
|
|
46
|
+
# Lock's time to live (in milliseconds). A positive Integer (both modes).
|
|
47
47
|
# @option queue_ttl [Integer]
|
|
48
48
|
# Lifetime of the acuier's lock request. In seconds.
|
|
49
49
|
# @option lock_index_enabled [Boolean]
|
|
@@ -57,7 +57,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
57
57
|
# - pre-configured in `config['lock_index_grace_period']`;
|
|
58
58
|
# - used only with the enabled lock index (`lock_index_enabled`);
|
|
59
59
|
# @option timeout [Integer,NilClass]
|
|
60
|
-
# Time period whe should try to acquire the lock (in seconds).
|
|
60
|
+
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout"
|
|
61
|
+
# (`0` is the same as `nil`: `Timeout.timeout(0)` sets no timeout).
|
|
61
62
|
# @option timed [Boolean]
|
|
62
63
|
# Limit the invocation time period of the passed block of code by the lock's TTL.
|
|
63
64
|
# @option retry_count [Integer,NilClass]
|
|
@@ -131,7 +132,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
131
132
|
# - works in tandem with <config['log_sampling_percent'] and <config['log_sampler']>;
|
|
132
133
|
# @option log_sampling_percent [Integer]
|
|
133
134
|
# - the percent of cases that should be logged;
|
|
134
|
-
# - take an effect when <config['
|
|
135
|
+
# - take an effect when <config['log_sampling_enabled']> is true;
|
|
135
136
|
# - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
|
|
136
137
|
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
|
137
138
|
# - percent-based log sampler that decides should be RQL case logged or not;
|
|
@@ -152,7 +153,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
152
153
|
# - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
|
|
153
154
|
# @option instr_sampling_percent [Integer]
|
|
154
155
|
# - the percent of cases that should be instrumented;
|
|
155
|
-
# - take an effect when <config['
|
|
156
|
+
# - take an effect when <config['instr_sampling_enabled']> is true;
|
|
156
157
|
# - works in tandem with <config['instr_sampling_enabled']>
|
|
157
158
|
# and <config['instr_sampler']> configs;
|
|
158
159
|
# @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
|
|
@@ -176,7 +177,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
176
177
|
#
|
|
177
178
|
# @api private
|
|
178
179
|
# @since 1.0.0
|
|
179
|
-
# @version 1.
|
|
180
|
+
# @version 1.21.0
|
|
180
181
|
def acquire_lock(
|
|
181
182
|
redis,
|
|
182
183
|
lock_name,
|
|
@@ -235,29 +236,79 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
235
236
|
)
|
|
236
237
|
end
|
|
237
238
|
|
|
238
|
-
# Step 0.1
|
|
239
|
-
# -
|
|
240
|
-
|
|
239
|
+
# Step 0.1: prevent :ttl incompatibilities (both modes)
|
|
240
|
+
# - write lock: `PEXPIRE` with a non-positive ttl deletes the just obtained lock key in the
|
|
241
|
+
# obtaining transaction (the lock is "obtained" without the mutual exclusion);
|
|
242
|
+
# - read lock: the read lock expiration time is calculated from the lock ttl;
|
|
243
|
+
if !ttl.is_a?(::Integer) || ttl <= 0
|
|
241
244
|
raise(
|
|
242
245
|
RedisQueuedLocks::ArgumentError,
|
|
243
|
-
"`:ttl` argument should be a positive Integer
|
|
246
|
+
"`:ttl` argument should be a positive Integer, got #{ttl.inspect}."
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Step 0.1: prevent :queue_ttl, :timeout, :retry_* and :lock_index_grace_period
|
|
251
|
+
# incompatibilities (the same ranges as their config keys):
|
|
252
|
+
# - a non-positive queue ttl drops the own lock request in the queueing pipeline (the lock
|
|
253
|
+
# request is always dead);
|
|
254
|
+
# - a negative timeout/retry delay/retry jitter fails `Timeout.timeout`/`sleep` in the
|
|
255
|
+
# middle of the acquirement (`0` timeout is the same as `nil`: no timeout);
|
|
256
|
+
if !queue_ttl.is_a?(::Integer) || queue_ttl <= 0
|
|
257
|
+
raise(
|
|
258
|
+
RedisQueuedLocks::ArgumentError,
|
|
259
|
+
"`:queue_ttl` argument should be a positive Integer, got #{queue_ttl.inspect}."
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
if timeout != nil && (!timeout.is_a?(::Integer) || timeout < 0)
|
|
263
|
+
raise(
|
|
264
|
+
RedisQueuedLocks::ArgumentError,
|
|
265
|
+
"`:timeout` argument should be nil or a non-negative Integer, got #{timeout.inspect}."
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
if retry_count != nil && (!retry_count.is_a?(::Integer) || retry_count < 0)
|
|
269
|
+
raise(
|
|
270
|
+
RedisQueuedLocks::ArgumentError,
|
|
271
|
+
"`:retry_count` argument should be nil or a non-negative Integer, " \
|
|
272
|
+
"got #{retry_count.inspect}."
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
if !retry_delay.is_a?(::Integer) || retry_delay < 0
|
|
276
|
+
raise(
|
|
277
|
+
RedisQueuedLocks::ArgumentError,
|
|
278
|
+
"`:retry_delay` argument should be a non-negative Integer, got #{retry_delay.inspect}."
|
|
279
|
+
)
|
|
280
|
+
end
|
|
281
|
+
if !retry_jitter.is_a?(::Integer) || retry_jitter < 0
|
|
282
|
+
raise(
|
|
283
|
+
RedisQueuedLocks::ArgumentError,
|
|
284
|
+
"`:retry_jitter` argument should be a non-negative Integer, got #{retry_jitter.inspect}."
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
if !lock_index_grace_period.is_a?(::Integer) || lock_index_grace_period < 0
|
|
288
|
+
raise(
|
|
289
|
+
RedisQueuedLocks::ArgumentError,
|
|
290
|
+
"`:lock_index_grace_period` argument should be a non-negative Integer, " \
|
|
291
|
+
"got #{lock_index_grace_period.inspect}."
|
|
244
292
|
)
|
|
245
293
|
end
|
|
246
294
|
|
|
247
295
|
# Step 0.2: prevent :meta incompatabiltiies (structure)
|
|
296
|
+
# - Symbol keys are written to the lock data as Strings (`Symbol#name`, as redis-client
|
|
297
|
+
# does it) and would overwrite the reserved lock data fields too;
|
|
248
298
|
if meta.is_a?(::Hash) && (meta.any? do |key, _value|
|
|
249
|
-
key
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
299
|
+
meta_key = key.is_a?(::Symbol) ? key.name : key
|
|
300
|
+
meta_key == 'acq_id' ||
|
|
301
|
+
meta_key == 'hst_id' ||
|
|
302
|
+
meta_key == 'ts' ||
|
|
303
|
+
meta_key == 'ini_ttl' ||
|
|
304
|
+
meta_key == 'lock_key' ||
|
|
305
|
+
meta_key == 'rem_ttl' ||
|
|
306
|
+
meta_key == 'spc_ext_ttl' ||
|
|
307
|
+
meta_key == 'spc_cnt' ||
|
|
308
|
+
meta_key == 'l_spc_ext_ini_ttl' ||
|
|
309
|
+
meta_key == 'l_spc_ext_ts' ||
|
|
310
|
+
meta_key == 'l_spc_ts' ||
|
|
311
|
+
meta_key == 'rw_mode'
|
|
261
312
|
end)
|
|
262
313
|
raise(
|
|
263
314
|
RedisQueuedLocks::ArgumentError,
|
|
@@ -346,6 +397,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
346
397
|
)
|
|
347
398
|
|
|
348
399
|
# Step 2: try to lock with timeout
|
|
400
|
+
# NOTE: the acquirement start time is defined out of the timeout block: the failed
|
|
401
|
+
# acquirement reports the acquirement time too (Steps 3.b and 3.c);
|
|
402
|
+
acq_start_time = clock_gettime
|
|
349
403
|
with_acq_timeout(
|
|
350
404
|
redis,
|
|
351
405
|
timeout,
|
|
@@ -355,8 +409,6 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
355
409
|
detailed_acq_timeout_error,
|
|
356
410
|
on_timeout: acq_dequeue
|
|
357
411
|
) do
|
|
358
|
-
acq_start_time = clock_gettime
|
|
359
|
-
|
|
360
412
|
# Step 2.1: cyclically try to obtain the lock
|
|
361
413
|
while acq_process[:should_try]
|
|
362
414
|
|
|
@@ -650,8 +702,43 @@ module RedisQueuedLocks::Acquirer::AcquireLock
|
|
|
650
702
|
acq_process[:result] = :timeout_reached
|
|
651
703
|
end
|
|
652
704
|
# Step 3.b: lock is not acquired (acquirer is dequeued by timeout callback)
|
|
705
|
+
acq_time = ((clock_gettime - acq_start_time) / 1_000.0).ceil(2)
|
|
706
|
+
LogVisitor.lock_obtaining_failed(
|
|
707
|
+
logger, log_sampled, lock_key, queue_ttl, acquirer_id, host_id,
|
|
708
|
+
acq_time, access_strategy, read_write_mode, acq_process[:result]
|
|
709
|
+
)
|
|
710
|
+
InstrVisitor.lock_obtaining_failed(
|
|
711
|
+
instrumenter, instr_sampled, lock_key, read_write_mode, lock_ttl, acquirer_id, host_id,
|
|
712
|
+
Time.now.to_f, acq_time, acq_process[:result], instrument
|
|
713
|
+
)
|
|
653
714
|
{ ok: false, result: acq_process[:result] } #: { ok: bool, result: Symbol }
|
|
654
715
|
end
|
|
716
|
+
rescue RedisQueuedLocks::LockAlreadyObtainedError,
|
|
717
|
+
RedisQueuedLocks::ConflictLockObtainError,
|
|
718
|
+
RedisQueuedLocks::LockAcquirementRetryLimitError,
|
|
719
|
+
RedisQueuedLocks::LockAcquirementTimeoutError => error
|
|
720
|
+
# Step 3.c: (raise_errors) the failed acquirement is reported before the error is raised
|
|
721
|
+
# (like Step 3.b); errors of the block of code under the obtained lock are not reported;
|
|
722
|
+
# NOTE: (steep) the locals of the method body are nilable in the method-level `rescue` for
|
|
723
|
+
# Steep, but these errors are raised by the acquirement only (after Step 1: the locals
|
|
724
|
+
# are assigned);
|
|
725
|
+
# steep:ignore:start
|
|
726
|
+
unless acq_process[:acquired]
|
|
727
|
+
if error.is_a?(RedisQueuedLocks::LockAcquirementTimeoutError)
|
|
728
|
+
acq_process[:result] = :timeout_reached
|
|
729
|
+
end
|
|
730
|
+
acq_time = ((clock_gettime - acq_start_time) / 1_000.0).ceil(2)
|
|
731
|
+
LogVisitor.lock_obtaining_failed(
|
|
732
|
+
logger, log_sampled, lock_key, queue_ttl, acquirer_id, host_id,
|
|
733
|
+
acq_time, access_strategy, read_write_mode, acq_process[:result]
|
|
734
|
+
)
|
|
735
|
+
InstrVisitor.lock_obtaining_failed(
|
|
736
|
+
instrumenter, instr_sampled, lock_key, read_write_mode, lock_ttl, acquirer_id, host_id,
|
|
737
|
+
Time.now.to_f, acq_time, acq_process[:result], instrument
|
|
738
|
+
)
|
|
739
|
+
end
|
|
740
|
+
# steep:ignore:end
|
|
741
|
+
raise
|
|
655
742
|
end
|
|
656
743
|
end
|
|
657
744
|
end
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.21.0
|
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
|
6
7
|
module RedisQueuedLocks::Acquirer::ClearDeadRequests
|
|
8
|
+
# @since 1.21.0
|
|
9
|
+
extend RedisQueuedLocks::Utilities
|
|
10
|
+
|
|
11
|
+
# rubocop:disable Metrics/ClassLength
|
|
7
12
|
class << self
|
|
8
13
|
# Drops dead lock requests (requests older than `dead_ttl`) from the lock queues (write and
|
|
9
14
|
# read):
|
|
@@ -13,6 +18,8 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
|
|
|
13
18
|
# over the lock names (the keyspace is never scanned), one round trip per batch of
|
|
14
19
|
# `scan_size` lock names (both lock queues of each lock name); expired lock names are
|
|
15
20
|
# dropped from the lock index first;
|
|
21
|
+
# - the operation is reported with one log and one instrumentation event
|
|
22
|
+
# (`redis_queued_locks.clear_dead_requests`) with the time of the whole operation;
|
|
16
23
|
#
|
|
17
24
|
# @param redis_client [RedisClient]
|
|
18
25
|
# @param scan_size [Integer]
|
|
@@ -36,7 +43,8 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
|
|
|
36
43
|
#
|
|
37
44
|
# @api private
|
|
38
45
|
# @since 1.0.0
|
|
39
|
-
# @version 1.
|
|
46
|
+
# @version 1.21.0
|
|
47
|
+
# rubocop:disable Metrics/MethodLength
|
|
40
48
|
def clear_dead_requests(
|
|
41
49
|
redis_client,
|
|
42
50
|
scan_size,
|
|
@@ -54,10 +62,82 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
|
|
|
54
62
|
instr_sampler,
|
|
55
63
|
instr_sample_this
|
|
56
64
|
)
|
|
65
|
+
# NOTE: (arguments) the same ranges as the config keys: zero/negative sizes fail SCAN/ZSCAN
|
|
66
|
+
# (`COUNT 0`) and the batching, a non-positive dead ttl drops all lock requests;
|
|
67
|
+
if !scan_size.is_a?(::Integer) || scan_size <= 0
|
|
68
|
+
raise(
|
|
69
|
+
RedisQueuedLocks::ArgumentError,
|
|
70
|
+
"`:scan_size` argument should be a positive Integer, got #{scan_size.inspect}."
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
if !dead_ttl.is_a?(::Integer) || dead_ttl <= 0
|
|
74
|
+
raise(
|
|
75
|
+
RedisQueuedLocks::ArgumentError,
|
|
76
|
+
"`:dead_ttl` argument should be a positive Integer, got #{dead_ttl.inspect}."
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
rel_start_time = clock_gettime
|
|
81
|
+
processed_queues = fully_clear_dead_requests(
|
|
82
|
+
redis_client, scan_size, dead_ttl, lock_index_enabled
|
|
83
|
+
)
|
|
84
|
+
time_at = Time.now.to_f
|
|
85
|
+
rel_end_time = clock_gettime
|
|
86
|
+
rel_time = ((rel_end_time - rel_start_time) / 1_000.0).ceil(2)
|
|
87
|
+
|
|
88
|
+
log_sampled = RedisQueuedLocks::Logging.should_log?(
|
|
89
|
+
log_sampling_enabled,
|
|
90
|
+
log_sample_this,
|
|
91
|
+
log_sampling_percent,
|
|
92
|
+
log_sampler
|
|
93
|
+
)
|
|
94
|
+
instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
|
|
95
|
+
instr_sampling_enabled,
|
|
96
|
+
instr_sample_this,
|
|
97
|
+
instr_sampling_percent,
|
|
98
|
+
instr_sampler
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
run_non_critical do
|
|
102
|
+
logger.debug do
|
|
103
|
+
"[redis_queued_locks.clear_dead_requests] " \
|
|
104
|
+
"dead_ttl => #{dead_ttl} " \
|
|
105
|
+
"prc_queue_cnt => #{processed_queues.size} " \
|
|
106
|
+
"rel_time => #{rel_time} " \
|
|
107
|
+
"at => #{time_at}"
|
|
108
|
+
end
|
|
109
|
+
end if log_sampled
|
|
110
|
+
|
|
111
|
+
run_non_critical do
|
|
112
|
+
instrumenter.notify('redis_queued_locks.clear_dead_requests', {
|
|
113
|
+
dead_ttl: dead_ttl,
|
|
114
|
+
prc_queue_cnt: processed_queues.size,
|
|
115
|
+
rel_time: rel_time,
|
|
116
|
+
at: time_at,
|
|
117
|
+
instrument: instrument
|
|
118
|
+
})
|
|
119
|
+
end if instr_sampled
|
|
120
|
+
|
|
121
|
+
{ ok: true, result: { processed_queues: } }
|
|
122
|
+
end
|
|
123
|
+
# rubocop:enable Metrics/MethodLength
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
# Drops dead lock requests from the lock queues (see `clear_dead_requests`).
|
|
128
|
+
#
|
|
129
|
+
# @param redis_client [RedisClient]
|
|
130
|
+
# @param scan_size [Integer]
|
|
131
|
+
# @param dead_ttl [Integer] In milliseconds
|
|
132
|
+
# @param lock_index_enabled [Boolean]
|
|
133
|
+
# @return [Set<String>] The processed lock queues (write and read lock request queues).
|
|
134
|
+
#
|
|
135
|
+
# @api private
|
|
136
|
+
# @since 1.21.0
|
|
137
|
+
def fully_clear_dead_requests(redis_client, scan_size, dead_ttl, lock_index_enabled)
|
|
57
138
|
dead_score = RedisQueuedLocks::Resource.acquirer_dead_score(dead_ttl / 1_000.0)
|
|
58
139
|
|
|
59
|
-
|
|
60
|
-
result = Set.new.tap do |processed_queues|
|
|
140
|
+
Set.new.tap do |processed_queues|
|
|
61
141
|
# @type var processed_queues: Set[String]
|
|
62
142
|
redis_client.with do |rconn|
|
|
63
143
|
if lock_index_enabled
|
|
@@ -92,12 +172,8 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
|
|
|
92
172
|
end
|
|
93
173
|
end
|
|
94
174
|
end
|
|
95
|
-
|
|
96
|
-
{ ok: true, result: { processed_queues: result } }
|
|
97
175
|
end
|
|
98
176
|
|
|
99
|
-
private
|
|
100
|
-
|
|
101
177
|
# Write and read lock queues in one SCAN pass (the disabled lock index).
|
|
102
178
|
#
|
|
103
179
|
# @param redis_client [RedisClient]
|
|
@@ -210,4 +286,6 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
|
|
|
210
286
|
yield(lock_names) unless lock_names.empty?
|
|
211
287
|
end
|
|
212
288
|
end
|
|
289
|
+
# rubocop:enable Metrics/ClassLength
|
|
213
290
|
end
|
|
291
|
+
# rubocop:enable Metrics/ModuleLength
|