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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +96 -23
  3. data/.claude/rules/acquirer.md +39 -20
  4. data/.claude/rules/arguments.md +3 -2
  5. data/.claude/rules/git.md +3 -2
  6. data/.claude/rules/logic.md +32 -8
  7. data/.claude/rules/swarm.md +14 -9
  8. data/.claude/rules/tests.md +12 -3
  9. data/.claude/rules/type-checking.md +2 -1
  10. data/.claude/rules/visitors.md +10 -11
  11. data/.claude/skills/rql-code-review/SKILL.md +386 -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 +194 -0
  15. data/CLAUDE.md +6 -3
  16. data/README.md +502 -92
  17. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +695 -128
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
  23. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
  24. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +8 -6
  25. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +134 -26
  26. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +229 -24
  27. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +189 -18
  28. data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
  29. data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
  30. data/lib/redis_queued_locks/acquirer/keys.rb +151 -7
  31. data/lib/redis_queued_locks/acquirer/lock_info.rb +39 -41
  32. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
  33. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
  34. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +79 -20
  35. data/lib/redis_queued_locks/acquirer/locks.rb +212 -83
  36. data/lib/redis_queued_locks/acquirer/queue_info.rb +11 -18
  37. data/lib/redis_queued_locks/acquirer/queues.rb +123 -50
  38. data/lib/redis_queued_locks/acquirer/reindex_locks.rb +253 -0
  39. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +177 -61
  40. data/lib/redis_queued_locks/acquirer/release_lock.rb +84 -25
  41. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +274 -70
  42. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
  43. data/lib/redis_queued_locks/acquirer.rb +1 -0
  44. data/lib/redis_queued_locks/client.rb +164 -36
  45. data/lib/redis_queued_locks/config.rb +16 -13
  46. data/lib/redis_queued_locks/errors.rb +34 -4
  47. data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
  48. data/lib/redis_queued_locks/logging/sampler.rb +4 -8
  49. data/lib/redis_queued_locks/resource.rb +113 -6
  50. data/lib/redis_queued_locks/swarm/flush_zombies.rb +333 -61
  51. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
  52. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
  53. data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
  54. data/lib/redis_queued_locks/swarm.rb +97 -5
  55. data/lib/redis_queued_locks/utilities.rb +1 -1
  56. data/lib/redis_queued_locks/version.rb +2 -2
  57. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
  58. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
  59. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +107 -1
  60. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -3
  61. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +3 -1
  62. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +19 -1
  63. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +20 -4
  64. data/sig/redis_queued_locks/acquirer/keys.rbs +11 -1
  65. data/sig/redis_queued_locks/acquirer/locks.rbs +15 -6
  66. data/sig/redis_queued_locks/acquirer/queues.rbs +9 -2
  67. data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +52 -0
  68. data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
  69. data/sig/redis_queued_locks/acquirer/release_lock.rbs +6 -3
  70. data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
  71. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
  72. data/sig/redis_queued_locks/client.rbs +21 -2
  73. data/sig/redis_queued_locks/errors.rbs +5 -2
  74. data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
  75. data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
  76. data/sig/redis_queued_locks/resource.rbs +7 -1
  77. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +51 -1
  78. data/sig/redis_queued_locks/swarm/zombie_info.rbs +58 -7
  79. data/sig/vendor/redis_client.rbs +2 -0
  80. metadata +6 -1
@@ -2,11 +2,16 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.9.0
5
+ # @version 1.20.0
6
+ # rubocop:disable Metrics/ModuleLength
5
7
  module RedisQueuedLocks::Swarm::ZombieInfo
8
+ # rubocop:disable Metrics/ClassLength
6
9
  class << self
7
10
  # @param redis_client [RedisClient]
8
11
  # @param zombie_ttl [Integer]
9
12
  # @param lock_scan_size [Integer]
13
+ # @param lock_index_enabled [Boolean]
14
+ # Find zombie locks via the lock index instead of the keyspace SCAN.
10
15
  # @return [Hash<Symbol,Set<String>>]
