redis_queued_locks 1.19.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 +62 -11
  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 +68 -0
  15. data/CLAUDE.md +4 -2
  16. data/README.md +257 -22
  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 +97 -11
  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 +8 -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
@@ -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
@@ -94,6 +94,9 @@ class RedisQueuedLocks::Swarm
94
94
  )
95
95
  end
96
96
 
97
+ # NOTE: zombie locks are found via the lock index or via the keyspace SCAN
98
+ # (`config['lock_index_enabled']`, the client-wide mode);
99
+ #
97
100
  # @option zombie_ttl [Integer]
98
101
  # @option lock_scan_size [Integer]
99
102
  # @option queue_scan_size [Integer]
@@ -101,6 +104,7 @@ class RedisQueuedLocks::Swarm
101
104
  #
102
105
  # @api public
103
106
  # @since 1.9.0
107
+ # @version 1.20.0
104
108
  def flush_zombies(
105
109
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
106
110
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size'],
@@ -110,7 +114,8 @@ class RedisQueuedLocks::Swarm
110
114
  rql_client.redis_client,
111
115
  zombie_ttl,
112
116
  lock_scan_size,
113
- queue_scan_size
117
+ queue_scan_size,
118
+ rql_client.config['lock_index_enabled']
114
119
  )
115
120
  end
116
121
 
@@ -120,6 +125,7 @@ class RedisQueuedLocks::Swarm
120
125
  #
121
126
  # @api public
122
127
  # @since 1.9.0
