redis_queued_locks 1.18.0 → 1.20.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 +65 -13
- data/.claude/rules/acquirer.md +29 -11
- data/.claude/rules/arguments.md +3 -2
- data/.claude/rules/git.md +3 -2
- data/.claude/rules/logic.md +4 -3
- data/.claude/rules/swarm.md +12 -7
- data/.claude/rules/tests.md +6 -0
- data/.claude/rules/type-checking.md +2 -1
- data/.claude/rules/visitors.md +1 -1
- data/.claude/skills/rql-code-review/SKILL.md +372 -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 +74 -0
- data/CLAUDE.md +4 -2
- data/README.md +338 -24
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +684 -128
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +3 -1
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +24 -3
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +145 -18
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +71 -10
- data/lib/redis_queued_locks/acquirer/keys.rb +142 -7
- data/lib/redis_queued_locks/acquirer/lock_info.rb +10 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +26 -7
- data/lib/redis_queued_locks/acquirer/locks.rb +195 -83
- data/lib/redis_queued_locks/acquirer/queue_info.rb +12 -18
- data/lib/redis_queued_locks/acquirer/queues.rb +114 -50
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +140 -0
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +147 -58
- data/lib/redis_queued_locks/acquirer/release_lock.rb +52 -19
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +235 -66
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +116 -16
- data/lib/redis_queued_locks/config.rb +5 -1
- data/lib/redis_queued_locks/resource.rb +99 -2
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +332 -61
- data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
- data/lib/redis_queued_locks/swarm.rb +15 -4
- data/lib/redis_queued_locks/version.rb +2 -2
- 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 +1 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +2 -0
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +10 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +5 -2
- 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 +31 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -1
- data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
- data/sig/redis_queued_locks/client.rbs +9 -0
- data/sig/redis_queued_locks/resource.rbs +6 -0
- 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 +1 -0
- metadata +6 -1
|
@@ -12,6 +12,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
12
12
|
# from the lock aquierment and should retry;
|
|
13
13
|
# - 2. delete the lock: drop lock key from Redis;
|
|
14
14
|
# - 3. (RW) clear the read lock queue and drop all read locks (the readers registry);
|
|
15
|
+
# - 4. (lock index) drop the lock name from the lock index (the enabled lock index only);
|
|
15
16
|
# It is safe because the lock obtain logic is transactional and
|
|
16
17
|
# watches the original lock for changes.
|
|
17
18
|
#
|
|
@@ -19,6 +20,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
19
20
|
# Redis connection client.
|
|
20
21
|
# @param lock_name [String]
|
|
21
22
|
# The lock name that should be released.
|
|
23
|
+
# @param lock_index_enabled [Boolean]
|
|
24
|
+
# Drop the lock name from the lock index (the enabled lock index).
|
|
22
25
|
# @param instrumenter [#notify]
|
|
23
26
|
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
|
24
27
|
# @param logger [::Logger,#debug]
|
|
@@ -72,11 +75,12 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
72
75
|
#
|
|
73
76
|
# @api private
|
|
74
77
|
# @since 1.0.0
|
|
75
|
-
# @version 1.
|
|
78
|
+
# @version 1.20.0
|
|
76
79
|
# rubocop:disable Metrics/MethodLength
|
|
77
80
|
def release_lock(
|
|
78
81
|
redis,
|
|
79
82
|
lock_name,
|
|
83
|
+
lock_index_enabled,
|
|
80
84
|
instrumenter,
|
|
81
85
|
logger,
|
|
82
86
|
log_sampling_enabled,
|
|
@@ -96,7 +100,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
96
100
|
rel_start_time = clock_gettime
|
|
97
101
|
# steep:ignore:start
|
|
98
102
|
fully_release_lock(
|
|
99
|
-
redis, lock_name, lock_key, lock_key_queue, read_lock_key_queue, lock_readers_key
|
|
103
|
+
redis, lock_name, lock_key, lock_key_queue, read_lock_key_queue, lock_readers_key,
|
|
104
|
+
lock_index_enabled
|
|
100
105
|
) => { ok:, result: }
|
|
101
106
|
# steep:ignore:end
|
|
102
107
|
|
|
@@ -138,7 +143,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
138
143
|
|
|
139
144
|
private
|
|
140
145
|
|
|
141
|
-
# Realease the lock: clear the lock queues and expire the lock (write lock and read locks)
|
|
146
|
+
# Realease the lock: clear the lock queues and expire the lock (write lock and read locks),
|
|
147
|
+
# drop the lock name from the lock index (the enabled lock index).
|
|
142
148
|
#
|
|
143
149
|
# @param redis [RedisClient]
|
|
144
150
|
# @param lock_name [String]
|
|
@@ -146,6 +152,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
146
152
|
# @param lock_key_queue [String]
|
|
147
153
|
# @param read_lock_key_queue [String]
|
|
148
154
|
# @param lock_readers_key [String]
|
|
155
|
+
# @param lock_index_enabled [Boolean]
|
|
149
156
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Symbol>>]
|
|
150
157
|
# Format: {
|
|
151
158
|
# ok: true/false,
|
|
@@ -157,43 +164,69 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
|
|
|
157
164
|
#
|
|
158
165
|
# @api private
|
|
159
166
|
# @since 1.0.0
|
|
160
|
-
# @version 1.
|
|
167
|
+
# @version 1.20.0
|
|
168
|
+
# rubocop:disable Metrics/MethodLength
|
|
161
169
|
def fully_release_lock(
|
|
162
170
|
redis,
|
|
163
171
|
lock_name,
|
|
164
172
|
lock_key,
|
|
165
173
|
lock_key_queue,
|
|
166
174
|
read_lock_key_queue,
|
|
167
|
-
lock_readers_key
|
|
175
|
+
lock_readers_key,
|
|
176
|
+
lock_index_enabled
|
|
168
177
|
)
|
|
169
|
-
# @type var result: [Integer,Integer,Integer,Integer]
|
|
178
|
+
# @type var result: [Array[String],Integer,Integer,Integer,Integer]
|
|
170
179
|
result = redis.with do |rconn|
|
|
171
|
-
#
|
|
172
|
-
# the readers registry
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
# Step 1: release the lock in one transaction (one round trip):
|
|
181
|
+
# - (RW) the readers registry is read in the same transaction: the snapshot of all
|
|
182
|
+
# released read locks (their data is dropped in Step 2);
|
|
183
|
+
# - (lock index) the enabled lock index: all the indexed keys of the lock are dropped,
|
|
184
|
+
# so the lock name is dropped from the lock index in the same transaction (a lock
|
|
185
|
+
# request queued or a lock obtained after this transaction indexes the lock name again);
|
|
186
|
+
# NOTE: (transaction layout) replies are read by their positions:
|
|
187
|
+
# [0] ZRANGE (released read locks), [1] write lock queue, [2] write lock,
|
|
188
|
+
# [3] read lock queue, [4] readers registry, [5] lock index (the enabled lock index
|
|
189
|
+
# only, the last command: not read);
|
|
190
|
+
released = rconn.multi do |transact|
|
|
191
|
+
transact.call('ZRANGE', lock_readers_key, '0', '-1')
|
|
177
192
|
transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
|
|
178
193
|
transact.call('EXPIRE', lock_key, '0')
|
|
179
194
|
transact.call('ZREMRANGEBYSCORE', read_lock_key_queue, '-inf', '+inf')
|
|
180
195
|
transact.call('EXPIRE', lock_readers_key, '0')
|
|
181
|
-
|
|
182
|
-
transact.call(
|
|
183
|
-
'DEL',
|
|
184
|
-
RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
185
|
-
)
|
|
196
|
+
if lock_index_enabled
|
|
197
|
+
transact.call('ZREM', RedisQueuedLocks::Resource::LOCK_INDEX_KEY, lock_name)
|
|
186
198
|
end
|
|
187
199
|
end
|
|
200
|
+
|
|
201
|
+
# Step 2: (RW) drop the data of the released read locks (only when read locks existed)
|
|
202
|
+
# NOTE:
|
|
203
|
+
# - the read lock data is a data carrier only: read locks are released via the readers
|
|
204
|
+
# registry (Step 1);
|
|
205
|
+
# - (trade-off) a reader of the snapshot that obtains a new read lock between Step 1 and
|
|
206
|
+
# Step 2 loses the data of the new read lock (meta, timestamps); the read lock itself is
|
|
207
|
+
# not affected (the readers registry is the source of truth);
|
|
208
|
+
# @type var read_lock_acquirers: Array[String]
|
|
209
|
+
read_lock_acquirers = released[0]
|
|
210
|
+
unless read_lock_acquirers.empty?
|
|
211
|
+
rconn.call(
|
|
212
|
+
'DEL',
|
|
213
|
+
*read_lock_acquirers.map do |acquirer_id|
|
|
214
|
+
RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
215
|
+
end
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
released
|
|
188
220
|
end
|
|
189
221
|
|
|
190
222
|
{
|
|
191
223
|
ok: true,
|
|
192
224
|
result: {
|
|
193
|
-
queue: (result[
|
|
194
|
-
lock: (result[
|
|
225
|
+
queue: (result[1] != 0 || result[3] != 0) ? :released : :nothing_to_release,
|
|
226
|
+
lock: (result[2] != 0 || result[4] != 0) ? :released : :nothing_to_release
|
|
195
227
|
}
|
|
196
228
|
}
|
|
197
229
|
end
|
|
230
|
+
# rubocop:enable Metrics/MethodLength
|
|
198
231
|
end
|
|
199
232
|
end
|
|
@@ -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;
|
|
@@ -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.20.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,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
107
113
|
instr_sampling_percent,
|
|
108
114
|
instr_sampler,
|
|
109
115
|
instr_sample_this
|
|
110
|
-
)
|
|
116
|
+
)
|
|
111
117
|
rel_start_time = clock_gettime
|
|
112
118
|
|
|
113
119
|
# steep:ignore:start
|
|
@@ -116,7 +122,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
116
122
|
refused_acquirer_id,
|
|
117
123
|
redis,
|
|
118
124
|
lock_scan_size,
|
|
119
|
-
queue_scan_size
|
|
125
|
+
queue_scan_size,
|
|
126
|
+
lock_index_enabled
|
|
120
127
|
) => { ok:, result: }
|
|
121
128
|
# steep:ignore:end
|
|
122
129
|
|
|
@@ -158,104 +165,266 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
158
165
|
|
|
159
166
|
private
|
|
160
167
|
|
|
168
|
+
# Releases the locks and the lock requests of the passed host/acquirer found in one pass:
|
|
169
|
+
# - the disabled lock index: one SCAN pass over the RQL keys (`KEY_PATTERN`): the found keys
|
|
170
|
+
# are processed in batches with one round trip per batch (batches are collected across
|
|
171
|
+
# SCAN pages);
|
|
172
|
+
# - the enabled lock index: one ZSCAN pass over the lock index (`LOCK_INDEX_KEY`: lock names,
|
|
173
|
+
# the keyspace is never scanned): the keys of each batch of `lock_scan_size` lock names are
|
|
174
|
+
# derived from the lock names and processed with one round trip per key family and batch;
|
|
175
|
+
# the lock names stay in the lock index (other keys of the locks can exist);
|
|
176
|
+
# Batches:
|
|
177
|
+
# - write locks of the passed acquirer (`lock_scan_size` locks per batch);
|
|
178
|
+
# - (RW) read locks of the passed acquirer (`lock_scan_size` readers registries per batch);
|
|
179
|
+
# - requests of the passed acquirer in the lock queues (`queue_scan_size` queues per batch);
|
|
180
|
+
#
|
|
161
181
|
# @param refused_host_id [String]
|
|
162
182
|
# @param refused_acquirer_id [String]
|
|
163
183
|
# @param redis [RedisClient]
|
|
164
184
|
# @param lock_scan_size [Integer]
|
|
165
|
-
#
|
|
185
|
+
# SCAN count and the batch size of locks (the disabled lock index) or ZSCAN count and
|
|
186
|
+
# the batch size of locks (lock names, the enabled lock index).
|
|
187
|
+
# @param queue_scan_size [Integer] The batch size of lock queues.
|
|
188
|
+
# @param lock_index_enabled [Boolean]
|
|
166
189
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
|
|
167
190
|
# - Example: { ok: true, result: { rel_key_cnt: 12345, tch_queue_cnt: 321 } }
|
|
168
191
|
#
|
|
169
192
|
# @api private
|
|
170
193
|
# @since 1.14.0
|
|
171
|
-
# @version 1.
|
|
194
|
+
# @version 1.20.0
|
|
172
195
|
# rubocop:disable Metrics/MethodLength
|
|
173
196
|
def fully_release_locks_of(
|
|
174
197
|
refused_host_id,
|
|
175
198
|
refused_acquirer_id,
|
|
176
199
|
redis,
|
|
177
200
|
lock_scan_size,
|
|
178
|
-
queue_scan_size
|
|
201
|
+
queue_scan_size,
|
|
202
|
+
lock_index_enabled
|
|
179
203
|
)
|
|
180
|
-
# TODO: some indexing approach isntead of <scan>
|
|
181
204
|
rel_key_cnt = 0
|
|
182
205
|
tch_queue_cnt = 0
|
|
206
|
+
# NOTE: (RW) read locks store acquirer ids only (the host is a part of the acquirer id),
|
|
207
|
+
# so they are released only when the passed acquirer belongs to the passed host;
|
|
208
|
+
with_read_locks =
|
|
209
|
+
RedisQueuedLocks::Resource.host_identifier_from_acquirer(refused_acquirer_id) ==
|
|
210
|
+
refused_host_id
|
|
183
211
|
|
|
184
212
|
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
|
|
213
|
+
# @type var lock_keys: Array[String]
|
|
214
|
+
lock_keys = []
|
|
215
|
+
# @type var lock_readers_keys: Array[String]
|
|
216
|
+
lock_readers_keys = []
|
|
217
|
+
# @type var lock_queues: Array[String]
|
|
218
|
+
lock_queues = []
|
|
196
219
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
if lock_index_enabled
|
|
221
|
+
# NOTE: (lock index) the keys of each batch of lock names are derived from the lock names
|
|
222
|
+
each_lock_name_batch(rconn, lock_scan_size) do |lock_names|
|
|
223
|
+
lock_names.each do |lock_name|
|
|
224
|
+
lock_keys << RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
225
|
+
if with_read_locks
|
|
226
|
+
lock_readers_keys << RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
|
|
227
|
+
end
|
|
228
|
+
lock_queues << RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
|
229
|
+
lock_queues << RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
|
|
230
|
+
end
|
|
204
231
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
232
|
+
# Step A: drop locks of the passed host/acquirer
|
|
233
|
+
rel_key_cnt += release_refused_locks(
|
|
234
|
+
rconn, lock_keys, refused_host_id, refused_acquirer_id
|
|
235
|
+
)
|
|
236
|
+
# Step A (RW): drop read locks of the passed host/acquirer
|
|
237
|
+
unless lock_readers_keys.empty?
|
|
238
|
+
rel_key_cnt += release_refused_read_locks(
|
|
239
|
+
rconn, lock_readers_keys, refused_acquirer_id
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
# Step B: drop passed host/acquirer from lock queues (write and read)
|
|
243
|
+
lock_queues.each_slice(queue_scan_size) do |lock_queues_batch|
|
|
244
|
+
tch_queue_cnt += dequeue_refused_acquirer(
|
|
245
|
+
rconn, lock_queues_batch, refused_acquirer_id
|
|
246
|
+
)
|
|
247
|
+
end
|
|
210
248
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
lock_keys.clear
|
|
250
|
+
lock_readers_keys.clear
|
|
251
|
+
lock_queues.clear
|
|
252
|
+
end
|
|
253
|
+
else
|
|
254
|
+
# NOTE: (keyspace) the found keys are collected into batches by their key families
|
|
215
255
|
rconn.scan(
|
|
216
256
|
'MATCH',
|
|
217
|
-
RedisQueuedLocks::Resource::
|
|
257
|
+
RedisQueuedLocks::Resource::KEY_PATTERN,
|
|
218
258
|
count: lock_scan_size
|
|
219
|
-
) do |
|
|
220
|
-
|
|
221
|
-
|
|
259
|
+
) do |key|
|
|
260
|
+
case RedisQueuedLocks::Resource.lock_key_family(key)
|
|
261
|
+
when :lock
|
|
262
|
+
# Step A: drop locks of the passed host/acquirer
|
|
263
|
+
lock_keys << key
|
|
264
|
+
next if lock_keys.size < lock_scan_size
|
|
265
|
+
|
|
266
|
+
rel_key_cnt += release_refused_locks(
|
|
267
|
+
rconn, lock_keys, refused_host_id, refused_acquirer_id
|
|
268
|
+
)
|
|
269
|
+
lock_keys.clear
|
|
270
|
+
when :lock_readers
|
|
271
|
+
# Step A (RW): drop read locks of the passed host/acquirer
|
|
272
|
+
next unless with_read_locks
|
|
273
|
+
|
|
274
|
+
lock_readers_keys << key
|
|
275
|
+
next if lock_readers_keys.size < lock_scan_size
|
|
222
276
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
rconn.call(
|
|
226
|
-
'DEL',
|
|
227
|
-
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
228
|
-
RedisQueuedLocks::Resource.lock_name_from_readers(lock_readers_key),
|
|
229
|
-
refused_acquirer_id
|
|
277
|
+
rel_key_cnt += release_refused_read_locks(
|
|
278
|
+
rconn, lock_readers_keys, refused_acquirer_id
|
|
230
279
|
)
|
|
280
|
+
lock_readers_keys.clear
|
|
281
|
+
when :lock_queue, :read_lock_queue
|
|
282
|
+
# Step B: drop passed host/acquirer from lock queues (write and read)
|
|
283
|
+
lock_queues << key
|
|
284
|
+
next if lock_queues.size < queue_scan_size
|
|
285
|
+
|
|
286
|
+
tch_queue_cnt += dequeue_refused_acquirer(rconn, lock_queues, refused_acquirer_id)
|
|
287
|
+
lock_queues.clear
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
unless lock_keys.empty?
|
|
292
|
+
rel_key_cnt += release_refused_locks(
|
|
293
|
+
rconn, lock_keys, refused_host_id, refused_acquirer_id
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
unless lock_readers_keys.empty?
|
|
297
|
+
rel_key_cnt += release_refused_read_locks(
|
|
298
|
+
rconn, lock_readers_keys, refused_acquirer_id
|
|
231
299
|
)
|
|
232
300
|
end
|
|
301
|
+
unless lock_queues.empty?
|
|
302
|
+
tch_queue_cnt += dequeue_refused_acquirer(rconn, lock_queues, refused_acquirer_id)
|
|
303
|
+
end
|
|
233
304
|
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
{ ok: true, result: { rel_key_cnt:, tch_queue_cnt: } }
|
|
308
|
+
end
|
|
309
|
+
# rubocop:enable Metrics/MethodLength
|
|
310
|
+
|
|
311
|
+
# Drops the locks of the passed host/acquirer from the given batch of write locks
|
|
312
|
+
# (two round trips: lock owners, then one `DEL` of the refused locks).
|
|
313
|
+
#
|
|
314
|
+
# @param redis_client [RedisClient]
|
|
315
|
+
# @param lock_keys [Array<String>]
|
|
316
|
+
# @param refused_host_id [String]
|
|
317
|
+
# @param refused_acquirer_id [String]
|
|
318
|
+
# @return [Integer] The number of the refused locks.
|
|
319
|
+
#
|
|
320
|
+
# @api private
|
|
321
|
+
# @since 1.20.0
|
|
322
|
+
def release_refused_locks(redis_client, lock_keys, refused_host_id, refused_acquirer_id)
|
|
323
|
+
# NOTE: (pipeline layout) one reply (acquirer and host) per lock in the batch order
|
|
324
|
+
# @type var lock_owners: Array[[String?, String?]]
|
|
325
|
+
lock_owners = redis_client.pipelined do |pipeline|
|
|
326
|
+
lock_keys.each { |lock_key| pipeline.call('HMGET', lock_key, 'acq_id', 'hst_id') }
|
|
327
|
+
end
|
|
234
328
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
res = rconn.call('ZREM', lock_queue, refused_acquirer_id)
|
|
242
|
-
tch_queue_cnt += 1 if res != 0
|
|
329
|
+
# @type var refused_locks: Array[String]
|
|
330
|
+
refused_locks = []
|
|
331
|
+
lock_keys.each_with_index do |lock_key, index|
|
|
332
|
+
acquirer_id, host_id = lock_owners[index]
|
|
333
|
+
if refused_host_id == host_id && refused_acquirer_id == acquirer_id
|
|
334
|
+
refused_locks << lock_key
|
|
243
335
|
end
|
|
336
|
+
end
|
|
337
|
+
return 0 if refused_locks.empty?
|
|
244
338
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
339
|
+
redis_client.call('DEL', *refused_locks)
|
|
340
|
+
refused_locks.size
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# (RW) Drops the read locks of the passed acquirer from the given batch of readers registries
|
|
344
|
+
# in one round trip: `ZREM` of the acquirer and `DEL` of its read lock data for each registry
|
|
345
|
+
# (the read lock data is a data carrier only: `DEL` of an absent data is a no-op).
|
|
346
|
+
#
|
|
347
|
+
# @param redis_client [RedisClient]
|
|
348
|
+
# @param lock_readers_keys [Array<String>]
|
|
349
|
+
# @param refused_acquirer_id [String]
|
|
350
|
+
# @return [Integer] The number of the dropped read locks.
|
|
351
|
+
#
|
|
352
|
+
# @api private
|
|
353
|
+
# @since 1.20.0
|
|
354
|
+
def release_refused_read_locks(redis_client, lock_readers_keys, refused_acquirer_id)
|
|
355
|
+
# NOTE: (pipeline layout) two replies per registry in the batch order: ZREM, DEL
|
|
356
|
+
# @type var replies: Array[Integer]
|
|
357
|
+
replies = redis_client.pipelined do |pipeline|
|
|
358
|
+
lock_readers_keys.each do |lock_readers_key|
|
|
359
|
+
pipeline.call('ZREM', lock_readers_key, refused_acquirer_id)
|
|
360
|
+
pipeline.call(
|
|
361
|
+
'DEL',
|
|
362
|
+
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
363
|
+
RedisQueuedLocks::Resource.lock_name_from_readers(lock_readers_key),
|
|
364
|
+
refused_acquirer_id
|
|
365
|
+
)
|
|
366
|
+
)
|
|
253
367
|
end
|
|
254
368
|
end
|
|
255
369
|
|
|
256
|
-
|
|
370
|
+
released = 0
|
|
371
|
+
replies.each_slice(2) { |(zrem_cmd_res, _del_cmd_res)| released += 1 if zrem_cmd_res != 0 }
|
|
372
|
+
released
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Drops the requests of the passed acquirer from the given batch of lock queues
|
|
376
|
+
# (write and read) in one round trip.
|
|
377
|
+
#
|
|
378
|
+
# @param redis_client [RedisClient]
|
|
379
|
+
# @param lock_queues [Array<String>]
|
|
380
|
+
# @param refused_acquirer_id [String]
|
|
381
|
+
# @return [Integer] The number of the touched lock queues.
|
|
382
|
+
#
|
|
383
|
+
# @api private
|
|
384
|
+
# @since 1.20.0
|
|
385
|
+
def dequeue_refused_acquirer(redis_client, lock_queues, refused_acquirer_id)
|
|
386
|
+
# @type var replies: Array[Integer]
|
|
387
|
+
replies = redis_client.pipelined do |pipeline|
|
|
388
|
+
lock_queues.each { |lock_queue| pipeline.call('ZREM', lock_queue, refused_acquirer_id) }
|
|
389
|
+
end
|
|
390
|
+
replies.count { |zrem_cmd_res| zrem_cmd_res != 0 }
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
394
|
+
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
395
|
+
# the index yet) are skipped: all their keys are expired.
|
|
396
|
+
#
|
|
397
|
+
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
398
|
+
# operation modules are independent of each other, see `Locks.each_lock_name_batch`);
|
|
399
|
+
#
|
|
400
|
+
# @param redis_client [RedisClient]
|
|
401
|
+
# @param scan_size [Integer] ZSCAN count and the number of lock names in one batch.
|
|
402
|
+
# @yield [lock_names]
|
|
403
|
+
# @yieldparam lock_names [Array<String>] The batch of lock names (reused between batches).
|
|
404
|
+
# @return [void]
|
|
405
|
+
#
|
|
406
|
+
# @api private
|
|
407
|
+
# @since 1.20.0
|
|
408
|
+
def each_lock_name_batch(redis_client, scan_size)
|
|
409
|
+
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
410
|
+
# @type var lock_names: Array[String]
|
|
411
|
+
lock_names = []
|
|
412
|
+
|
|
413
|
+
redis_client.zscan(
|
|
414
|
+
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
415
|
+
) do |lock_name, lock_index_score|
|
|
416
|
+
next if lock_index_score.to_f < lock_index_time
|
|
417
|
+
|
|
418
|
+
lock_names << lock_name
|
|
419
|
+
next if lock_names.size < scan_size
|
|
420
|
+
|
|
421
|
+
yield(lock_names)
|
|
422
|
+
lock_names.clear
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
yield(lock_names) unless lock_names.empty?
|
|
257
426
|
end
|
|
258
427
|
end
|
|
259
|
-
# rubocop:enable Metrics/
|
|
428
|
+
# rubocop:enable Metrics/ClassLength
|
|
260
429
|
end
|
|
261
430
|
# rubocop:enable Metrics/ModuleLength
|