11
16
  # Format: {
12
17
  # zombie_hosts: <Set<String>>,
@@ -16,22 +21,25 @@ module RedisQueuedLocks::Swarm::ZombieInfo
16
21
  #
17
22
  # @api private
18
23
  # @since 1.9.0
19
- def zombies_info(redis_client, zombie_ttl, lock_scan_size)
24
+ # @version 1.20.0
25
+ def zombies_info(redis_client, zombie_ttl, lock_scan_size, lock_index_enabled)
20
26
  redis_client.with do |rconn|
21
- extract_all(rconn, zombie_ttl, lock_scan_size)
27
+ extract_all(rconn, zombie_ttl, lock_scan_size, lock_index_enabled)
22
28
  end
23
29
  end
24
30
 
25
31
  # @param redis_client [RedisClient]
26
32
  # @param zombie_ttl [Integer]
27
33
  # @param lock_scan_size [Integer]
34
+ # @param lock_index_enabled [Boolean]
28
35
  # @return [Set<String>]
29
36
  #
30
37
  # @api private
31
38
  # @since 1.9.0
32
- def zombie_locks(redis_client, zombie_ttl, lock_scan_size)
39
+ # @version 1.20.0
40
+ def zombie_locks(redis_client, zombie_ttl, lock_scan_size, lock_index_enabled)
33
41
  redis_client.with do |rconn|
34
- extract_zombie_locks(rconn, zombie_ttl, lock_scan_size)
42
+ extract_zombie_locks(rconn, zombie_ttl, lock_scan_size, lock_index_enabled)
35
43
  end
36
44
  end
37
45
 
@@ -50,13 +58,15 @@ module RedisQueuedLocks::Swarm::ZombieInfo
50
58
  # @param redis_client [RedisClient]
51
59
  # @param zombie_ttl [Integer]
52
60
  # @param lock_scan_size [Integer]
61
+ # @param lock_index_enabled [Boolean]
53
62
  # @return [Set<String>]
54
63
  #
55
64
  # @api private
56
65
  # @since 1.9.0
57
- def zombie_acquirers(redis_client, zombie_ttl, lock_scan_size)
66
+ # @version 1.20.0
67
+ def zombie_acquirers(redis_client, zombie_ttl, lock_scan_size, lock_index_enabled)
58
68
  redis_client.with do |rconn|
59
- extract_zombie_acquirers(rconn, zombie_ttl, lock_scan_size)
69
+ extract_zombie_acquirers(rconn, zombie_ttl, lock_scan_size, lock_index_enabled)
60
70
  end
61
71
  end
62
72
 
@@ -79,23 +89,20 @@ module RedisQueuedLocks::Swarm::ZombieInfo
79
89
  # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
80
90
  # @param zombie_ttl [Integer]
81
91
  # @param lock_scan_size [Integer]
92
+ # @param lock_index_enabled [Boolean]
82
93
  # @return [Set<String>]
83
94
  #
84
95
  # @api private
85
96
  # @since 1.9.0
86
- # @version 1.18.0
87
- def extract_zombie_locks(rconn, zombie_ttl, lock_scan_size)
97
+ # @version 1.20.0
98
+ def extract_zombie_locks(rconn, zombie_ttl, lock_scan_size, lock_index_enabled)
88
99
  zombie_hosts = extract_zombie_hosts(rconn, zombie_ttl)
89
- zombie_locks = Set.new
90
- rconn.scan(
91
- 'MATCH', RedisQueuedLocks::Resource::LOCK_PATTERN, count: lock_scan_size
92
- ) do |lock_key|
93
- _acquirer_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
94
- zombie_locks << lock_key if zombie_hosts.include?(host_id)
95
- end
96
- # NOTE: (RW) readers registries with zombie read locks
97
- each_zombie_read_lock(rconn, zombie_hosts, lock_scan_size) do |lock_readers_key, _acquirer_id|
98
- zombie_locks << lock_readers_key
100
+ zombie_locks = Set.new #: Set[String]
101
+ # NOTE: (RW) readers registries with zombie read locks are zombie locks too
102
+ each_zombie_lock(
103
+ rconn, zombie_hosts, lock_scan_size, lock_index_enabled
104
+ ) do |lock_key, _acquirer_id|
105
+ zombie_locks << lock_key
99
106
  end