128
+ # @version 1.20.0
123
129
  def zombie_locks(
124
130
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
125
131
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size']
@@ -127,7 +133,8 @@ class RedisQueuedLocks::Swarm
127
133
  RedisQueuedLocks::Swarm::ZombieInfo.zombie_locks(
128
134
  rql_client.redis_client,
129
135
  zombie_ttl,
130
- lock_scan_size
136
+ lock_scan_size,
137
+ rql_client.config['lock_index_enabled']
131
138
  )
132
139
  end
133
140
 
@@ -137,6 +144,7 @@ class RedisQueuedLocks::Swarm
137
144
  #
138
145
  # @api ppublic
139
146
  # @since 1.9.0
147
+ # @version 1.20.0
140
148
  def zombie_acquirers(
141
149
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
142
150
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size']
@@ -144,7 +152,8 @@ class RedisQueuedLocks::Swarm
144
152
  RedisQueuedLocks::Swarm::ZombieInfo.zombie_acquirers(
145
153
  rql_client.redis_client,
146
154
  zombie_ttl,
147
- lock_scan_size
155
+ lock_scan_size,
156
+ rql_client.config['lock_index_enabled']
148
157
  )
149
158
  end
150
159
 
@@ -163,6 +172,7 @@ class RedisQueuedLocks::Swarm
163
172
  #
164
173
  # @api public
165
174
  # @since 1.9.0
175
+ # @version 1.20.0
166
176
  def zombies_info(
167
177
  zombie_ttl: rql_client.config['swarm.flush_zombies.zombie_ttl'],
168
178
  lock_scan_size: rql_client.config['swarm.flush_zombies.zombie_lock_scan_size']
@@ -170,7 +180,8 @@ class RedisQueuedLocks::Swarm
170
180
  RedisQueuedLocks::Swarm::ZombieInfo.zombies_info(
171
181
  rql_client.redis_client,
172
182
  zombie_ttl,
173
- lock_scan_size
183
+ lock_scan_size,
184
+ rql_client.config['lock_index_enabled']
174
185
  )
175
186
  end
176
187
 
@@ -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.20.0
9
+ VERSION = '1.20.0'
10
10
  end
@@ -6,6 +6,13 @@ module RedisQueuedLocks
6
6
  module AcquireLock
7
7
  module TryToLock
8
8
  EXTEND_LOCK_PTTL: String
9
+ NO_LOCK_DATA: Hash[String, untyped]
10
+ LOCK_STATE_OBTAINER_REPLY: Integer
11
+ LOCK_STATE_REDIS_TIME_REPLY: Integer
12
+ LOCK_STATE_OWN_READ_LOCK_REPLY: Integer
13
+ LOCK_STATE_QUEUE_REPLIES_AT: Integer
14
+ QUEUE_HEAD_REPLY: Integer
15
+ OPPOSITE_QUEUE_HEAD_REPLY: Integer
9
16
 
10
17
  # { ok: bool, result: Symbol }
11
18
  # { ok: bool, result: Symbol }
@@ -31,19 +38,118 @@ module RedisQueuedLocks
31
38
  String lock_key_queue,
32
39
  String read_lock_key_queue,
33
40
  String lock_readers_key,
34
- String read_lock_key,
41
+ String? read_lock_key,
42
+ String lock_name,
35
43
  String acquirer_id,
36
44
  String host_id,
37
45
  Integer|Float acquirer_position,
38
46
  Integer? ttl,
39
47
  Integer queue_ttl,
48
+ bool lock_index_enabled,
49
+ Integer lock_index_grace_period,
40
50
  bool fail_fast,
41
51
  Symbol conflict_strategy,
42
52
  Symbol access_strategy,
43
53
  Hash[String|Symbol,untyped]? meta,
54
+ bool retry_attempt,
44
55
  bool log_sampled,
45
56
  bool instr_sampled
46
57
  ) -> { ok: bool, result: Symbol|Hash[Symbol,untyped] }
58
+
59
+ private
60
+
61
+ def queue_lock_request: (
62
+ RedisClient::Pipeline pipeline,
63
+ String request_queue,
64
+ String opposite_request_queue,
65
+ Integer|Float acquirer_position,
66
+ String acquirer_id,
67
+ Float acquirer_dead_score,
68
+ String lock_name,
69
+ Integer queue_ttl,
70
+ bool lock_index_enabled,
71
+ Integer lock_index_grace_period
72
+ ) -> void
73
+
74
+ def pipeline_lock_state: (
75
+ RedisClient::Pipeline pipeline,
76
+ String lock_key,
77
+ String lock_readers_key,
78
+ String acquirer_id,
79
+ bool read_mode,
80
+ bool queueing,
81
+ String request_queue,
82
+ String opposite_request_queue,
83
+ Integer|Float acquirer_position,
84
+ Float acquirer_dead_score,
85
+ String lock_name,
86
+ Integer queue_ttl,
87
+ bool lock_index_enabled,
88
+ Integer lock_index_grace_period
89
+ ) -> void
90
+
91
+ def find_opposite_request_ahead: (
92
+ Symbol access_strategy,
93
+ [String, Float]? opposite_waiting_request,
94
+ Integer|Float acquirer_position,
95
+ String acquirer_id
96
+ ) -> String?
97
+
98
+ type lockObstacle = :dead_score_reached
99
+ | :acquirer_is_not_first_in_queue
100
+ | :write_request_is_ahead
101
+ | :read_request_is_ahead
102
+ | :lock_is_still_acquired
103
+ | :read_lock_is_still_acquired
104
+
105
+ def lock_obstacle: (
106
+ bool read_mode,
107
+ Symbol access_strategy,
108
+ String acquirer_id,
109
+ String? waiting_acquirer,
110
+ String? opposite_request_ahead,
111
+ String? current_lock_obtainer,
112
+ bool read_locked
113
+ ) -> lockObstacle?
114
+
115
+ def log_lock_obstacle: (
116
+ RC::client rconn,
117
+ Symbol obstacle,
118
+ RQL::loggerObj logger,
119
+ bool log_sampled,
120
+ bool log_lock_try,
121
+ String lock_key,
122
+ Integer queue_ttl,
123
+ String acquirer_id,
124
+ String host_id,
125
+ Symbol access_strategy,
126
+ Symbol read_write_mode,
127
+ String? waiting_acquirer,
128
+ String? opposite_request_ahead,
129
+ String? current_lock_obtainer
130
+ ) -> void
131
+
132
+ def precheck_lock: (
133
+ RC::client rconn,
134
+ RQL::loggerObj logger,
135
+ bool log_sampled,
136
+ bool log_lock_try,
137
+ String lock_key,
138
+ Symbol read_write_mode,
139
+ String lock_readers_key,
140
+ String request_queue,
141
+ String opposite_request_queue,
142
+ String lock_name,
143
+ String acquirer_id,
144
+ String host_id,
145
+ Integer|Float acquirer_position,
146
+ Float acquirer_dead_score,
147
+ Integer queue_ttl,
148
+ bool lock_index_enabled,
149
+ Integer lock_index_grace_period,
150
+ bool fail_fast,
151
+ Symbol access_strategy
152
+ ) -> (lockObstacle | :fail_fast_no_try)?
47
153
  end
48
154
  end
49
155
  end
@@ -12,7 +12,7 @@ module RedisQueuedLocks
12
12
  RQL::loggerObj logger,
13
13
  String lock_key,
14
14
  String lock_readers_key,
15
- String read_lock_key,
15
+ String? read_lock_key,
16
16
  Symbol rw_mode,
17
17
  String acquirer_id,
18
18
  String host_id,
@@ -21,6 +21,8 @@ module RedisQueuedLocks
21
21
  ractor_id: Integer|String,
22
22
  ttl: Integer?,
23
23
  queue_ttl: Integer,
24
+ lock_index_enabled: bool,
25
+ lock_index_grace_period: Integer,
24
26
  timeout: Integer?,
25
27
  timed: bool,
26
28
  retry_count: Integer?,
@@ -9,6 +9,7 @@ module RedisQueuedLocks
9
9
  RC::client redis_client,
10
10
  Integer scan_size,
11
11
  Integer dead_ttl,
12
+ bool lock_index_enabled,
12
13
  RQL::loggerObj logger,
13
14
  RQL::instrObj instrumenter,
14
15
  untyped? instrument,
@@ -22,7 +23,15 @@ module RedisQueuedLocks
22
23
  bool instr_sample_this
23
24
  ) -> clearResult
24
25
 
25
- private def self.each_lock_queue: (RC::client redis_client, Integer scan_size) { (String lock_queue) -> untyped } -> Enumerator[String]
26
+ private def self.each_lock_queue: (RC::client redis_client, Integer scan_size) { (String lock_queue) -> void } -> void
27
+ private def self.drop_dead_requests: (RC::client redis_client, Array[String] lock_queues, Float dead_score) -> void
28
+ private def self.drop_dead_requests_of_locks: (
29
+ RC::client redis_client,
30
+ Array[String] lock_names,
31
+ Float dead_score,
32
+ Set[String] processed_queues
33
+ ) -> void
34
+ private def self.each_lock_name_batch: (RC::client redis_client, Integer scan_size) { (Array[String] lock_names) -> void } -> void
26
35
  end
27
36
  end
28
37
  end
@@ -14,6 +14,7 @@ module RedisQueuedLocks
14
14
  Symbol read_write_mode,
15
15
  bool all_read_locks,
16
16
  String acquirer_id,
17
+ bool lock_index_enabled,
17
18
  RQL::loggerObj logger,
18
19
  RQL::instrObj instrumenter,
19
20
  untyped? instrument,
@@ -32,14 +33,16 @@ module RedisQueuedLocks
32
33
  String lock_name,
33
34
  String lock_key,
34
35
  String acquirer_id,
35
- Integer milliseconds
36
+ Integer milliseconds,
37
+ bool lock_index_enabled
36
38
  ) -> extendResult
37
39
 
38
40
  private def self.extend_all_read_locks_ttl: (
39
41
  RC::client redis_client,
40
42
  String lock_name,
41
43
  String lock_key,
42
- Integer milliseconds
44
+ Integer milliseconds,
45
+ bool lock_index_enabled
43
46
  ) -> extendResult
44
47
  end
45
48
  end
@@ -4,7 +4,17 @@ module RedisQueuedLocks
4
4
  module Acquirer
5
5
  module Keys
6
6
  type keyList = Set[String]
7
- def self.keys: (RC::client redis_client, scan_size: Integer) -> keyList
7
+
8
+ KEY_STATE_REPLIES: Integer
9
+
10
+ def self.keys: (
11
+ RC::client redis_client,
12
+ scan_size: Integer,
13
+ lock_index_enabled: bool
14
+ ) -> keyList
15
+
16
+ private def self.collect_lock_keys: (RC::client redis_client, Array[String] lock_names, keyList keyset) -> void
17
+ private def self.each_lock_name_batch: (RC::client redis_client, Integer scan_size) { (Array[String] lock_names) -> void } -> void
8
18
  end
9
19
  end
10
20
  end