redis_queued_locks 1.21.0 → 1.22.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 +216 -25
- data/.claude/rules/acquirer.md +6 -5
- data/.claude/rules/arguments.md +2 -2
- data/.claude/rules/changelog.md +27 -0
- data/.claude/rules/git.md +1 -1
- data/.claude/rules/logic.md +10 -12
- data/.claude/rules/swarm.md +3 -4
- data/.claude/rules/tests.md +6 -6
- data/.claude/rules/type-checking.md +13 -14
- data/.claude/rules/visitors.md +5 -5
- data/.claude/skills/rql-code-review/SKILL.md +112 -35
- data/CHANGELOG.md +98 -244
- data/CLAUDE.md +9 -8
- data/README.md +275 -85
- data/Rakefile +11 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +28 -12
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +38 -16
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -15
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +430 -63
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +72 -22
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +3 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +10 -5
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -9
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +52 -23
- data/lib/redis_queued_locks/acquirer/locks.rb +6 -2
- data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -7
- data/lib/redis_queued_locks/acquirer/queues.rb +17 -6
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +6 -2
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +6 -2
- data/lib/redis_queued_locks/client.rb +66 -24
- data/lib/redis_queued_locks/config.rb +93 -1
- data/lib/redis_queued_locks/resource.rb +124 -9
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +6 -5
- data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
- data/lib/redis_queued_locks/version.rb +2 -2
- data/rbs_collection.lock.yaml +4 -0
- data/rbs_collection.yaml +2 -1
- data/sig/manifest.yml +1 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +12 -6
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +21 -14
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +59 -11
- data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +5 -3
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +14 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +10 -7
- data/sig/redis_queued_locks/config.rbs +54 -1
- data/sig/redis_queued_locks/instrument/sampler.rbs +3 -1
- data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
- data/sig/redis_queued_locks/logging/sampler.rbs +3 -1
- data/sig/redis_queued_locks/resource.rbs +22 -12
- data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
- data/sig/redis_queued_locks/utilities.rbs +2 -1
- data/sig/redis_queued_locks.rbs +7 -0
- metadata +4 -3
- /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
|
@@ -134,7 +134,8 @@ module RedisQueuedLocks::Acquirer::Keys
|
|
|
134
134
|
|
|
135
135
|
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
136
136
|
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
137
|
-
# the index yet) are skipped: all their keys are expired
|
|
137
|
+
# the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
|
|
138
|
+
# expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
|
|
138
139
|
#
|
|
139
140
|
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
140
141
|
# operation modules are independent of each other, see `Locks.each_lock_name_batch`);
|
|
@@ -147,6 +148,7 @@ module RedisQueuedLocks::Acquirer::Keys
|
|
|
147
148
|
#
|
|
148
149
|
# @api private
|
|
149
150
|
# @since 1.20.0
|
|
151
|
+
# @version 1.22.0
|
|
150
152
|
def each_lock_name_batch(redis_client, scan_size)
|
|
151
153
|
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
152
154
|
# @type var lock_names: Array[String]
|
|
@@ -155,7 +157,9 @@ module RedisQueuedLocks::Acquirer::Keys
|
|
|
155
157
|
redis_client.zscan(
|
|
156
158
|
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
157
159
|
) do |lock_name, lock_index_score|
|
|
158
|
-
next if
|
|
160
|
+
next if RedisQueuedLocks::Resource.lock_index_score_expired?(
|
|
161
|
+
lock_index_score, lock_index_time
|
|
162
|
+
)
|
|
159
163
|
|
|
160
164
|
lock_names << lock_name
|
|
161
165
|
next if lock_names.size < scan_size
|
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
# NOTE: Lock Series PoC
|
|
4
4
|
# @api private
|
|
5
5
|
# @since 1.16.1
|
|
6
|
-
# @version 1.
|
|
6
|
+
# @version 1.22.0
|
|
7
7
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
8
8
|
class << self
|
|
9
9
|
# NOTE: Lock Series PoC
|
|
10
10
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
11
|
+
# @param priority [Integer] The lock request priority of the series.
|
|
11
12
|
# @api private
|
|
12
13
|
# @since 1.16.1
|
|
13
|
-
# @version 1.
|
|
14
|
+
# @version 1.22.0
|
|
14
15
|
def lock_series_obtained( # steep:ignore
|
|
15
16
|
instrumenter,
|
|
16
17
|
instr_sampled,
|
|
17
18
|
lock_keys,
|
|
18
19
|
rw_mode,
|
|
20
|
+
priority,
|
|
19
21
|
ttl,
|
|
20
22
|
acq_id,
|
|
21
23
|
hst_id,
|
|
@@ -25,20 +27,22 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
|
25
27
|
)
|
|
26
28
|
return unless instr_sampled
|
|
27
29
|
instrumenter.notify('redis_queued_locks.lock_series_obtained', {
|
|
28
|
-
lock_keys:, rw_mode:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
30
|
+
lock_keys:, rw_mode:, priority:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
29
31
|
}) rescue nil
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
# NOTE: Lock Series PoC
|
|
33
35
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
36
|
+
# @param priority [Integer] The lock request priority of the series.
|
|
34
37
|
# @api private
|
|
35
38
|
# @since 1.16.1
|
|
36
|
-
# @version 1.
|
|
39
|
+
# @version 1.22.0
|
|
37
40
|
def lock_series_hold_and_release( # steep:ignore
|
|
38
41
|
instrumenter,
|
|
39
42
|
instr_sampled,
|
|
40
43
|
lock_keys,
|
|
41
44
|
rw_mode,
|
|
45
|
+
priority,
|
|
42
46
|
ttl,
|
|
43
47
|
acq_id,
|
|
44
48
|
hst_id,
|
|
@@ -49,7 +53,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
|
49
53
|
)
|
|
50
54
|
return unless instr_sampled
|
|
51
55
|
instrumenter.notify('redis_queued_locks.lock_series_hold_and_release', {
|
|
52
|
-
lock_keys:, rw_mode:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:,
|
|
56
|
+
lock_keys:, rw_mode:, priority:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:,
|
|
57
|
+
instrument:
|
|
53
58
|
}) rescue nil
|
|
54
59
|
end
|
|
55
60
|
end
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
# NOTE: Lock Series PoC
|
|
4
4
|
# @api private
|
|
5
5
|
# @since 1.16.1
|
|
6
|
-
# @version 1.
|
|
6
|
+
# @version 1.22.0
|
|
7
7
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
8
8
|
class << self
|
|
9
9
|
# NOTE: Lock Series PoC
|
|
10
10
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
11
|
+
# @param priority [Integer] The lock request priority of the series.
|
|
11
12
|
# @api private
|
|
12
13
|
# @since 1.16.0
|
|
13
|
-
# @version 1.
|
|
14
|
+
# @version 1.22.0
|
|
14
15
|
def start_lock_series_obtaining( # steep:ignore
|
|
15
16
|
logger,
|
|
16
17
|
log_sampled,
|
|
@@ -19,7 +20,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
19
20
|
acquirer_id,
|
|
20
21
|
host_id,
|
|
21
22
|
access_strategy,
|
|
22
|
-
rw_mode
|
|
23
|
+
rw_mode,
|
|
24
|
+
priority
|
|
23
25
|
)
|
|
24
26
|
return unless log_sampled
|
|
25
27
|
|
|
@@ -30,15 +32,17 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
30
32
|
"acq_id => '#{acquirer_id}' " \
|
|
31
33
|
"hst_id => '#{host_id}' " \
|
|
32
34
|
"acs_strat => '#{access_strategy}' " \
|
|
33
|
-
"rw_mode => '#{rw_mode}'"
|
|
35
|
+
"rw_mode => '#{rw_mode}' " \
|
|
36
|
+
"priority => #{priority}"
|
|
34
37
|
end rescue nil
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
# NOTE: Lock Series PoC
|
|
38
41
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
42
|
+
# @param priority [Integer] The lock request priority of the series.
|
|
39
43
|
# @api private
|
|
40
44
|
# @since 1.16.0
|
|
41
|
-
# @version 1.
|
|
45
|
+
# @version 1.22.0
|
|
42
46
|
def lock_series_obtained( # steep:ignore
|
|
43
47
|
logger,
|
|
44
48
|
log_sampled,
|
|
@@ -48,7 +52,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
48
52
|
host_id,
|
|
49
53
|
acq_time,
|
|
50
54
|
access_strategy,
|
|
51
|
-
rw_mode
|
|
55
|
+
rw_mode,
|
|
56
|
+
priority
|
|
52
57
|
)
|
|
53
58
|
return unless log_sampled
|
|
54
59
|
|
|
@@ -60,15 +65,17 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
60
65
|
"hst_id => '#{host_id}' " \
|
|
61
66
|
"acs_strat => '#{access_strategy}' " \
|
|
62
67
|
"rw_mode => '#{rw_mode}' " \
|
|
68
|
+
"priority => #{priority} " \
|
|
63
69
|
"acq_time => #{acq_time} (ms)"
|
|
64
70
|
end rescue nil
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
# NOTE: Lock Series PoC
|
|
68
74
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
75
|
+
# @param priority [Integer] The lock request priority of the series.
|
|
69
76
|
# @api private
|
|
70
77
|
# @since 1.16.0
|
|
71
|
-
# @version 1.
|
|
78
|
+
# @version 1.22.0
|
|
72
79
|
def expire_lock_series( # steep:ignore
|
|
73
80
|
logger,
|
|
74
81
|
log_sampled,
|
|
@@ -77,7 +84,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
77
84
|
acquirer_id,
|
|
78
85
|
host_id,
|
|
79
86
|
access_strategy,
|
|
80
|
-
rw_mode
|
|
87
|
+
rw_mode,
|
|
88
|
+
priority
|
|
81
89
|
)
|
|
82
90
|
return unless log_sampled
|
|
83
91
|
|
|
@@ -88,7 +96,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
88
96
|
"acq_id => '#{acquirer_id}' " \
|
|
89
97
|
"hst_id => '#{host_id}' " \
|
|
90
98
|
"acs_strat => '#{access_strategy}' " \
|
|
91
|
-
"rw_mode => '#{rw_mode}'"
|
|
99
|
+
"rw_mode => '#{rw_mode}' " \
|
|
100
|
+
"priority => #{priority}"
|
|
92
101
|
end rescue nil
|
|
93
102
|
end
|
|
94
103
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# NOTE: Lock Series PoC
|
|
4
|
-
# rubocop:disable
|
|
4
|
+
# rubocop:disable Metrics/ModuleLength, Metrics/ClassLength, Metrics/MethodLength
|
|
5
5
|
# @api private
|
|
6
6
|
# @since 1.16.0
|
|
7
|
-
# @version 1.
|
|
7
|
+
# @version 1.22.0
|
|
8
8
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
9
9
|
require_relative 'lock_series_poc/log_visitor'
|
|
10
10
|
require_relative 'lock_series_poc/instr_visitor'
|
|
@@ -15,7 +15,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
15
15
|
class << self
|
|
16
16
|
# @api private
|
|
17
17
|
# @since 1.16.0
|
|
18
|
-
# @version 1.
|
|
18
|
+
# @version 1.22.0
|
|
19
19
|
def lock_series_poc( # steep:ignore
|
|
20
20
|
redis,
|
|
21
21
|
lock_names,
|
|
@@ -45,6 +45,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
45
45
|
conflict_strategy:,
|
|
46
46
|
read_write_mode:,
|
|
47
47
|
access_strategy:,
|
|
48
|
+
priority:,
|
|
49
|
+
lock_priorities:,
|
|
48
50
|
log_sampling_enabled:,
|
|
49
51
|
log_sampling_percent:,
|
|
50
52
|
log_sampler:,
|
|
@@ -57,6 +59,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
57
59
|
)
|
|
58
60
|
# NOTE: (RW) the lock mode is the same for all locks of the series
|
|
59
61
|
# (see RedisQueuedLocks::Acquirer::AcquireLock for read_write_mode validations);
|
|
62
|
+
# NOTE: the lock request priority is the same for all locks of the series too (validated
|
|
63
|
+
# below: the series logs and instrumentation report it);
|
|
60
64
|
|
|
61
65
|
case meta
|
|
62
66
|
when Hash, NilClass then nil
|
|
@@ -120,6 +124,23 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
120
124
|
)
|
|
121
125
|
end
|
|
122
126
|
|
|
127
|
+
# NOTE: the priority of the series is validated before the series logs and instrumentation:
|
|
128
|
+
# a priority name is resolved to its Integer priority (the series events report the Integer
|
|
129
|
+
# priority; the same check as in RedisQueuedLocks::Acquirer::AcquireLock);
|
|
130
|
+
priority = lock_priorities.fetch(priority, priority) if priority.is_a?(::Symbol)
|
|
131
|
+
if !priority.is_a?(::Integer) ||
|
|
132
|
+
priority < RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY ||
|
|
133
|
+
priority > RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY
|
|
134
|
+
raise(
|
|
135
|
+
RedisQueuedLocks::ArgumentError,
|
|
136
|
+
"`:priority` argument should be an Integer from " \
|
|
137
|
+
"#{RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY} to " \
|
|
138
|
+
"#{RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY} or a priority name of " \
|
|
139
|
+
"`config['lock_priorities']` (#{lock_priorities.keys.map(&:inspect).join(', ')}), " \
|
|
140
|
+
"got #{priority.inspect}."
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
123
144
|
locks_count = lock_names.size
|
|
124
145
|
|
|
125
146
|
lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
|
|
@@ -164,10 +185,10 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
164
185
|
identity
|
|
165
186
|
)
|
|
166
187
|
|
|
167
|
-
|
|
188
|
+
LogVisitor.start_lock_series_obtaining( # steep:ignore
|
|
168
189
|
logger, log_sampled, lock_keys_for_instrumentation,
|
|
169
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
170
|
-
read_write_mode
|
|
190
|
+
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
191
|
+
access_strategy, read_write_mode, priority
|
|
171
192
|
)
|
|
172
193
|
|
|
173
194
|
acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
@@ -208,6 +229,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
208
229
|
conflict_strategy:,
|
|
209
230
|
read_write_mode:,
|
|
210
231
|
access_strategy:,
|
|
232
|
+
priority:,
|
|
233
|
+
lock_priorities:,
|
|
211
234
|
log_sampling_enabled:,
|
|
212
235
|
log_sampling_percent:,
|
|
213
236
|
log_sampler:,
|
|
@@ -240,7 +263,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
240
263
|
else
|
|
241
264
|
failed_on_lock = lock_operation_options[:lock_name]
|
|
242
265
|
failed_locks_and_errors[lock_operation_options[:lock_name]] = result
|
|
243
|
-
# NOTE: release all previously acquired locks
|
|
266
|
+
# NOTE: release all previously acquired locks
|
|
267
|
+
# (non-exceptional failure: raise_errors: false)
|
|
244
268
|
unless is_failed_series_released
|
|
245
269
|
release_lock_series( # steep:ignore
|
|
246
270
|
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
@@ -251,20 +275,22 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
251
275
|
end
|
|
252
276
|
end
|
|
253
277
|
|
|
254
|
-
if
|
|
278
|
+
if successfully_acquired_locks.size == lock_names.size &&
|
|
279
|
+
successfully_acquired_locks.all? { |res| res[:ok] }
|
|
255
280
|
acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
256
281
|
acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
|
|
257
282
|
ts = Time.now.to_f
|
|
258
283
|
|
|
259
|
-
|
|
284
|
+
LogVisitor.lock_series_obtained( # steep:ignore
|
|
260
285
|
logger, log_sampled, lock_keys_for_instrumentation,
|
|
261
286
|
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
262
|
-
acq_time, access_strategy, read_write_mode
|
|
287
|
+
acq_time, access_strategy, read_write_mode, priority
|
|
263
288
|
)
|
|
264
289
|
|
|
265
|
-
|
|
266
|
-
instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode,
|
|
267
|
-
ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time,
|
|
290
|
+
InstrVisitor.lock_series_obtained( # steep:ignore
|
|
291
|
+
instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode, priority,
|
|
292
|
+
ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time,
|
|
293
|
+
instrument
|
|
268
294
|
)
|
|
269
295
|
|
|
270
296
|
yield_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
@@ -274,8 +300,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
274
300
|
).ceil(2)
|
|
275
301
|
|
|
276
302
|
yield_result = nil
|
|
277
|
-
is_lock_manually_released
|
|
278
|
-
hold_time
|
|
303
|
+
# @type var is_lock_manually_released: bool
|
|
304
|
+
# @type var hold_time: Float | Integer
|
|
279
305
|
|
|
280
306
|
begin
|
|
281
307
|
yield_result = yield_expire( # steep:ignore
|
|
@@ -316,17 +342,18 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
316
342
|
hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
|
|
317
343
|
ts = Time.now.to_f
|
|
318
344
|
|
|
319
|
-
|
|
345
|
+
LogVisitor.expire_lock_series( # steep:ignore
|
|
320
346
|
logger, log_sampled, lock_keys_for_instrumentation,
|
|
321
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
322
|
-
read_write_mode
|
|
347
|
+
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
348
|
+
access_strategy, read_write_mode, priority
|
|
323
349
|
) if is_lock_manually_released
|
|
324
350
|
|
|
325
|
-
|
|
351
|
+
InstrVisitor.lock_series_hold_and_release( # steep:ignore
|
|
326
352
|
instrumenter,
|
|
327
353
|
instr_sampled,
|
|
328
354
|
lock_keys_for_instrumentation,
|
|
329
355
|
read_write_mode,
|
|
356
|
+
priority,
|
|
330
357
|
ttl,
|
|
331
358
|
acquirer_id_for_instrumentation,
|
|
332
359
|
host_id_for_instrumentation,
|
|
@@ -362,7 +389,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
362
389
|
lock_series: lock_names,
|
|
363
390
|
acquired_locks:,
|
|
364
391
|
missing_locks:,
|
|
365
|
-
failed_on_lock
|
|
392
|
+
failed_on_lock:
|
|
366
393
|
}
|
|
367
394
|
}
|
|
368
395
|
end
|
|
@@ -382,7 +409,9 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
382
409
|
#
|
|
383
410
|
# @api private
|
|
384
411
|
# @since 1.18.0
|
|
385
|
-
def release_lock_series(
|
|
412
|
+
def release_lock_series( # steep:ignore
|
|
413
|
+
redis, read_write_mode, successfully_acquired_locks, acquirer_id
|
|
414
|
+
)
|
|
386
415
|
lock_names = obtained_lock_names(successfully_acquired_locks) # steep:ignore
|
|
387
416
|
|
|
388
417
|
if read_write_mode == :read
|
|
@@ -427,7 +456,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
427
456
|
own_lock_keys.each { |lock_key| transaction.call('DEL', lock_key) }
|
|
428
457
|
end
|
|
429
458
|
# NOTE: `nil` => the transaction is aborted (a watched lock is changed) => retry
|
|
430
|
-
break unless released
|
|
459
|
+
break unless released == nil
|
|
431
460
|
end
|
|
432
461
|
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
433
462
|
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
@@ -481,4 +510,4 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
481
510
|
end
|
|
482
511
|
end
|
|
483
512
|
end
|
|
484
|
-
# rubocop:enable
|
|
513
|
+
# rubocop:enable Metrics/ModuleLength, Metrics/ClassLength, Metrics/MethodLength
|
|
@@ -115,7 +115,8 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
115
115
|
|
|
116
116
|
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
117
117
|
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
118
|
-
# the index yet) are skipped: all their keys are expired
|
|
118
|
+
# the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
|
|
119
|
+
# expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
|
|
119
120
|
#
|
|
120
121
|
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
121
122
|
# operation modules are independent of each other, see `extract_locks_info`);
|
|
@@ -128,6 +129,7 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
128
129
|
#
|
|
129
130
|
# @api private
|
|
130
131
|
# @since 1.20.0
|
|
132
|
+
# @version 1.22.0
|
|
131
133
|
def each_lock_name_batch(redis_client, scan_size)
|
|
132
134
|
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
133
135
|
# @type var lock_names: Array[String]
|
|
@@ -136,7 +138,9 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
136
138
|
redis_client.zscan(
|
|
137
139
|
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
138
140
|
) do |lock_name, lock_index_score|
|
|
139
|
-
next if
|
|
141
|
+
next if RedisQueuedLocks::Resource.lock_index_score_expired?(
|
|
142
|
+
lock_index_score, lock_index_time
|
|
143
|
+
)
|
|
140
144
|
|
|
141
145
|
lock_names << lock_name
|
|
142
146
|
next if lock_names.size < scan_size
|
|
@@ -16,22 +16,34 @@ module RedisQueuedLocks::Acquirer::QueueInfo
|
|
|
16
16
|
# - result format: {
|
|
17
17
|
# "lock_queue" => "rql:lock_queue:your_lock_name", # lock queue key in redis,
|
|
18
18
|
# "queue" => [
|
|
19
|
-
# {
|
|
20
|
-
#
|
|
19
|
+
# {
|
|
20
|
+
# "acq_id" => "rql:acq:123/456/789/987/id",
|
|
21
|
+
# "score" => 123.456,
|
|
22
|
+
# "rw_mode" => "write",
|
|
23
|
+
# "priority" => 0
|
|
24
|
+
# },
|
|
21
25
|
# ...
|
|
22
26
|
# ] # ordered set (by score) with information about an acquirer, their position in queue
|
|
23
|
-
# #
|
|
27
|
+
# # (the enqueue time shifted by the priority band: see
|
|
28
|
+
# # `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`), the requested lock mode and
|
|
29
|
+
# # the lock request priority (decoded from the position)
|
|
24
30
|
# <additional keys when the queue of read lock requests exists>:
|
|
25
31
|
# "read_lock_queue" => "rql:lock_read_queue:your_lock_name", # read lock queue key in redis
|
|
26
32
|
# "read_queue" => [
|
|
27
|
-
# {
|
|
33
|
+
# {
|
|
34
|
+
# "acq_id" => "rql:acq:123/456/789/987/id",
|
|
35
|
+
# "score" => 123.456,
|
|
36
|
+
# "rw_mode" => "read",
|
|
37
|
+
# "priority" => 0
|
|
38
|
+
# },
|
|
28
39
|
# ...
|
|
29
40
|
# ] # read lock requests (positions are comparable with the write lock request positions)
|
|
30
41
|
# }
|
|
31
42
|
#
|
|
32
43
|
# @api private
|
|
33
44
|
# @since 1.0.0
|
|
34
|
-
# @version 1.
|
|
45
|
+
# @version 1.22.0
|
|
46
|
+
# rubocop:disable Metrics/MethodLength
|
|
35
47
|
def queue_info(redis_client, lock_name)
|
|
36
48
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
|
37
49
|
read_lock_key_queue = RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
|
|
@@ -54,14 +66,24 @@ module RedisQueuedLocks::Acquirer::QueueInfo
|
|
|
54
66
|
queue_info = {
|
|
55
67
|
'lock_queue' => lock_key_queue,
|
|
56
68
|
'queue' => zrange_cmd_res.map do |val|
|
|
57
|
-
{
|
|
69
|
+
{
|
|
70
|
+
'acq_id' => val[0],
|
|
71
|
+
'score' => val[1],
|
|
72
|
+
'rw_mode' => 'write',
|
|
73
|
+
'priority' => RedisQueuedLocks::Resource.lock_request_priority(val[1])
|
|
74
|
+
}
|
|
58
75
|
end
|
|
59
76
|
}
|
|
60
77
|
# NOTE: (RW) the queue of read lock requests
|
|
61
78
|
unless read_zrange_cmd_res.empty?
|
|
62
79
|
queue_info['read_lock_queue'] = read_lock_key_queue
|
|
63
80
|
queue_info['read_queue'] = read_zrange_cmd_res.map do |val|
|
|
64
|
-
{
|
|
81
|
+
{
|
|
82
|
+
'acq_id' => val[0],
|
|
83
|
+
'score' => val[1],
|
|
84
|
+
'rw_mode' => 'read',
|
|
85
|
+
'priority' => RedisQueuedLocks::Resource.lock_request_priority(val[1])
|
|
86
|
+
}
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
queue_info
|
|
@@ -70,5 +92,6 @@ module RedisQueuedLocks::Acquirer::QueueInfo
|
|
|
70
92
|
nil
|
|
71
93
|
end
|
|
72
94
|
end
|
|
95
|
+
# rubocop:enable Metrics/MethodLength
|
|
73
96
|
end
|
|
74
97
|
end
|
|
@@ -15,7 +15,7 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
15
15
|
#
|
|
16
16
|
# @api private
|
|
17
17
|
# @since 1.0.0
|
|
18
|
-
# @version 1.
|
|
18
|
+
# @version 1.22.0
|
|
19
19
|
def queues(redis_client, scan_size:, with_info:, lock_index_enabled:)
|
|
20
20
|
# NOTE: (arguments) the same range as the config key: zero/negative sizes fail SCAN/ZSCAN
|
|
21
21
|
# (`COUNT 0`) and the batching (`each_slice(0)`);
|
|
@@ -102,7 +102,8 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
102
102
|
|
|
103
103
|
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
104
104
|
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
105
|
-
# the index yet) are skipped: all their keys are expired
|
|
105
|
+
# the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
|
|
106
|
+
# expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
|
|
106
107
|
#
|
|
107
108
|
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
108
109
|
# operation modules are independent of each other, see `Locks.each_lock_name_batch`);
|
|
@@ -115,6 +116,7 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
115
116
|
#
|
|
116
117
|
# @api private
|
|
117
118
|
# @since 1.20.0
|
|
119
|
+
# @version 1.22.0
|
|
118
120
|
def each_lock_name_batch(redis_client, scan_size)
|
|
119
121
|
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
120
122
|
# @type var lock_names: Array[String]
|
|
@@ -123,7 +125,9 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
123
125
|
redis_client.zscan(
|
|
124
126
|
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
125
127
|
) do |lock_name, lock_index_score|
|
|
126
|
-
next if
|
|
128
|
+
next if RedisQueuedLocks::Resource.lock_index_score_expired?(
|
|
129
|
+
lock_index_score, lock_index_time
|
|
130
|
+
)
|
|
127
131
|
|
|
128
132
|
lock_names << lock_name
|
|
129
133
|
next if lock_names.size < scan_size
|
|
@@ -139,11 +143,13 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
139
143
|
# @param lock_queues [Set<String>]
|
|
140
144
|
# @param scan_size [Integer] The number of queues whose requests are read in one round trip.
|
|
141
145
|
# @return [Set<Hash<Symbol,Any>>]
|
|
142
|
-
# - each request: `{ 'acq_id' => String, 'score' => Float, 'rw_mode' => 'read'/'write'
|
|
146
|
+
# - each request: `{ 'acq_id' => String, 'score' => Float, 'rw_mode' => 'read'/'write',
|
|
147
|
+
# 'priority' => Integer }` (the priority is decoded from the position: see
|
|
148
|
+
# `RedisQueuedLocks::Resource::LOCK_PRIORITY_BAND`);
|
|
143
149
|
#
|
|
144
150
|
# @api private
|
|
145
151
|
# @since 1.0.0
|
|
146
|
-
# @version 1.
|
|
152
|
+
# @version 1.22.0
|
|
147
153
|
def extract_queues_info(redis_client, lock_queues, scan_size)
|
|
148
154
|
# NOTE: the same approach as in `QueueInfo` (duplicated on purpose: public operation modules
|
|
149
155
|
# do not reuse each other, they share logic via `Resource`, `Utilities`);
|
|
@@ -171,7 +177,12 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
171
177
|
seeded_queues << {
|
|
172
178
|
queue: lock_queue,
|
|
173
179
|
requests: queues_requests[index].map do |val|
|
|
174
|
-
{
|
|
180
|
+
{
|
|
181
|
+
'acq_id' => val[0],
|
|
182
|
+
'score' => val[1],
|
|
183
|
+
'rw_mode' => rw_mode,
|
|
184
|
+
'priority' => RedisQueuedLocks::Resource.lock_request_priority(val[1])
|
|
185
|
+
}
|
|
175
186
|
end
|
|
176
187
|
}
|
|
177
188
|
end
|
|
@@ -272,7 +272,8 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
|
|
|
272
272
|
|
|
273
273
|
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
274
274
|
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
275
|
-
# the index yet) are skipped: all their keys are expired
|
|
275
|
+
# the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
|
|
276
|
+
# expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
|
|
276
277
|
#
|
|
277
278
|
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
278
279
|
# operation modules are independent of each other, see `Locks.each_lock_name_batch`);
|
|
@@ -285,6 +286,7 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
|
|
|
285
286
|
#
|
|
286
287
|
# @api private
|
|
287
288
|
# @since 1.20.0
|
|
289
|
+
# @version 1.22.0
|
|
288
290
|
def each_lock_name_batch(redis_client, scan_size)
|
|
289
291
|
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
290
292
|
# @type var lock_names: Array[String]
|
|
@@ -293,7 +295,9 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
|
|
|
293
295
|
redis_client.zscan(
|
|
294
296
|
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
295
297
|
) do |lock_name, lock_index_score|
|
|
296
|
-
next if
|
|
298
|
+
next if RedisQueuedLocks::Resource.lock_index_score_expired?(
|
|
299
|
+
lock_index_score, lock_index_time
|
|
300
|
+
)
|
|
297
301
|
|
|
298
302
|
lock_names << lock_name
|
|
299
303
|
next if lock_names.size < scan_size
|
|
@@ -427,7 +427,8 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
427
427
|
|
|
428
428
|
# Iterates over the lock names of the lock index (one ZSCAN pass) in batches of `scan_size`
|
|
429
429
|
# lock names (batches are collected across ZSCAN pages). Expired lock names (not dropped from
|
|
430
|
-
# the index yet) are skipped: all their keys are expired
|
|
430
|
+
# the index yet) are skipped: all their keys are expired (`+inf` names, keys without TTL, never
|
|
431
|
+
# expire: see `RedisQueuedLocks::Resource.lock_index_score_expired?`).
|
|
431
432
|
#
|
|
432
433
|
# NOTE: each operation module iterates over the lock index by itself (duplicated on purpose:
|
|
433
434
|
# operation modules are independent of each other, see `Locks.each_lock_name_batch`);
|
|
@@ -440,6 +441,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
440
441
|
#
|
|
441
442
|
# @api private
|
|
442
443
|
# @since 1.20.0
|
|
444
|
+
# @version 1.22.0
|
|
443
445
|
def each_lock_name_batch(redis_client, scan_size)
|
|
444
446
|
lock_index_time = RedisQueuedLocks::Resource.lock_index_time
|
|
445
447
|
# @type var lock_names: Array[String]
|
|
@@ -448,7 +450,9 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
|
|
|
448
450
|
redis_client.zscan(
|
|
449
451
|
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: scan_size
|
|
450
452
|
) do |lock_name, lock_index_score|
|
|
451
|
-
next if
|
|
453
|
+
next if RedisQueuedLocks::Resource.lock_index_score_expired?(
|
|
454
|
+
lock_index_score, lock_index_time
|
|
455
|
+
)
|
|
452
456
|
|
|
453
457
|
lock_names << lock_name
|
|
454
458
|
next if lock_names.size < scan_size
|