100
107
  zombie_locks
101
108
  end
@@ -103,22 +110,19 @@ module RedisQueuedLocks::Swarm::ZombieInfo
103
110
  # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
104
111
  # @param zombie_ttl [Integer]
105
112
  # @param lock_scan_size [Integer]
113
+ # @param lock_index_enabled [Boolean]
106
114
  # @return [Set<String>]
107
115
  #
108
116
  # @api private
109
117
  # @since 1.9.0
110
- # @version 1.18.0
111
- def extract_zombie_acquirers(rconn, zombie_ttl, lock_scan_size)
118
+ # @version 1.20.0
119
+ def extract_zombie_acquirers(rconn, zombie_ttl, lock_scan_size, lock_index_enabled)
112
120
  zombie_hosts = extract_zombie_hosts(rconn, zombie_ttl)
113
- zombie_acquirers = Set.new
114
- rconn.scan(
115
- 'MATCH', RedisQueuedLocks::Resource::LOCK_PATTERN, count: lock_scan_size
116
- ) do |lock_key|
117
- acquirer_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
118
- zombie_acquirers << acquirer_id if zombie_hosts.include?(host_id)
119
- end
120
- # NOTE: (RW) zombie read lock acquirers
121
- each_zombie_read_lock(rconn, zombie_hosts, lock_scan_size) do |_lock_readers_key, acquirer_id|
121
+ zombie_acquirers = Set.new #: Set[String]
122
+ # NOTE: (RW) zombie read lock acquirers are zombie acquirers too
123
+ each_zombie_lock(
124
+ rconn, zombie_hosts, lock_scan_size, lock_index_enabled
125
+ ) do |_lock_key, acquirer_id|
122
126
  zombie_acquirers << acquirer_id
123
127
  end
124
128
  zombie_acquirers
@@ -127,6 +131,7 @@ module RedisQueuedLocks::Swarm::ZombieInfo
127
131
  # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
128
132
  # @param zombie_ttl [Integer]
129
133
  # @param lock_scan_size [Integer]
134
+ # @param lock_index_enabled [Boolean]
130
135
  # @return [Hash<Symbol,<Set<String>>]
131
136
  # Format: {
132
137
  # zombie_hosts: <Set<String>>,
@@ -136,34 +141,172 @@ module RedisQueuedLocks::Swarm::ZombieInfo
136
141
  #
137
142
  # @api private
138
143
  # @since 1.9.0
139
- # @version 1.18.0
140
- def extract_all(rconn, zombie_ttl, lock_scan_size)
144
+ # @version 1.20.0
145
+ def extract_all(rconn, zombie_ttl, lock_scan_size, lock_index_enabled)
141
146
  zombie_hosts = extract_zombie_hosts(rconn, zombie_ttl)
