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.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +62 -11
- 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 +68 -0
- data/CLAUDE.md +4 -2
- data/README.md +257 -22
- 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 +97 -11
- 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 +8 -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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
zombie_locks << lock_key
|
|
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.
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
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::
|
|
146
|
-
) do |
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
162
|
-
# (
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
|
@@ -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
|
|
@@ -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) ->
|
|
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
|
-
|
|
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
|