redis_queued_locks 1.19.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 +96 -23
- data/.claude/rules/acquirer.md +39 -20
- data/.claude/rules/arguments.md +3 -2
- data/.claude/rules/git.md +3 -2
- data/.claude/rules/logic.md +32 -8
- data/.claude/rules/swarm.md +14 -9
- data/.claude/rules/tests.md +12 -3
- data/.claude/rules/type-checking.md +2 -1
- data/.claude/rules/visitors.md +10 -11
- data/.claude/skills/rql-code-review/SKILL.md +386 -0
- data/.claude/skills/rql-code-review/templates/report.en.md +31 -0
- data/.claude/skills/rql-code-review/templates/report.ru.md +31 -0
- data/CHANGELOG.md +194 -0
- data/CLAUDE.md +6 -3
- data/README.md +502 -92
- 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 +695 -128
- 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 +8 -6
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +134 -26
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +229 -24
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +189 -18
- 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 +151 -7
- data/lib/redis_queued_locks/acquirer/lock_info.rb +39 -41
- 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 +79 -20
- data/lib/redis_queued_locks/acquirer/locks.rb +212 -83
- data/lib/redis_queued_locks/acquirer/queue_info.rb +11 -18
- data/lib/redis_queued_locks/acquirer/queues.rb +123 -50
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +253 -0
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +177 -61
- data/lib/redis_queued_locks/acquirer/release_lock.rb +84 -25
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +274 -70
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +164 -36
- data/lib/redis_queued_locks/config.rb +16 -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 +113 -6
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +333 -61
- 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/zombie_info.rb +203 -53
- data/lib/redis_queued_locks/swarm.rb +97 -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/try_to_lock.rbs +107 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -3
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +3 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +19 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +20 -4
- data/sig/redis_queued_locks/acquirer/keys.rbs +11 -1
- data/sig/redis_queued_locks/acquirer/locks.rbs +15 -6
- data/sig/redis_queued_locks/acquirer/queues.rbs +9 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +52 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +6 -3
- data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +21 -2
- 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 +7 -1
- data/sig/redis_queued_locks/swarm/flush_zombies.rbs +51 -1
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +58 -7
- data/sig/vendor/redis_client.rbs +2 -0
- metadata +6 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.14.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.20.0
|
|
6
6
|
# rubocop:disable Metrics/ModuleLength
|
|
7
7
|
module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
8
8
|
# @since 1.14.0
|
|
@@ -20,11 +20,16 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
20
20
|
# @param redis [RedisClient]
|
|
21
21
|
# Redis connection client.
|
|
22
22
|
# @param lock_scan_size [Integer]
|
|
23
|
-
# The number of lock keys
|
|
23
|
+
# The number of lock keys (the disabled lock index: the SCAN count of the single SCAN pass
|
|
24
|
+
# over the lock keys and lock queues) or locks (lock names, the enabled lock index: the ZSCAN
|
|
25
|
+
# count of the single ZSCAN pass over the lock index) that should be processed in a time.
|
|
24
26
|
# Affects the RubyVM memmory (cuz each found lock will be temporary stored in memory for
|
|
25
27
|
# subsequent removal from redis in one query at a time).
|
|
26
28
|
# @param queue_scan_size [Integer]
|
|
27
|
-
# The number of lock queues that should be
|
|
29
|
+
# The number of lock queues that should be processed removing an acquirer from them
|
|
30
|
+
# (in one round trip).
|
|
31
|
+
# @param lock_index_enabled [Boolean]
|
|
32
|
+
# Find locks via the lock index instead of the keyspace SCAN.
|
|
28
33
|
# @param logger [::Logger,#debug]
|
|
29
34
|
# - Logger object used from `configuration` layer (see config['logger']);
|
|
30
35
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
|
@@ -39,7 +44,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
39
44
|
# - works in tandem with <config['log_sampling_percent']> and <config['log_sampler']>;
|
|
40
45
|
# @param log_sampling_percent [Integer]
|
|
41
46
|
# - the percent of cases that should be logged;
|
|
42
|
-
# - take an effect when <config['
|
|
47
|
+
# - take an effect when <config['log_sampling_enabled']> is true;
|
|
43
48
|
# - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
|
|
44
49
|
# @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
|
45
50
|
# - percent-based log sampler that decides should be RQL case logged or not;
|
|
@@ -60,7 +65,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
60
65
|
# - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
|
|
61
66
|
# @param instr_sampling_percent [Integer]
|
|
62
67
|
# - the percent of cases that should be instrumented;
|
|
63
|
-
# - take an effect when <config['
|
|
68
|
+
# - take an effect when <config['instr_sampling_enabled']> is true;
|
|
64
69
|
# - works in tandem with <config['instr_sampling_enabled']>
|
|
65
70
|
# and <config['instr_sampler']> configs;
|
|
66
71
|
# @param instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
|
|
@@ -88,7 +93,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
88
93
|
#
|
|
89
94
|
# @api private
|
|
90
95
|
# @since 1.14.0
|
|
91
|
-
# @version 1.
|
|
96
|
+
# @version 1.21.0
|
|
92
97
|
# rubocop:disable Metrics/MethodLength
|
|
93
98
|
def release_locks_of(
|
|
94
99
|
refused_host_id,
|
|
@@ -96,6 +101,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
96
101
|
redis,
|
|
97
102
|
lock_scan_size,
|
|
98
103
|
queue_scan_size,
|
|
104
|
+
lock_index_enabled,
|
|
99
105
|
logger,
|
|
100
106
|
instrumenter,
|
|
101
107
|
instrument,
|
|
@@ -107,7 +113,23 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
107
113
|
instr_sampling_percent,
|
|
108
114
|
instr_sampler,
|
|
109
115
|
instr_sample_this
|
|
110
|
-
)
|
|
116
|
+
)
|
|
117
|
+
# NOTE: (arguments) the same ranges as the config keys: zero/negative sizes fail SCAN/ZSCAN
|
|
118
|
+
# (`COUNT 0`) and the batching (`each_slice(0)`);
|
|
119
|
+
if !lock_scan_size.is_a?(::Integer) || lock_scan_size <= 0
|
|
120
|
+
raise(
|
|
121
|
+
RedisQueuedLocks::ArgumentError,
|
|
122
|
+
"`:lock_scan_size` argument should be a positive Integer, got #{lock_scan_size.inspect}."
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
if !queue_scan_size.is_a?(::Integer) || queue_scan_size <= 0
|
|
126
|
+
raise(
|
|
127
|
+
RedisQueuedLocks::ArgumentError,
|
|
128
|
+
"`:queue_scan_size` argument should be a positive Integer, " \
|
|
129
|
+
"got #{queue_scan_size.inspect}."
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
111
133
|
rel_start_time = clock_gettime
|
|
112
134
|
|
|
113
135
|
# steep:ignore:start
|
|
@@ -116,7 +138,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
116
138
|
refused_acquirer_id,
|
|
117
139
|
redis,
|
|
118
140
|
lock_scan_size,
|
|
119
|
-
queue_scan_size
|
|
141
|
+
queue_scan_size,
|
|
142
|
+
lock_index_enabled
|
|
120
143
|
) => { ok:, result: }
|
|
121
144
|
# steep:ignore:end
|
|
122
145
|
|
|
@@ -127,6 +150,12 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
127
150
|
rel_end_time = clock_gettime
|
|
128
151
|
rel_time = ((rel_end_time - rel_start_time) / 1_000.0).ceil(2)
|
|
129
152
|
|
|
153
|
+
log_sampled = RedisQueuedLocks::Logging.should_log?(
|
|
154
|
+
log_sampling_enabled,
|
|
155
|
+
log_sample_this,
|
|
156
|
+
log_sampling_percent,
|
|
157
|
+
log_sampler
|
|
158
|
+
)
|
|
130
159
|
instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
|
|
131
160
|
instr_sampling_enabled,
|
|
132
161
|
instr_sample_this,
|
|
@@ -134,6 +163,18 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
134
163
|
instr_sampler
|
|
135
164
|
)
|
|
136
165
|
|
|
166
|
+
run_non_critical do
|
|
167
|
+
logger.debug do
|
|
168
|
+
"[redis_queued_locks.release_locks_of] " \
|
|
169
|
+
"at => #{time_at} " \
|
|
170
|
+
"hst_id => '#{refused_host_id}' " \
|
|
171
|
+
"acq_id => '#{refused_acquirer_id}' " \
|
|
172
|
+
"rel_time => #{rel_time} " \
|
|
173
|
+
"rel_key_cnt => #{result[:rel_key_cnt]} " \
|
|
174
|
+
"tch_queue_cnt => #{result[:tch_queue_cnt]}"
|
|
175
|
+
end
|
|
176
|
+
end if log_sampled
|
|
177
|
+
|
|
137
178
|
run_non_critical do
|
|
138
179
|
instrumenter.notify('redis_queued_locks.release_locks_of', {
|
|
139
180
|
at: time_at,
|
|
@@ -141,7 +182,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
141
182
|
acq_id: refused_acquirer_id,
|
|
142
183
|
rel_time: rel_time,
|
|
143
184
|
rel_key_cnt: result[:rel_key_cnt],
|
|
144
|
-
tch_queue_cnt: result[:tch_queue_cnt]
|
|
185
|
+
tch_queue_cnt: result[:tch_queue_cnt],
|
|
186
|
+
instrument: instrument
|
|
145
187
|
})
|
|
146
188
|
end if instr_sampled
|
|
147
189
|
|
|
@@ -158,104 +200,266 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
158
200
|
|
|
159
201
|
private
|
|
160
202
|
|
|
203
|
+
# Releases the locks and the lock requests of the passed host/acquirer found in one pass:
|
|
204
|
+
# - the disabled lock index: one SCAN pass over the RQL keys (`KEY_PATTERN`): the found keys
|
|
205
|
+
# are processed in batches with one round trip per batch (batches are collected across
|
|
206
|
+
# SCAN pages);
|
|
207
|
+
# - the enabled lock index: one ZSCAN pass over the lock index (`LOCK_INDEX_KEY`: lock names,
|
|
208
|
+
# the keyspace is never scanned): the keys of each batch of `lock_scan_size` lock names are
|
|
209
|
+
# derived from the lock names and processed with one round trip per key family and batch;
|
|
210
|
+
# the lock names stay in the lock index (other keys of the locks can exist);
|
|
211
|
+
# Batches:
|
|
212
|
+
# - write locks of the passed acquirer (`lock_scan_size` locks per batch);
|
|
213
|
+
# - (RW) read locks of the passed acquirer (`lock_scan_size` readers registries per batch);
|
|
214
|
+
# - requests of the passed acquirer in the lock queues (`queue_scan_size` queues per batch);
|
|
215
|
+
#
|
|
161
216
|
# @param refused_host_id [String]
|
|
162
217
|
# @param refused_acquirer_id [String]
|
|
163
218
|
# @param redis [RedisClient]
|
|
164
219
|
# @param lock_scan_size [Integer]
|
|
165
|
-
#
|
|
220
|
+
# SCAN count and the batch size of locks (the disabled lock index) or ZSCAN count and
|
|
221
|
+
# the batch size of locks (lock names, the enabled lock index).
|
|
222
|
+
# @param queue_scan_size [Integer] The batch size of lock queues.
|
|
223
|
+
# @param lock_index_enabled [Boolean]
|
|
166
224
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
|
|
167
225
|
# - Example: { ok: true, result: { rel_key_cnt: 12345, tch_queue_cnt: 321 } }
|
|
168
226
|
#
|
|
169
227
|
# @api private
|
|
170
228
|
# @since 1.14.0
|
|
171
|
-
# @version 1.
|
|
229
|
+
# @version 1.20.0
|
|
172
230
|
# rubocop:disable Metrics/MethodLength
|
|
173
231
|
def fully_release_locks_of(
|
|
174
232
|
refused_host_id,
|
|
175
233
|
refused_acquirer_id,
|
|
176
234
|
redis,
|
|
177
235
|
lock_scan_size,
|
|
178
|
-
queue_scan_size
|
|
236
|
+
queue_scan_size,
|
|
237
|
+
lock_index_enabled
|
|
179
238
|
)
|
|
180
|
-
# TODO: some indexing approach isntead of <scan>
|
|
181
239
|
rel_key_cnt = 0
|
|
182
240
|
tch_queue_cnt = 0
|
|
241
|
+
# NOTE: (RW) read locks store acquirer ids only (the host is a part of the acquirer id),
|
|
242
|
+
# so they are released only when the passed acquirer belongs to the passed host;
|
|
243
|
+
with_read_locks =
|
|
244
|
+
RedisQueuedLocks::Resource.host_identifier_from_acquirer(refused_acquirer_id) ==
|
|
245
|
+
refused_host_id
|
|
183
246
|
|
|
184
247
|
redis.with do |rconn|
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
) do |lock_key|
|
|
192
|
-
acquirer_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
|
|
193
|
-
if refused_host_id == host_id && refused_acquirer_id == acquirer_id
|
|
194
|
-
refused_locks << lock_key
|
|
195
|
-
end
|
|
248
|
+
# @type var lock_keys: Array[String]
|
|
249
|
+
lock_keys = []
|
|
250
|
+
# @type var lock_readers_keys: Array[String]
|
|
251
|
+
lock_readers_keys = []
|
|
252
|
+
# @type var lock_queues: Array[String]
|
|
253
|
+
lock_queues = []
|
|
196
254
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
255
|
+
if lock_index_enabled
|
|
256
|
+
# NOTE: (lock index) the keys of each batch of lock names are derived from the lock names
|
|
257
|
+
each_lock_name_batch(rconn, lock_scan_size) do |lock_names|
|
|
258
|
+
lock_names.each do |lock_name|
|
|
259
|
+
lock_keys << RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
260
|
+
if with_read_locks
|
|
261
|
+
lock_readers_keys << RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
|
|
262
|
+
end
|
|
263
|
+
lock_queues << RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
|
264
|
+
lock_queues << RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
|
|
265
|
+
end
|
|
204
266
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
267
|
+
# Step A: drop locks of the passed host/acquirer
|
|
268
|
+
rel_key_cnt += release_refused_locks(
|
|
269
|
+
rconn, lock_keys, refused_host_id, refused_acquirer_id
|
|
270
|
+
)
|
|
271
|
+
# Step A (RW): drop read locks of the passed host/acquirer
|
|
272
|
+
unless lock_readers_keys.empty?
|
|
273
|
+
rel_key_cnt += release_refused_read_locks(
|
|
274
|
+
rconn, lock_readers_keys, refused_acquirer_id
|
|
275
|
+
)
|
|
276
|
+
end
|
|
277
|
+
# Step B: drop passed host/acquirer from lock queues (write and read)
|
|
278
|
+
lock_queues.each_slice(queue_scan_size) do |lock_queues_batch|
|
|
279
|
+
tch_queue_cnt += dequeue_refused_acquirer(
|
|
280
|
+
rconn, lock_queues_batch, refused_acquirer_id
|
|
281
|
+
)
|
|
282
|
+
end
|
|
210
283
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
284
|
+
lock_keys.clear
|
|
285
|
+
lock_readers_keys.clear
|
|
286
|
+
lock_queues.clear
|
|
287
|
+
end
|
|
288
|
+
else
|
|
289
|
+
# NOTE: (keyspace) the found keys are collected into batches by their key families
|
|
215
290
|
rconn.scan(
|
|
216
291
|
'MATCH',
|
|
217
|
-
RedisQueuedLocks::Resource::
|
|
292
|
+
RedisQueuedLocks::Resource::KEY_PATTERN,
|
|
218
293
|
count: lock_scan_size
|
|
219
|
-
) do |
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
RedisQueuedLocks::Resource.lock_name_from_readers(lock_readers_key),
|
|
229
|
-
refused_acquirer_id
|
|
294
|
+
) do |key|
|
|
295
|
+
case RedisQueuedLocks::Resource.lock_key_family(key)
|
|
296
|
+
when :lock
|
|
297
|
+
# Step A: drop locks of the passed host/acquirer
|
|
298
|
+
lock_keys << key
|
|
299
|
+
next if lock_keys.size < lock_scan_size
|
|
300
|
+
|
|
301
|
+
rel_key_cnt += release_refused_locks(
|
|
302
|
+
rconn, lock_keys, refused_host_id, refused_acquirer_id
|
|
230
303
|
)
|
|
304
|
+
lock_keys.clear
|
|
305
|
+
when :lock_readers
|
|
306
|
+
# Step A (RW): drop read locks of the passed host/acquirer
|
|
307
|
+
next unless with_read_locks
|
|
308
|
+
|
|
309
|
+
lock_readers_keys << key
|
|
310
|
+
next if lock_readers_keys.size < lock_scan_size
|
|
311
|
+
|
|
312
|
+
rel_key_cnt += release_refused_read_locks(
|
|
313
|
+
rconn, lock_readers_keys, refused_acquirer_id
|
|
314
|
+
)
|
|
315
|
+
lock_readers_keys.clear
|
|
316
|
+
when :lock_queue, :read_lock_queue
|
|
317
|
+
# Step B: drop passed host/acquirer from lock queues (write and read)
|
|
318
|
+
lock_queues << key
|
|
319
|
+
next if lock_queues.size < queue_scan_size
|
|
320
|
+
|
|
321
|
+
tch_queue_cnt += dequeue_refused_acquirer(rconn, lock_queues, refused_acquirer_id)
|
|
322
|
+
lock_queues.clear
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
unless lock_keys.empty?
|
|
327
|
+
rel_key_cnt += release_refused_locks(
|
|
328
|
+
rconn, lock_keys, refused_host_id, refused_acquirer_id
|
|
231
329
|
)
|
|
232
330
|
end
|
|
331
|
+
unless lock_readers_keys.empty?
|
|
332
|
+
rel_key_cnt += release_refused_read_locks(
|
|
333
|
+
rconn, lock_readers_keys, refused_acquirer_id
|
|
334
|
+
)
|
|
335
|
+
end
|
|
336
|
+
unless lock_queues.empty?
|
|
337
|
+
tch_queue_cnt += dequeue_refused_acquirer(rconn, lock_queues, refused_acquirer_id)
|
|
338
|
+
end
|
|
233
339
|
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
{ ok: true, result: { rel_key_cnt:, tch_queue_cnt: } }
|
|
343
|
+
end
|
|
344
|
+
# rubocop:enable Metrics/MethodLength
|
|
234
345
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
346
|
+
# Drops the locks of the passed host/acquirer from the given batch of write locks
|
|
347
|
+
# (two round trips: lock owners, then one `DEL` of the refused locks).
|
|
348
|
+
#
|
|
349
|
+
# @param redis_client [RedisClient]
|
|
350
|
+
# @param lock_keys [Array<String>]
|
|
351
|
+
# @param refused_host_id [String]
|
|
352
|
+
# @param refused_acquirer_id [String]
|
|
353
|
+
# @return [Integer] The number of the refused locks.
|
|
354
|
+
#
|
|
355
|
+
# @api private
|
|
356
|
+
# @since 1.20.0
|
|
357
|
+
def release_refused_locks(redis_client, lock_keys, refused_host_id, refused_acquirer_id)
|
|
358
|
+
# NOTE: (pipeline layout) one reply (acquirer and host) per lock in the batch order
|
|
359
|
+
# @type var lock_owners: Array[[String?, String?]]
|
|
360
|
+
lock_owners = redis_client.pipelined do |pipeline|
|
|
361
|
+
lock_keys.each { |lock_key| pipeline.call('HMGET', lock_key, 'acq_id', 'hst_id') }
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# @type var refused_locks: Array[String]
|
|
365
|
+
refused_locks = []
|
|
366
|
+
lock_keys.each_with_index do |lock_key, index|
|
|
367
|
+
acquirer_id, host_id = lock_owners[index]
|
|
368
|
+
if refused_host_id == host_id && refused_acquirer_id == acquirer_id
|
|
369
|
+
refused_locks << lock_key
|
|
243
370
|
end
|
|
371
|
+
end
|
|
372
|
+
return 0 if refused_locks.empty?
|
|
244
373
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
374
|
+
redis_client.call('DEL', *refused_locks)
|
|
375
|
+
refused_locks.size
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# (RW) Drops the read locks of the passed acquirer from the given batch of readers registries
|
|
379
|
+
# in one round trip: `ZREM` of the acquirer and `DEL` of its read lock data for each registry
|
|
380
|
+
# (the read lock data is a data carrier only: `DEL` of an absent data is a no-op).
|
|
381
|
+
#
|
|
382
|
+
# @param redis_client [RedisClient]
|
|
383
|
+
# @param lock_readers_keys [Array<String>]
|
|
384
|
+
# @param refused_acquirer_id [String]
|
|
385
|
+
# @return [Integer] The number of the dropped read locks.
|
|
386
|
+
#
|
|
387
|
+
# @api private
|
|
388
|
+
# @since 1.20.0
|
|
389
|
+
def release_refused_read_locks(redis_client, lock_readers_keys, refused_acquirer_id)
|
|
390
|
+
# NOTE: (pipeline layout) two replies per registry in the batch order: ZREM, DEL
|
|
391
|
+
# @type var replies: Array[Integer]
|
|
392
|
+
replies = redis_client.pipelined do |pipeline|
|
|
393
|
+
lock_readers_keys.each do |lock_readers_key|
|
|
394
|
+
pipeline.call('ZREM', lock_readers_key, refused_acquirer_id)
|
|
395
|
+
pipeline.call(
|
|
396
|
+
'DEL',
|
|
397
|
+
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
398
|
+
RedisQueuedLocks::Resource.lock_name_from_readers(lock_readers_key),
|
|
399
|
+
refused_acquirer_id
|
|
400
|
+
)
|
|
401
|
+
)
|
|
253
402
|
end
|
|
254
403
|
end
|
|
255
404
|
|
|
256
|
-
|
|
405
|
+
released = 0
|
|
406
|
+
replies.each_slice(2) { |(zrem_cmd_res, _del_cmd_res)| released += 1 if zrem_cmd_res != 0 }
|
|
407
|
+
released
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Drops the requests of the passed acquirer from the given batch of lock queues
|
|
411
|
+
# (write and read) in one round trip.
|
|
412
|
+
#
|
|
413
|
+
# @param redis_client [RedisClient]
|
|
414
|
+
# @param lock_queues [Array<String>]
|
|
415
|
+
# @param refused_acquirer_id [String]
|
|
416
|
+
# @return [Integer] The number of the touched lock queues.
|
|
417
|
+
#
|
|
418
|
+
# @api private
|
|
419
|
+
# @since 1.20.0
|
|
420
|
+
def dequeue_refused_acquirer(redis_client, lock_queues, refused_acquirer_id)
|
|
421
|
+
# @type var replies: Array[Integer]
|
|
422
|
+
replies = redis_client.pipelined do |pipeline|
|
|
423
|
+
lock_queues.each { |lock_queue| pipeline.call('ZREM', lock_queue, refused_acquirer_id) }
|
|
424
|
+
end
|
|
425
|
+
replies.count { |zrem_cmd_res| zrem_cmd_res != 0 }
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
429
|
+
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
430
|
+
# the index yet) are skipped: all their keys are expired.
|
|
431
|
+
#
|
|
432
|
+
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
433
|
+
# operation modules are independent of each other, see `Locks.each_lock_name_batch`);
|
|
434
|
+
#
|
|
435
|
+
# @param redis_client [RedisClient]
|
|
436
|
+
# @param scan_size [Integer] ZSCAN count and the number of lock names in one batch.
|
|
437
|
+
# @yield [lock_names]
|
|
438
|
+
# @yieldparam lock_names [Array<String>] The batch of lock names (reused between batches).
|
|
439
|
+
# @return [void]
|
|
440
|
+
#
|
|
441
|
+
# @api private
|
|
442
|
+
# @since 1.20.0
|
|
443
|
+
def each_lock_name_batch(redis_client, scan_size)
|
|
444
|
+
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
445
|
+
# @type var lock_names: Array[String]
|
|
446
|
+
lock_names = []
|
|
447
|
+
|
|
448
|
+
redis_client.zscan(
|
|
449
|
+
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
450
|
+
) do |lock_name, lock_index_score|
|
|
451
|
+
next if lock_index_score.to_f < lock_index_time
|
|
452
|
+
|
|
453
|
+
lock_names << lock_name
|
|
454
|
+
next if lock_names.size < scan_size
|
|
455
|
+
|
|
456
|
+
yield(lock_names)
|
|
457
|
+
lock_names.clear
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
yield(lock_names) unless lock_names.empty?
|
|
257
461
|
end
|
|
258
462
|
end
|
|
259
|
-
# rubocop:enable Metrics/
|
|
463
|
+
# rubocop:enable Metrics/ClassLength
|
|
260
464
|
end
|
|
261
465
|
# rubocop:enable Metrics/ModuleLength
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.18.0
|
|
5
|
+
# @version 1.21.0
|
|
5
6
|
module RedisQueuedLocks::Acquirer::ReleaseReadLock
|
|
6
7
|
# @since 1.18.0
|
|
7
8
|
extend RedisQueuedLocks::Utilities
|
|
@@ -17,11 +18,11 @@ module RedisQueuedLocks::Acquirer::ReleaseReadLock
|
|
|
17
18
|
# The lock name whose read lock should be released.
|
|
18
19
|
# @param acquirer_id [String]
|
|
19
20
|
# The read lock acquirer.
|
|
20
|
-
# @param instrumenter [#notify]
|
|
21
|
-
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
|
22
21
|
# @param logger [::Logger,#debug]
|
|
23
22
|
# - Logger object used from `configuration` layer (see config['logger']);
|
|
24
23
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
|
24
|
+
# @param instrumenter [#notify]
|
|
25
|
+
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
|
25
26
|
# @param instrument [NilClass,Any]
|
|
26
27
|
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
|
27
28
|
# with :instrument key;
|
|
@@ -52,13 +53,14 @@ module RedisQueuedLocks::Acquirer::ReleaseReadLock
|
|
|
52
53
|
#
|
|
53
54
|
# @api private
|
|
54
55
|
# @since 1.18.0
|
|
56
|
+
# @version 1.21.0
|
|
55
57
|
# rubocop:disable Metrics/MethodLength
|
|
56
58
|
def release_read_lock(
|
|
57
59
|
redis,
|
|
58
60
|
lock_name,
|
|
59
61
|
acquirer_id,
|
|
60
|
-
instrumenter,
|
|
61
62
|
logger,
|
|
63
|
+
instrumenter,
|
|
62
64
|
instrument,
|
|
63
65
|
log_sampling_enabled,
|
|
64
66
|
log_sampling_percent,
|
|
@@ -87,6 +89,12 @@ module RedisQueuedLocks::Acquirer::ReleaseReadLock
|
|
|
87
89
|
rel_end_time = clock_gettime
|
|
88
90
|
rel_time = ((rel_end_time - rel_start_time) / 1_000.0).ceil(2)
|
|
89
91
|
|
|
92
|
+
log_sampled = RedisQueuedLocks::Logging.should_log?(
|
|
93
|
+
log_sampling_enabled,
|
|
94
|
+
log_sample_this,
|
|
95
|
+
log_sampling_percent,
|
|
96
|
+
log_sampler
|
|
97
|
+
)
|
|
90
98
|
instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
|
|
91
99
|
instr_sampling_enabled,
|
|
92
100
|
instr_sample_this,
|
|
@@ -94,6 +102,17 @@ module RedisQueuedLocks::Acquirer::ReleaseReadLock
|
|
|
94
102
|
instr_sampler
|
|
95
103
|
)
|
|
96
104
|
|
|
105
|
+
run_non_critical do
|
|
106
|
+
logger.debug do
|
|
107
|
+
"[redis_queued_locks.explicit_read_lock_release] " \
|
|
108
|
+
"lock_key => '#{lock_key}' " \
|
|
109
|
+
"acq_id => '#{acquirer_id}' " \
|
|
110
|
+
"lock_res => '#{result[:lock]}' " \
|
|
111
|
+
"rel_time => #{rel_time} " \
|
|
112
|
+
"at => #{time_at}"
|
|
113
|
+
end
|
|
114
|
+
end if log_sampled
|
|
115
|
+
|
|
97
116
|
run_non_critical do
|
|
98
117
|
instrumenter.notify('redis_queued_locks.explicit_read_lock_release', {
|
|
99
118
|
lock_key: lock_key,
|