142
- zombie_locks = Set.new
143
- zombie_acquirers = Set.new
147
+ zombie_locks = Set.new #: Set[String]
148
+ zombie_acquirers = Set.new #: Set[String]
149
+ # NOTE: (RW) zombie read locks are reported by their readers registries
150
+ each_zombie_lock(
151
+ rconn, zombie_hosts, lock_scan_size, lock_index_enabled
152
+ ) do |lock_key, acquirer_id|
153
+ zombie_acquirers << acquirer_id
154
+ zombie_locks << lock_key
155
+ end
156
+ { zombie_hosts:, zombie_acquirers:, zombie_locks: }
157
+ end
158
+
159
+ # Iterates over the locks of zombie hosts in one pass (the keyspace SCAN or the lock index
160
+ # ZSCAN, see `each_keyspace_zombie_lock` and `each_lock_index_zombie_lock`). Nothing is scanned
161
+ # when there are no zombie hosts.
162
+ #
163
+ # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
164
+ # @param zombie_hosts [Set<String>]
165
+ # @param lock_scan_size [Integer]
166
+ # @param lock_index_enabled [Boolean]
167
+ # @yield [lock_key, acquirer_id]
168
+ # @yieldparam lock_key [String] Zombie write lock or readers registry with a zombie read lock.
169
+ # @yieldparam acquirer_id [String] Zombie lock acquirer.
170
+ # @return [void]
171
+ #
172
+ # @api private
173
+ # @since 1.20.0
174
+ def each_zombie_lock(rconn, zombie_hosts, lock_scan_size, lock_index_enabled, &block)
175
+ return if zombie_hosts.empty?
176
+
177
+ if lock_index_enabled
178
+ each_lock_index_zombie_lock(rconn, zombie_hosts, lock_scan_size, &block)
179
+ else
180
+ each_keyspace_zombie_lock(rconn, zombie_hosts, lock_scan_size, &block)
181
+ end
182
+ end
183
+
184
+ # (keyspace) Iterates over the locks of zombie hosts in one SCAN pass over the RQL keys
185
+ # (`KEY_PATTERN`, the disabled lock index): write locks and readers registries are processed
186
+ # in batches of `lock_scan_size` keys with one round trip per batch (batches are collected
187
+ # across SCAN pages).
188
+ #
189
+ # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
190
+ # @param zombie_hosts [Set<String>]
191
+ # @param lock_scan_size [Integer]
192
+ # @yield [lock_key, acquirer_id]
193
+ # @yieldparam lock_key [String] Zombie write lock or readers registry with a zombie read lock.
194
+ # @yieldparam acquirer_id [String] Zombie lock acquirer.
195
+ # @return [void]
196
+ #
197
+ # @api private
198
+ # @since 1.20.0
199
+ def each_keyspace_zombie_lock(rconn, zombie_hosts, lock_scan_size, &block)
200
+ # @type var lock_keys: Array[String]
201
+ lock_keys = []
202
+ # @type var lock_readers_keys: Array[String]
203
+ lock_readers_keys = []
204
+
144
205
  rconn.scan(
145
- 'MATCH', RedisQueuedLocks::Resource::LOCK_PATTERN, count: lock_scan_size
146
- ) do |lock_key|
147
- acquirer_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
148
- if zombie_hosts.include?(host_id)
149
- zombie_acquirers << acquirer_id
150
- zombie_locks << lock_key
206
+ 'MATCH', RedisQueuedLocks::Resource::KEY_PATTERN, count: lock_scan_size
207
+ ) do |key|
208
+ case RedisQueuedLocks::Resource.lock_key_family(key)
209
+ when :lock
210
+ lock_keys << key
211
+ next if lock_keys.size < lock_scan_size
212
+
213
+ each_zombie_write_lock(rconn, lock_keys, zombie_hosts, &block)
214
+ lock_keys.clear
215
+ when :lock_readers
216
+ lock_readers_keys << key
217
+ next if lock_readers_keys.size < lock_scan_size
218
+
219
+ each_zombie_read_lock(rconn, lock_readers_keys, zombie_hosts, &block)
220
+ lock_readers_keys.clear
151
221
  end
152
222
  end
153
- # NOTE: (RW) zombie read locks
154
- each_zombie_read_lock(rconn, zombie_hosts, lock_scan_size) do |lock_readers_key, acquirer_id|
155
- zombie_acquirers << acquirer_id
156
- zombie_locks << lock_readers_key
223
+
224
+ each_zombie_write_lock(rconn, lock_keys, zombie_hosts, &block) unless lock_keys.empty?
225
+ unless lock_readers_keys.empty?
226
+ each_zombie_read_lock(rconn, lock_readers_keys, zombie_hosts, &block)
157
227
  end
158
- { zombie_hosts:, zombie_acquirers:, zombie_locks: }
159
228
  end
160
229
 
