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.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +96 -23
- data/.claude/rules/acquirer.md +39 -20
- data/.claude/rules/arguments.md +3 -2
- data/.claude/rules/git.md +3 -2
- data/.claude/rules/logic.md +32 -8
- data/.claude/rules/swarm.md +14 -9
- data/.claude/rules/tests.md +12 -3
- data/.claude/rules/type-checking.md +2 -1
- data/.claude/rules/visitors.md +10 -11
- data/.claude/skills/rql-code-review/SKILL.md +386 -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 +194 -0
- data/CLAUDE.md +6 -3
- data/README.md +502 -92
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +695 -128
- data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +8 -6
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +134 -26
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +229 -24
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +189 -18
- data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
- data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +151 -7
- data/lib/redis_queued_locks/acquirer/lock_info.rb +39 -41
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +79 -20
- data/lib/redis_queued_locks/acquirer/locks.rb +212 -83
- data/lib/redis_queued_locks/acquirer/queue_info.rb +11 -18
- data/lib/redis_queued_locks/acquirer/queues.rb +123 -50
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +253 -0
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +177 -61
- data/lib/redis_queued_locks/acquirer/release_lock.rb +84 -25
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +274 -70
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +164 -36
- data/lib/redis_queued_locks/config.rb +16 -13
- data/lib/redis_queued_locks/errors.rb +34 -4
- data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
- data/lib/redis_queued_locks/logging/sampler.rb +4 -8
- data/lib/redis_queued_locks/resource.rb +113 -6
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +333 -61
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
- data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
- data/lib/redis_queued_locks/swarm.rb +97 -5
- data/lib/redis_queued_locks/utilities.rb +1 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
- 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 +3 -3
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +3 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +19 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +20 -4
- 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 +52 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +6 -3
- data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +21 -2
- data/sig/redis_queued_locks/errors.rbs +5 -2
- data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
- data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
- data/sig/redis_queued_locks/resource.rbs +7 -1
- 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 +2 -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
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|