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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +65 -13
  3. data/.claude/rules/acquirer.md +29 -11
  4. data/.claude/rules/arguments.md +3 -2
  5. data/.claude/rules/git.md +3 -2
  6. data/.claude/rules/logic.md +4 -3
  7. data/.claude/rules/swarm.md +12 -7
  8. data/.claude/rules/tests.md +6 -0
  9. data/.claude/rules/type-checking.md +2 -1
  10. data/.claude/rules/visitors.md +1 -1
  11. data/.claude/skills/rql-code-review/SKILL.md +372 -0
  12. data/.claude/skills/rql-code-review/templates/report.en.md +31 -0
  13. data/.claude/skills/rql-code-review/templates/report.ru.md +31 -0
  14. data/CHANGELOG.md +74 -0
  15. data/CLAUDE.md +4 -2
  16. data/README.md +338 -24
  17. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +684 -128
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +3 -1
  19. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +24 -3
  20. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +145 -18
  21. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +71 -10
  22. data/lib/redis_queued_locks/acquirer/keys.rb +142 -7
  23. data/lib/redis_queued_locks/acquirer/lock_info.rb +10 -4
  24. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +26 -7
  25. data/lib/redis_queued_locks/acquirer/locks.rb +195 -83
  26. data/lib/redis_queued_locks/acquirer/queue_info.rb +12 -18
  27. data/lib/redis_queued_locks/acquirer/queues.rb +114 -50
  28. data/lib/redis_queued_locks/acquirer/reindex_locks.rb +140 -0
  29. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +147 -58
  30. data/lib/redis_queued_locks/acquirer/release_lock.rb +52 -19
  31. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +235 -66
  32. data/lib/redis_queued_locks/acquirer.rb +1 -0
  33. data/lib/redis_queued_locks/client.rb +116 -16
  34. data/lib/redis_queued_locks/config.rb +5 -1
  35. data/lib/redis_queued_locks/resource.rb +99 -2
  36. data/lib/redis_queued_locks/swarm/flush_zombies.rb +332 -61
  37. data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
  38. data/lib/redis_queued_locks/swarm.rb +15 -4
  39. data/lib/redis_queued_locks/version.rb +2 -2
  40. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +107 -1
  41. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +1 -1
  42. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +2 -0
  43. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +10 -1
  44. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +5 -2
  45. data/sig/redis_queued_locks/acquirer/keys.rbs +11 -1
  46. data/sig/redis_queued_locks/acquirer/locks.rbs +15 -6
  47. data/sig/redis_queued_locks/acquirer/queues.rbs +9 -2
  48. data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +31 -0
  49. data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
  50. data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -1
  51. data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
  52. data/sig/redis_queued_locks/client.rbs +9 -0
  53. data/sig/redis_queued_locks/resource.rbs +6 -0
  54. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +51 -1
  55. data/sig/redis_queued_locks/swarm/zombie_info.rbs +58 -7
  56. data/sig/vendor/redis_client.rbs +1 -0
  57. 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.18.0
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
- # TODO: indexing (in order to prevent full database scan);
52
- # NOTE: original redis does not support indexing so we need to use
53
- # internal data structers to simulate data indexing (such as sorted sets or lists);
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
- rconn.scan(
58
- 'MATCH', RedisQueuedLocks::Resource::LOCK_PATTERN, count: lock_scan_size
59
- ) do |lock_key|
60
- acquirer_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
61
- if zombie_hosts.include?(host_id)
62
- zombie_locks << lock_key
63
- zombie_acquirers << acquirer_id
64
- end
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
- # NOTE: (steep) steep can't use <Set>s for splats
68
- rconn.call('DEL', *zombie_locks) if zombie_locks.any? # steep:ignore
69
-
70
- # Step 3 (RW): find zombie read locks (read locks of zombie hosts) and drop them
71
- # - read locks store acquirer ids only (the host is a part of the acquirer id);
72
- # - readers registry is reported as a zombie lock if any of its read locks is dropped;
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
- rconn.call('ZREM', lock_readers_key, *zombie_readers)
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
- rconn.call(
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
- # Step 4: find zombie requests => and drop them
97
- # TODO: indexing (in order to prevent full database scan);
98
- # NOTE: original redis does not support indexing so we need to use
99
- # internal data structers to simulate data indexing (such as sorted sets or lists);
100
- rconn.scan(
101
- 'MATCH', RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN, count: queue_scan_size
102
- ) do |lock_queue|
103
- zombie_acquirers.each do |zombie_acquirer|
104
- rconn.call('ZREM', lock_queue, zombie_acquirer)
105
- end
106
- end
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
- # Step 4 (RW): drop zombie requests from the queues of read lock requests
109
- rconn.scan(
110
- 'MATCH', RedisQueuedLocks::Resource::READ_LOCK_QUEUE_PATTERN, count: queue_scan_size
111
- ) do |read_lock_queue|
112
- zombie_acquirers.each do |zombie_acquirer|
113
- rconn.call('ZREM', read_lock_queue, zombie_acquirer)
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
- end
390
+ next if zombie_requests.empty?
116
391
 
117
- # Step 5: drop zombies from the swarm
118
- rconn.call('HDEL', RedisQueuedLocks::Resource::SWARM_KEY, *zombie_hosts)
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
- ) do |r_res_p, rc, z_ttl, z_lss, z_qss, z_fl_prd|
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