161
- # Iterates over read locks of zombie hosts
162
- # (read locks store acquirer ids only: the host is a part of the acquirer id).
230
+ # (lock index) Iterates over the locks of zombie hosts in one ZSCAN pass over the lock index
231
+ # (`LOCK_INDEX_KEY`: lock names, the enabled lock index; lock keys are derived from the lock
232
+ # names, the full keyspace is never scanned): write locks and readers registries are processed
233
+ # in batches of `lock_scan_size` locks with one round trip per batch and per key family
234
+ # (batches are collected across ZSCAN pages). Expired lock names of the index are skipped
235
+ # (all their keys are expired).
163
236
  #
164
237
  # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
165
238
  # @param zombie_hosts [Set<String>]
166
239
  # @param lock_scan_size [Integer]
240
+ # @yield [lock_key, acquirer_id]
241
+ # @yieldparam lock_key [String] Zombie write lock or readers registry with a zombie read lock.
242
+ # @yieldparam acquirer_id [String] Zombie lock acquirer.
243
+ # @return [void]
244
+ #
245
+ # @api private
246
+ # @since 1.20.0
247
+ def each_lock_index_zombie_lock(rconn, zombie_hosts, lock_scan_size, &block)
248
+ # @type var lock_keys: Array[String]
249
+ lock_keys = []
250
+ # @type var lock_readers_keys: Array[String]
251
+ lock_readers_keys = []
252
+ lock_index_time = RedisQueuedLocks::Resource.lock_index_time
253
+
254
+ rconn.zscan(
255
+ RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: lock_scan_size
256
+ ) do |lock_name, lock_index_score|
257
+ # NOTE: the expired lock name (not dropped from the index yet): its keys are expired
258
+ next if lock_index_score.to_f < lock_index_time
259
+
260
+ lock_keys << RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
261
+ lock_readers_keys << RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
262
+ next if lock_keys.size < lock_scan_size
263
+
264
+ each_zombie_write_lock(rconn, lock_keys, zombie_hosts, &block)
265
+ each_zombie_read_lock(rconn, lock_readers_keys, zombie_hosts, &block)
266
+ lock_keys.clear
267
+ lock_readers_keys.clear
268
+ end
269
+
270
+ return if lock_keys.empty?
271
+
272
+ each_zombie_write_lock(rconn, lock_keys, zombie_hosts, &block)
273
+ each_zombie_read_lock(rconn, lock_readers_keys, zombie_hosts, &block)
274
+ end
275
+
276
+ # Iterates over the write locks of zombie hosts in the given batch of write locks
277
+ # (one round trip).
278
+ #
279
+ # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
280
+ # @param lock_keys [Array<String>]
281
+ # @param zombie_hosts [Set<String>]
282
+ # @yield [lock_key, acquirer_id]
283
+ # @yieldparam lock_key [String] Zombie write lock.
284
+ # @yieldparam acquirer_id [String] Zombie write lock acquirer.
285
+ # @return [void]
286
+ #
287
+ # @api private
288
+ # @since 1.20.0
289
+ def each_zombie_write_lock(rconn, lock_keys, zombie_hosts)
290
+ # NOTE: (pipeline layout) one reply (acquirer and host) per lock in the batch order
291
+ # @type var lock_owners: Array[[String?, String?]]
292
+ lock_owners = rconn.pipelined do |pipeline|
293
+ lock_keys.each { |lock_key| pipeline.call('HMGET', lock_key, 'acq_id', 'hst_id') }
294
+ end
295
+
296
+ lock_keys.each_with_index do |lock_key, index|
297
+ acquirer_id, host_id = lock_owners[index]
298
+ next unless acquirer_id && host_id && zombie_hosts.include?(host_id)
299
+
300
+ yield(lock_key, acquirer_id)
301
+ end
302
+ end
303
+
304
+ # Iterates over read locks of zombie hosts in the given batch of readers registries
305
+ # (one round trip; read locks store acquirer ids only: the host is a part of the acquirer id).
306
+ #
307
+ # @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
308
+ # @param lock_readers_keys [Array<String>]
309
+ # @param zombie_hosts [Set<String>]
167
310
  # @yield [lock_readers_key, acquirer_id]
