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
|
@@ -9,6 +9,8 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
9
9
|
# @param zombie_ttl [Integer]
|
|
10
10
|
# @param lock_scan_size [Integer]
|
|
11
11
|
# @param queue_scan_size [Integer]
|
|
12
|
+
# @param lock_index_enabled [Boolean]
|
|
13
|
+
# Find zombie locks and requests via the lock index instead of the keyspace SCAN.
|
|
12
14
|
# @return [Hash<Symbol,Boolean|Set<String>]] Format:
|
|
13
15
|
# {
|
|
14
16
|
# ok: <Boolean>,
|
|
@@ -19,13 +21,14 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
19
21
|
#
|
|
20
22
|
# @api private
|
|
21
23
|
# @since 1.9.0
|
|
22
|
-
# @version 1.
|
|
24
|
+
# @version 1.20.0
|
|
23
25
|
# rubocop:disable Metrics/MethodLength
|
|
24
26
|
def flush_zombies(
|
|
25
27
|
redis_client,
|
|
26
28
|
zombie_ttl,
|
|
27
29
|
lock_scan_size,
|
|
28
|
-
queue_scan_size
|
|
30
|
+
queue_scan_size,
|
|
31
|
+
lock_index_enabled
|
|
29
32
|
)
|
|
30
33
|
redis_client.with do |rconn|
|
|
31
34
|
# Step 1:
|
|
@@ -47,43 +50,306 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
47
50
|
deleted_zombie_locks: Set.new
|
|
48
51
|
} if zombie_hosts.empty?
|
|
49
52
|
|
|
50
|
-
# Step 3: find zombie locks held by zombies and delete them
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
53
|
+
# Step 3: find zombie locks and zombie requests held by zombies and delete them in one pass
|
|
54
|
+
# (the keyspace SCAN or the lock index ZSCAN): write locks and readers registries are
|
|
55
|
+
# processed in batches of `lock_scan_size` locks, lock queues - in batches of
|
|
56
|
+
# `queue_scan_size` queues (two round trips per batch);
|
|
54
57
|
zombie_locks = Set.new #: Set[String]
|
|
55
58
|
zombie_acquirers = Set.new #: Set[String]
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
if lock_index_enabled
|
|
61
|
+
flush_lock_index_zombies(
|
|
62
|
+
rconn, zombie_hosts, lock_scan_size, queue_scan_size, zombie_locks, zombie_acquirers
|
|
63
|
+
)
|
|
64
|
+
else
|
|
65
|
+
flush_keyspace_zombies(
|
|
66
|
+
rconn, zombie_hosts, lock_scan_size, queue_scan_size, zombie_locks, zombie_acquirers
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Step 4: drop zombies from the swarm
|
|
71
|
+
rconn.call('HDEL', RedisQueuedLocks::Resource::SWARM_KEY, *zombie_hosts)
|
|
72
|
+
|
|
73
|
+
# Step 5: inform about deleted zombies
|
|
74
|
+
{
|
|
75
|
+
ok: true,
|
|
76
|
+
deleted_zombie_hosts: zombie_hosts,
|
|
77
|
+
deleted_zombie_acquirers: zombie_acquirers,
|
|
78
|
+
deleted_zombie_locks: zombie_locks
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
# rubocop:enable Metrics/MethodLength
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# (keyspace) Finds zombie locks (write and read) and zombie requests in one SCAN pass over
|
|
87
|
+
# the RQL keys (`KEY_PATTERN`) and deletes them (the disabled lock index):
|
|
88
|
+
# - write locks and readers registries are processed in batches of `lock_scan_size` keys
|
|
89
|
+
# (two round trips per batch, batches are collected across SCAN pages);
|
|
90
|
+
# - lock queues are collected during the pass and processed after it in batches of
|
|
91
|
+
# `queue_scan_size` queues (two round trips per batch);
|
|
92
|
+
#
|
|
93
|
+
# @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
|
|
94
|
+
# @param zombie_hosts [Set<String>]
|
|
95
|
+
# @param lock_scan_size [Integer] SCAN count and the batch size of locks.
|
|
96
|
+
# @param queue_scan_size [Integer] The batch size of lock queues.
|
|
97
|
+
# @param zombie_locks [Set<String>] Collects dropped zombie locks.
|
|
98
|
+
# @param zombie_acquirers [Set<String>] Collects acquirers of the dropped zombie locks/requests.
|
|
99
|
+
# @return [void]
|
|
100
|
+
#
|
|
101
|
+
# @api private
|
|
102
|
+
# @since 1.20.0
|
|
103
|
+
# rubocop:disable Metrics/MethodLength
|
|
104
|
+
def flush_keyspace_zombies(
|
|
105
|
+
rconn,
|
|
106
|
+
zombie_hosts,
|
|
107
|
+
lock_scan_size,
|
|
108
|
+
queue_scan_size,
|
|
109
|
+
zombie_locks,
|
|
110
|
+
zombie_acquirers
|
|
111
|
+
)
|
|
112
|
+
# @type var lock_keys: Array[String]
|
|
113
|
+
lock_keys = []
|
|
114
|
+
# @type var lock_readers_keys: Array[String]
|
|
115
|
+
lock_readers_keys = []
|
|
116
|
+
# @type var lock_queues: Array[String]
|
|
117
|
+
lock_queues = []
|
|
118
|
+
|
|
119
|
+
rconn.scan(
|
|
120
|
+
'MATCH', RedisQueuedLocks::Resource::KEY_PATTERN, count: lock_scan_size
|
|
121
|
+
) do |key|
|
|
122
|
+
case RedisQueuedLocks::Resource.lock_key_family(key)
|
|
123
|
+
when :lock
|
|
124
|
+
lock_keys << key
|
|
125
|
+
next if lock_keys.size < lock_scan_size
|
|
126
|
+
|
|
127
|
+
flush_zombie_locks(rconn, lock_keys, zombie_hosts, zombie_locks, zombie_acquirers)
|
|
128
|
+
lock_keys.clear
|
|
129
|
+
when :lock_readers
|
|
130
|
+
# NOTE: (RW) zombie read locks (read locks of zombie hosts)
|
|
131
|
+
lock_readers_keys << key
|
|
132
|
+
next if lock_readers_keys.size < lock_scan_size
|
|
133
|
+
|
|
134
|
+
flush_zombie_read_locks(
|
|
135
|
+
rconn, lock_readers_keys, zombie_hosts, zombie_locks, zombie_acquirers
|
|
136
|
+
)
|
|
137
|
+
lock_readers_keys.clear
|
|
138
|
+
when :lock_queue, :read_lock_queue
|
|
139
|
+
lock_queues << key
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
unless lock_keys.empty?
|
|
144
|
+
flush_zombie_locks(rconn, lock_keys, zombie_hosts, zombie_locks, zombie_acquirers)
|
|
145
|
+
end
|
|
146
|
+
unless lock_readers_keys.empty?
|
|
147
|
+
flush_zombie_read_locks(
|
|
148
|
+
rconn, lock_readers_keys, zombie_hosts, zombie_locks, zombie_acquirers
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# NOTE: drop zombie requests from the lock queues (write and read)
|
|
153
|
+
# - requests of zombie hosts: the host is derived from the acquirer id of the request,
|
|
154
|
+
# so the requests of zombie acquirers that hold no locks are dropped too (and their
|
|
155
|
+
# acquirers are reported as zombie acquirers);
|
|
156
|
+
# - two round trips per batch of `queue_scan_size` queues (requests, then ZREM);
|
|
157
|
+
lock_queues.each_slice(queue_scan_size) do |lock_queues_batch|
|
|
158
|
+
flush_zombie_requests(rconn, lock_queues_batch, zombie_hosts, zombie_acquirers)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
# rubocop:enable Metrics/MethodLength
|
|
162
|
+
|
|
163
|
+
# (lock index) Finds zombie locks (write and read) and zombie requests in one ZSCAN pass over
|
|
164
|
+
# the lock index (`LOCK_INDEX_KEY`: lock names) and deletes them (the enabled lock index):
|
|
165
|
+
# - expired lock names are dropped from the lock index first: all the keys of these locks are
|
|
166
|
+
# expired (they are not visited by the index pass);
|
|
167
|
+
# - lock keys are derived from the lock names instead of the full keyspace SCAN (keys of other
|
|
168
|
+
# applications are never visited);
|
|
169
|
+
# - write locks and readers registries are processed in batches of `lock_scan_size` locks
|
|
170
|
+
# (two round trips per batch, batches are collected across ZSCAN pages);
|
|
171
|
+
# - lock queues (write and read) are processed in batches of `queue_scan_size` queues during
|
|
172
|
+
# the same pass (two round trips per batch): zombie requests are found by the host of
|
|
173
|
+
# the request, so they do not depend on the zombie locks;
|
|
174
|
+
#
|
|
175
|
+
# @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
|
|
176
|
+
# @param zombie_hosts [Set<String>]
|
|
177
|
+
# @param lock_scan_size [Integer] ZSCAN count and the batch size of locks (lock names).
|
|
178
|
+
# @param queue_scan_size [Integer] The batch size of lock queues.
|
|
179
|
+
# @param zombie_locks [Set<String>] Collects dropped zombie locks.
|
|
180
|
+
# @param zombie_acquirers [Set<String>] Collects acquirers of the dropped zombie locks/requests.
|
|
181
|
+
# @return [void]
|
|
182
|
+
#
|
|
183
|
+
# @api private
|
|
184
|
+
# @since 1.20.0
|
|
185
|
+
# rubocop:disable Metrics/MethodLength
|
|
186
|
+
def flush_lock_index_zombies(
|
|
187
|
+
rconn,
|
|
188
|
+
zombie_hosts,
|
|
189
|
+
lock_scan_size,
|
|
190
|
+
queue_scan_size,
|
|
191
|
+
zombie_locks,
|
|
192
|
+
zombie_acquirers
|
|
193
|
+
)
|
|
194
|
+
# NOTE: drop the expired lock names from the lock index
|
|
195
|
+
rconn.call(
|
|
196
|
+
'ZREMRANGEBYSCORE', RedisQueuedLocks::Resource::LOCK_INDEX_KEY,
|
|
197
|
+
'-inf', RedisQueuedLocks::Resource.lock_index_time - 1
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# @type var lock_keys: Array[String]
|
|
201
|
+
lock_keys = []
|
|
202
|
+
# @type var lock_readers_keys: Array[String]
|
|
203
|
+
lock_readers_keys = []
|
|
204
|
+
# @type var lock_queues: Array[String]
|
|
205
|
+
lock_queues = []
|
|
206
|
+
|
|
207
|
+
rconn.zscan(
|
|
208
|
+
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: lock_scan_size
|
|
209
|
+
) do |lock_name|
|
|
210
|
+
lock_keys << RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
211
|
+
# NOTE: (RW) zombie read locks (read locks of zombie hosts)
|
|
212
|
+
lock_readers_keys << RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
|
|
213
|
+
if lock_keys.size >= lock_scan_size
|
|
214
|
+
flush_zombie_locks(rconn, lock_keys, zombie_hosts, zombie_locks, zombie_acquirers)
|
|
215
|
+
flush_zombie_read_locks(
|
|
216
|
+
rconn, lock_readers_keys, zombie_hosts, zombie_locks, zombie_acquirers
|
|
217
|
+
)
|
|
218
|
+
lock_keys.clear
|
|
219
|
+
lock_readers_keys.clear
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# NOTE: drop zombie requests from the lock queues (write and read)
|
|
223
|
+
# - requests of zombie hosts: the host is derived from the acquirer id of the request,
|
|
224
|
+
# so the requests of zombie acquirers that hold no locks are dropped too (and their
|
|
225
|
+
# acquirers are reported as zombie acquirers);
|
|
226
|
+
batch_zombie_requests(
|
|
227
|
+
rconn, lock_queues, RedisQueuedLocks::Resource.prepare_lock_queue(lock_name),
|
|
228
|
+
queue_scan_size, zombie_hosts, zombie_acquirers
|
|
229
|
+
)
|
|
230
|
+
batch_zombie_requests(
|
|
231
|
+
rconn, lock_queues, RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name),
|
|
232
|
+
queue_scan_size, zombie_hosts, zombie_acquirers
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
unless lock_keys.empty?
|
|
237
|
+
flush_zombie_locks(rconn, lock_keys, zombie_hosts, zombie_locks, zombie_acquirers)
|
|
238
|
+
flush_zombie_read_locks(
|
|
239
|
+
rconn, lock_readers_keys, zombie_hosts, zombie_locks, zombie_acquirers
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
return if lock_queues.empty?
|
|
243
|
+
|
|
244
|
+
flush_zombie_requests(rconn, lock_queues, zombie_hosts, zombie_acquirers)
|
|
245
|
+
end
|
|
246
|
+
# rubocop:enable Metrics/MethodLength
|
|
247
|
+
|
|
248
|
+
# (lock index) Adds the lock queue to the batch of lock queues and drops the zombie requests of
|
|
249
|
+
# the batch when it is full (`queue_scan_size` queues; see `flush_zombie_requests`).
|
|
250
|
+
#
|
|
251
|
+
# @param redis_client [RedisClient]
|
|
252
|
+
# @param lock_queues [Array<String>] The batch of lock queues (cleared after the flush).
|
|
253
|
+
# @param lock_queue [String]
|
|
254
|
+
# @param queue_scan_size [Integer]
|
|
255
|
+
# @param zombie_hosts [Set<String>]
|
|
256
|
+
# @param zombie_acquirers [Set<String>] Collects acquirers of the dropped zombie requests.
|
|
257
|
+
# @return [void]
|
|
258
|
+
#
|
|
259
|
+
# @api private
|
|
260
|
+
# @since 1.20.0
|
|
261
|
+
def batch_zombie_requests(
|
|
262
|
+
redis_client,
|
|
263
|
+
lock_queues,
|
|
264
|
+
lock_queue,
|
|
265
|
+
queue_scan_size,
|
|
266
|
+
zombie_hosts,
|
|
267
|
+
zombie_acquirers
|
|
268
|
+
)
|
|
269
|
+
lock_queues << lock_queue
|
|
270
|
+
return if lock_queues.size < queue_scan_size
|
|
271
|
+
|
|
272
|
+
flush_zombie_requests(redis_client, lock_queues, zombie_hosts, zombie_acquirers)
|
|
273
|
+
lock_queues.clear
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Drops zombie locks (write locks of zombie hosts) of the given batch of write locks
|
|
277
|
+
# (two round trips: lock owners, then one `DEL` of the zombie locks).
|
|
278
|
+
#
|
|
279
|
+
# @param redis_client [RedisClient]
|
|
280
|
+
# @param lock_keys [Array<String>]
|
|
281
|
+
# @param zombie_hosts [Set<String>]
|
|
282
|
+
# @param zombie_locks [Set<String>] Collects dropped zombie locks.
|
|
283
|
+
# @param zombie_acquirers [Set<String>] Collects acquirers of the dropped zombie locks.
|
|
284
|
+
# @return [void]
|
|
285
|
+
#
|
|
286
|
+
# @api private
|
|
287
|
+
# @since 1.20.0
|
|
288
|
+
def flush_zombie_locks(redis_client, lock_keys, zombie_hosts, zombie_locks, zombie_acquirers)
|
|
289
|
+
# NOTE: (pipeline layout) one reply (acquirer and host) per lock in the batch order
|
|
290
|
+
# @type var lock_owners: Array[[String?, String?]]
|
|
291
|
+
lock_owners = redis_client.pipelined do |pipeline|
|
|
292
|
+
lock_keys.each { |lock_key| pipeline.call('HMGET', lock_key, 'acq_id', 'hst_id') }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# @type var batch_zombie_locks: Array[String]
|
|
296
|
+
batch_zombie_locks = []
|
|
297
|
+
lock_keys.each_with_index do |lock_key, index|
|
|
298
|
+
acquirer_id, host_id = lock_owners[index]
|
|
299
|
+
next unless acquirer_id && host_id && zombie_hosts.include?(host_id)
|
|
300
|
+
|
|
301
|
+
batch_zombie_locks << lock_key
|
|
302
|
+
zombie_acquirers << acquirer_id
|
|
303
|
+
end
|
|
304
|
+
return if batch_zombie_locks.empty?
|
|
305
|
+
|
|
306
|
+
redis_client.call('DEL', *batch_zombie_locks)
|
|
307
|
+
zombie_locks.merge(batch_zombie_locks)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# (RW) Drops zombie read locks (read locks of zombie hosts) of the given batch of readers
|
|
311
|
+
# registries (two round trips: registry members, then `ZREM` of zombie readers + `DEL` of
|
|
312
|
+
# their read lock data for each registry with zombie readers).
|
|
313
|
+
# - read locks store acquirer ids only (the host is a part of the acquirer id);
|
|
314
|
+
# - readers registry is reported as a zombie lock if any of its read locks is dropped;
|
|
315
|
+
#
|
|
316
|
+
# @param redis_client [RedisClient]
|
|
317
|
+
# @param lock_readers_keys [Array<String>]
|
|
318
|
+
# @param zombie_hosts [Set<String>]
|
|
319
|
+
# @param zombie_locks [Set<String>] Collects registries with dropped zombie read locks.
|
|
320
|
+
# @param zombie_acquirers [Set<String>] Collects acquirers of the dropped zombie read locks.
|
|
321
|
+
# @return [void]
|
|
322
|
+
#
|
|
323
|
+
# @api private
|
|
324
|
+
# @since 1.20.0
|
|
325
|
+
def flush_zombie_read_locks(
|
|
326
|
+
redis_client,
|
|
327
|
+
lock_readers_keys,
|
|
328
|
+
zombie_hosts,
|
|
329
|
+
zombie_locks,
|
|
330
|
+
zombie_acquirers
|
|
331
|
+
)
|
|
332
|
+
# NOTE: (pipeline layout) one reply (read lock acquirers) per registry in the batch order
|
|
333
|
+
# @type var read_lock_acquirers: Array[Array[String]]
|
|
334
|
+
read_lock_acquirers = redis_client.pipelined do |pipeline|
|
|
335
|
+
lock_readers_keys.each do |lock_readers_key|
|
|
336
|
+
pipeline.call('ZRANGE', lock_readers_key, '0', '-1')
|
|
65
337
|
end
|
|
338
|
+
end
|
|
66
339
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
rconn.scan(
|
|
74
|
-
'MATCH', RedisQueuedLocks::Resource::LOCK_READERS_PATTERN, count: lock_scan_size
|
|
75
|
-
) do |lock_readers_key|
|
|
76
|
-
# @type var read_lock_acquirers: Array[String]
|
|
77
|
-
read_lock_acquirers = rconn.call('ZRANGE', lock_readers_key, '0', '-1')
|
|
78
|
-
zombie_readers = read_lock_acquirers.select do |acquirer_id|
|
|
79
|
-
zombie_hosts.include?(RedisQueuedLocks::Resource.host_identifier_from_acquirer(acquirer_id))
|
|
340
|
+
redis_client.pipelined do |pipeline|
|
|
341
|
+
lock_readers_keys.each_with_index do |lock_readers_key, index|
|
|
342
|
+
zombie_readers = read_lock_acquirers[index].select do |acquirer_id|
|
|
343
|
+
zombie_hosts.include?(
|
|
344
|
+
RedisQueuedLocks::Resource.host_identifier_from_acquirer(acquirer_id)
|
|
345
|
+
)
|
|
80
346
|
end
|
|
81
347
|
next if zombie_readers.empty?
|
|
82
348
|
|
|
83
|
-
|
|
349
|
+
pipeline.call('ZREM', lock_readers_key, *zombie_readers)
|
|
84
350
|
# NOTE: drop the zombie read lock data
|
|
85
351
|
lock_name = RedisQueuedLocks::Resource.lock_name_from_readers(lock_readers_key)
|
|
86
|
-
|
|
352
|
+
pipeline.call(
|
|
87
353
|
'DEL',
|
|
88
354
|
*zombie_readers.map do |acquirer_id|
|
|
89
355
|
RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
@@ -92,41 +358,42 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
92
358
|
zombie_locks << lock_readers_key
|
|
93
359
|
zombie_acquirers.merge(zombie_readers)
|
|
94
360
|
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
95
363
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
364
|
+
# Drops the requests of zombie hosts from the given batch of lock queues (write and read)
|
|
365
|
+
# in two round trips: queued acquirers, then one `ZREM` of the zombie requests per queue
|
|
366
|
+
# (the host of a request is derived from its acquirer id).
|
|
367
|
+
#
|
|
368
|
+
# @param redis_client [RedisClient]
|
|
369
|
+
# @param lock_queues [Array<String>]
|
|
370
|
+
# @param zombie_hosts [Set<String>]
|
|
371
|
+
# @param zombie_acquirers [Set<String>] Collects acquirers of the dropped zombie requests.
|
|
372
|
+
# @return [void]
|
|
373
|
+
#
|
|
374
|
+
# @api private
|
|
375
|
+
# @since 1.20.0
|
|
376
|
+
def flush_zombie_requests(redis_client, lock_queues, zombie_hosts, zombie_acquirers)
|
|
377
|
+
# NOTE: (pipeline layout) one reply (queued acquirers) per queue in the batch order
|
|
378
|
+
# @type var queued_acquirers: Array[Array[String]]
|
|
379
|
+
queued_acquirers = redis_client.pipelined do |pipeline|
|
|
380
|
+
lock_queues.each { |lock_queue| pipeline.call('ZRANGE', lock_queue, '0', '-1') }
|
|
381
|
+
end
|
|
107
382
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
383
|
+
redis_client.pipelined do |pipeline|
|
|
384
|
+
lock_queues.each_with_index do |lock_queue, index|
|
|
385
|
+
zombie_requests = queued_acquirers[index].select do |acquirer_id|
|
|
386
|
+
zombie_hosts.include?(
|
|
387
|
+
RedisQueuedLocks::Resource.host_identifier_from_acquirer(acquirer_id)
|
|
388
|
+
)
|
|
114
389
|
end
|
|
115
|
-
|
|
390
|
+
next if zombie_requests.empty?
|
|
116
391
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# Step 6: inform about deleted zombies
|
|
121
|
-
{
|
|
122
|
-
ok: true,
|
|
123
|
-
deleted_zombie_hosts: zombie_hosts,
|
|
124
|
-
deleted_zombie_acquirers: zombie_acquirers,
|
|
125
|
-
deleted_zombie_locks: zombie_locks
|
|
126
|
-
}
|
|
392
|
+
pipeline.call('ZREM', lock_queue, *zombie_requests)
|
|
393
|
+
zombie_acquirers.merge(zombie_requests)
|
|
394
|
+
end
|
|
127
395
|
end
|
|
128
396
|
end
|
|
129
|
-
# rubocop:enable Metrics/MethodLength
|
|
130
397
|
end
|
|
131
398
|
|
|
132
399
|
# @return [Boolean]
|
|
@@ -142,6 +409,8 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
142
409
|
#
|
|
143
410
|
# @api private
|
|
144
411
|
# @since 1.17.0
|
|
412
|
+
# @version 1.20.0
|
|
413
|
+
# rubocop:disable Metrics/MethodLength
|
|
145
414
|
def spawn_swarm_element!(swarm_element_results_port)
|
|
146
415
|
Ractor.new(
|
|
147
416
|
swarm_element_results_port,
|
|
@@ -149,8 +418,9 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
149
418
|
rql_client.config['swarm.flush_zombies.zombie_ttl'],
|
|
150
419
|
rql_client.config['swarm.flush_zombies.zombie_lock_scan_size'],
|
|
151
420
|
rql_client.config['swarm.flush_zombies.zombie_queue_scan_size'],
|
|
152
|
-
rql_client.config['swarm.flush_zombies.zombie_flush_period']
|
|
153
|
-
|
|
421
|
+
rql_client.config['swarm.flush_zombies.zombie_flush_period'],
|
|
422
|
+
rql_client.config['lock_index_enabled']
|
|
423
|
+
) do |r_res_p, rc, z_ttl, z_lss, z_qss, z_fl_prd, l_idx_en|
|
|
154
424
|
RedisQueuedLocks::Swarm::FlushZombies.swarm_loop(r_res_p) do
|
|
155
425
|
Thread.new do
|
|
156
426
|
redis_client = RedisQueuedLocks::Swarm::RedisClientBuilder.build(
|
|
@@ -162,7 +432,7 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
162
432
|
|
|
163
433
|
loop do
|
|
164
434
|
RedisQueuedLocks::Swarm::FlushZombies.flush_zombies(
|
|
165
|
-
redis_client, z_ttl, z_lss, z_qss
|
|
435
|
+
redis_client, z_ttl, z_lss, z_qss, l_idx_en
|
|
166
436
|
)
|
|
167
437
|
sleep(z_fl_prd)
|
|
168
438
|
end
|
|
@@ -170,5 +440,6 @@ class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElem
|
|
|
170
440
|
end
|
|
171
441
|
end
|
|
172
442
|
end
|
|
443
|
+
# rubocop:enable Metrics/MethodLength
|
|
173
444
|
end
|
|
174
445
|
# rubocop:enable Metrics/ClassLength
|