168
311
  # @yieldparam lock_readers_key [String] Readers registry with a zombie read lock.
169
312
  # @yieldparam acquirer_id [String] Zombie read lock acquirer.
@@ -171,17 +314,24 @@ module RedisQueuedLocks::Swarm::ZombieInfo
171
314
  #
172
315
  # @api private
173
316
  # @since 1.18.0
174
- def each_zombie_read_lock(rconn, zombie_hosts, lock_scan_size)
175
- rconn.scan(
176
- 'MATCH', RedisQueuedLocks::Resource::LOCK_READERS_PATTERN, count: lock_scan_size
177
- ) do |lock_readers_key|
178
- # @type var read_lock_acquirers: Array[String]
179
- read_lock_acquirers = rconn.call('ZRANGE', lock_readers_key, '0', '-1')
180
- read_lock_acquirers.each do |acquirer_id|
317
+ # @version 1.20.0
318
+ def each_zombie_read_lock(rconn, lock_readers_keys, zombie_hosts)
319
+ # NOTE: (pipeline layout) one reply (read lock acquirers) per registry in the batch order
320
+ # @type var read_lock_acquirers: Array[Array[String]]
321
+ read_lock_acquirers = rconn.pipelined do |pipeline|
322
+ lock_readers_keys.each do |lock_readers_key|
323
+ pipeline.call('ZRANGE', lock_readers_key, '0', '-1')
324
+ end
325
+ end
326
+
327
+ lock_readers_keys.each_with_index do |lock_readers_key, index|
328
+ read_lock_acquirers[index].each do |acquirer_id|
181
329
  host_id = RedisQueuedLocks::Resource.host_identifier_from_acquirer(acquirer_id)
182
330
  yield(lock_readers_key, acquirer_id) if zombie_hosts.include?(host_id)
183
331
  end
184
332
  end
185
333
  end
186
334
  end
335
+ # rubocop:enable Metrics/ClassLength
187
336
  end
337
+ # rubocop:enable Metrics/ModuleLength
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api public
4
4
  # @since 1.9.0
5
+ # @version 1.13.0
5
6
  # rubocop:disable Metrics/ClassLength
6
7
  class RedisQueuedLocks::Swarm
7
8
  require_relative 'swarm/redis_client_builder'
@@ -76,7 +77,16 @@ class RedisQueuedLocks::Swarm
76
77
  #
77
78
  # @api public
78
79
  # @since 1.9.0
80
+ # @version 1.21.0
79
81
  def swarm_info(zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'])
82
+ # NOTE: (arguments) the same ranges as the config keys;
83
+ if !zombie_ttl.is_a?(::Integer) || zombie_ttl <= 0
84
+ raise(
85
+ RedisQueuedLocks::SwarmArgumentError,
86
+ "`:zombie_ttl` argument should be a positive Integer, got #{zombie_ttl.inspect}."
87
+ )
88
+ end
89
+
80
90
  RedisQueuedLocks::Swarm::Acquirers.acquirers(
81
91
  rql_client.redis_client,
82
92
  zombie_ttl
@@ -94,6 +104,9 @@ class RedisQueuedLocks::Swarm
94
104
  )
95
105
  end
96
106
 
107
+ # NOTE: zombie locks are found via the lock index or via the keyspace SCAN
108
+ # (`config['lock_index_enabled']`, the client-wide mode);
109
+ #
97
110
  # @option zombie_ttl [Integer]
98
111
  # @option lock_scan_size [Integer]
99
112
  # @option queue_scan_size [Integer]
@@ -101,16 +114,38 @@ class RedisQueuedLocks::Swarm
101
114
  #
102
115
  # @api public
103
116
  # @since 1.9.0
117
+ # @version 1.21.0
104
118
  def flush_zombies(
105
119
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
106
120
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size'],
107
121
  queue_scan_size: rql_client.config['swarm.flush_zombies.zombie_queue_scan_size']
108
122
  )
123
+ # NOTE: (arguments) the same ranges as the config keys;
124
+ if !zombie_ttl.is_a?(::Integer) || zombie_ttl <= 0
125
+ raise(
126
+ RedisQueuedLocks::SwarmArgumentError,
127
+ "`:zombie_ttl` argument should be a positive Integer, got #{zombie_ttl.inspect}."
128
+ )
129
+ end
130
+ if !lock_scan_size.is_a?(::Integer) || lock_scan_size <= 0
131
+ raise(
132
+ RedisQueuedLocks::SwarmArgumentError,
133
+ "`:lock_scan_size` argument should be a positive Integer, got #{lock_scan_size.inspect}."
134
+ )
135
+ end
136
+ if !queue_scan_size.is_a?(::Integer) || queue_scan_size <= 0
137
+ raise(
138
+ RedisQueuedLocks::SwarmArgumentError,
139
+ "`:queue_scan_size` argument should be a positive Integer, got #{queue_scan_size.inspect}."
140
+ )
141
+ end
142
+
109
143
  RedisQueuedLocks::Swarm::FlushZombies.flush_zombies(
110
144
  rql_client.redis_client,
111
145
  zombie_ttl,
112
146
  lock_scan_size,
113
- queue_scan_size
147
+ queue_scan_size,
148
+ rql_client.config['lock_index_enabled']
114
149
  )
115
150
  end
116
151
 
@@ -120,14 +155,30 @@ class RedisQueuedLocks::Swarm
120
155
  #
121
156
  # @api public
122
157
  # @since 1.9.0
158
+ # @version 1.21.0
123
159
  def zombie_locks(
124
160
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
125
161
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size']
126
162
  )
163
+ # NOTE: (arguments) the same ranges as the config keys;
164
+ if !zombie_ttl.is_a?(::Integer) || zombie_ttl <= 0
165
+ raise(
166
+ RedisQueuedLocks::SwarmArgumentError,
167
+ "`:zombie_ttl` argument should be a positive Integer, got #{zombie_ttl.inspect}."
168
+ )
169
+ end
170
+ if !lock_scan_size.is_a?(::Integer) || lock_scan_size <= 0
171
+ raise(
172
+ RedisQueuedLocks::SwarmArgumentError,
173
+ "`:lock_scan_size` argument should be a positive Integer, got #{lock_scan_size.inspect}."
174
+ )
175
+ end
176
+
127
177
  RedisQueuedLocks::Swarm::ZombieInfo.zombie_locks(
128
178
  rql_client.redis_client,
129
179
  zombie_ttl,
130
- lock_scan_size
180
+ lock_scan_size,
181
+ rql_client.config['lock_index_enabled']
131
182
  )
132
183
  end
133
184
 
@@ -135,16 +186,32 @@ class RedisQueuedLocks::Swarm
135
186
  # @option lock_scan_size [Integer]
136
187
  # @return [Set<String>]
137
188
  #
138
- # @api ppublic
189
+ # @api public
139
190
  # @since 1.9.0
191
+ # @version 1.21.0
140
192
  def zombie_acquirers(
141
193
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
142
194
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size']
143
195
  )
196
+ # NOTE: (arguments) the same ranges as the config keys;
197
+ if !zombie_ttl.is_a?(::Integer) || zombie_ttl <= 0
198
+ raise(
199
+ RedisQueuedLocks::SwarmArgumentError,
200
+ "`:zombie_ttl` argument should be a positive Integer, got #{zombie_ttl.inspect}."
201
+ )
202
+ end
203
+ if !lock_scan_size.is_a?(::Integer) || lock_scan_size <= 0
204
+ raise(
205
+ RedisQueuedLocks::SwarmArgumentError,
206
+ "`:lock_scan_size` argument should be a positive Integer, got #{lock_scan_size.inspect}."
207
+ )
208
+ end
209
+
144
210
  RedisQueuedLocks::Swarm::ZombieInfo.zombie_acquirers(
145
211
  rql_client.redis_client,
146
212
  zombie_ttl,
147
- lock_scan_size
213
+ lock_scan_size,
214
+ rql_client.config['lock_index_enabled']
148
215
  )
149
216
  end
150
217
 
@@ -153,7 +220,16 @@ class RedisQueuedLocks::Swarm
153
220
  #
154
221
  # @api public
155
222
  # @since 1.9.0
223
+ # @version 1.21.0
156
224
  def zombie_hosts(zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'])
225
+ # NOTE: (arguments) the same ranges as the config keys;
226
+ if !zombie_ttl.is_a?(::Integer) || zombie_ttl <= 0
227
+ raise(
228
+ RedisQueuedLocks::SwarmArgumentError,
229
+ "`:zombie_ttl` argument should be a positive Integer, got #{zombie_ttl.inspect}."
230
+ )
231
+ end
232
+
157
233
  RedisQueuedLocks::Swarm::ZombieInfo.zombie_hosts(rql_client.redis_client, zombie_ttl)
158
234
  end
159
235
 
@@ -163,14 +239,30 @@ class RedisQueuedLocks::Swarm
163
239
  #
164
240
  # @api public
165
241
  # @since 1.9.0
242
+ # @version 1.21.0
166
243
  def zombies_info(
167
244
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
168
245
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size']
169
246
  )
247
+ # NOTE: (arguments) the same ranges as the config keys;
248
+ if !zombie_ttl.is_a?(::Integer) || zombie_ttl <= 0
249
+ raise(
250
+ RedisQueuedLocks::SwarmArgumentError,
251
+ "`:zombie_ttl` argument should be a positive Integer, got #{zombie_ttl.inspect}."
252
+ )
253
+ end
254
+ if !lock_scan_size.is_a?(::Integer) || lock_scan_size <= 0
255
+ raise(
256
+ RedisQueuedLocks::SwarmArgumentError,
257
+ "`:lock_scan_size` argument should be a positive Integer, got #{lock_scan_size.inspect}."
258
+ )
259
+ end
260
+
170
261
  RedisQueuedLocks::Swarm::ZombieInfo.zombies_info(
171
262
  rql_client.redis_client,
172
263
  zombie_ttl,
173
- lock_scan_size
264
+ lock_scan_size,
265
+ rql_client.config['lock_index_enabled']
174
266
  )
175
267
  end
176
268
 
@@ -39,7 +39,7 @@ module RedisQueuedLocks::Utilities
39
39
  # @return [Boolean]
40
40
  #
41
41
  # @api public
42
- # @since ?.?.?
42
+ # @since 1.16.0
43
43
  RUBY_OVER_4 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('4.0.0')
44
44
 
45
45
  # @yield
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 1.19.0
9
- VERSION = '1.19.0'
8
+ # @version 1.21.0
9
+ VERSION = '1.21.0'
10
10
  end
@@ -70,6 +70,20 @@ module RedisQueuedLocks
70
70
  Integer|Float hold_time,
71
71
  untyped? instrument
72
72
  ) -> void
73
+
74
+ def self.lock_obtaining_failed: (
75
+ RQL::instrObj instrumenter,
76
+ bool instr_sampled,
77
+ String lock_key,
78
+ Symbol rw_mode,
79
+ Integer ttl,
80
+ String acq_id,
81
+ String hst_id,
82
+ Integer|Float ts,
83
+ Integer|Float acq_time,
84
+ Symbol result,
85
+ untyped? instrument
86
+ ) -> void
73
87
  end
74
88
  end
75
89
  end
@@ -72,6 +72,19 @@ module RedisQueuedLocks
72
72
  Symbol access_strategy,
73
73
  Symbol rw_mode
74
74
  ) -> void
75
+
76
+ def self.lock_obtaining_failed: (
77
+ RQL::loggerObj logger,
78
+ bool log_sampled,
79
+ String lock_key,
80
+ Integer queue_ttl,
81
+ String acquirer_id,
82
+ String host_id,
83
+ Integer|Float acq_time,
84
+ Symbol access_strategy,
85
+ Symbol rw_mode,
86
+ Symbol result
87
+ ) -> void
75
88
  end
76
89
  end
77
90
  end