redis_queued_locks 1.20.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 +35 -13
- data/.claude/rules/acquirer.md +10 -9
- data/.claude/rules/logic.md +28 -5
- data/.claude/rules/swarm.md +2 -2
- data/.claude/rules/tests.md +6 -3
- data/.claude/rules/visitors.md +9 -10
- data/.claude/skills/rql-code-review/SKILL.md +24 -10
- data/CHANGELOG.md +126 -0
- data/CLAUDE.md +2 -1
- data/README.md +245 -70
- 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 +12 -1
- 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 +5 -5
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +111 -24
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +86 -8
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +122 -12
- 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 +10 -1
- data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
- 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 +55 -15
- data/lib/redis_queued_locks/acquirer/locks.rb +26 -9
- data/lib/redis_queued_locks/acquirer/queue_info.rb +0 -1
- data/lib/redis_queued_locks/acquirer/queues.rb +10 -1
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +31 -4
- data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +39 -4
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/client.rb +82 -40
- data/lib/redis_queued_locks/config.rb +12 -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 +14 -4
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
- 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.rb +86 -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/yield_expire.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +9 -0
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +15 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +14 -3
- 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 +1 -1
- data/sig/vendor/redis_client.rbs +1 -0
- metadata +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.21.0
|
|
6
6
|
# rubocop:disable Metrics/ModuleLength
|
|
7
7
|
module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
8
8
|
# @return [String]
|
|
@@ -14,11 +14,18 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
14
14
|
return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
|
|
15
15
|
LUA_SCRIPT
|
|
16
16
|
|
|
17
|
+
# @since 1.21.0
|
|
18
|
+
extend RedisQueuedLocks::Utilities
|
|
19
|
+
|
|
17
20
|
# rubocop:disable Metrics/ClassLength
|
|
18
21
|
class << self
|
|
22
|
+
# Extends the lock TTL and reports the operation (one log and one instrumentation event
|
|
23
|
+
# `redis_queued_locks.extend_lock_ttl` with the time of the whole operation).
|
|
24
|
+
#
|
|
19
25
|
# @param redis_client [RedisClient]
|
|
20
26
|
# @param lock_name [String]
|
|
21
27
|
# @param milliseconds [Integer]
|
|
28
|
+
# How many milliseconds should be added: a positive Integer.
|
|
22
29
|
# @param read_write_mode [Symbol]
|
|
23
30
|
# - `:write` - extends the write lock (of any acquirer);
|
|
24
31
|
# - `:read` - extends the read lock of the given acquirer (other read locks are not affected)
|
|
@@ -45,7 +52,7 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
45
52
|
#
|
|
46
53
|
# @api private
|
|
47
54
|
# @since 1.0.0
|
|
48
|
-
# @version 1.
|
|
55
|
+
# @version 1.21.0
|
|
49
56
|
# rubocop:disable Metrics/MethodLength
|
|
50
57
|
def extend_lock_ttl(
|
|
51
58
|
redis_client,
|
|
@@ -75,16 +82,105 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
75
82
|
)
|
|
76
83
|
end
|
|
77
84
|
|
|
85
|
+
# NOTE: a non-positive value would decrease the lock ttl or delete the lock;
|
|
86
|
+
if !milliseconds.is_a?(::Integer) || milliseconds <= 0
|
|
87
|
+
raise(
|
|
88
|
+
RedisQueuedLocks::ArgumentError,
|
|
89
|
+
"`milliseconds` argument should be a positive Integer, got #{milliseconds.inspect}."
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if read_write_mode == :read && all_read_locks != true && all_read_locks != false
|
|
94
|
+
raise(
|
|
95
|
+
RedisQueuedLocks::ArgumentError,
|
|
96
|
+
"`:all_read_locks` argument should be a boolean, got #{all_read_locks.inspect}."
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
78
100
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
ext_start_time = clock_gettime
|
|
103
|
+
result = fully_extend_lock_ttl(
|
|
104
|
+
redis_client, lock_name, lock_key, milliseconds, read_write_mode, all_read_locks,
|
|
105
|
+
acquirer_id, lock_index_enabled
|
|
106
|
+
)
|
|
107
|
+
time_at = Time.now.to_f
|
|
108
|
+
ext_end_time = clock_gettime
|
|
109
|
+
ext_time = ((ext_end_time - ext_start_time) / 1_000.0).ceil(2)
|
|
110
|
+
ext_result = result[:result]
|
|
111
|
+
extended_locks_count = ext_result.is_a?(::Hash) ? ext_result[:extended_locks_count] : 0
|
|
112
|
+
|
|
113
|
+
log_sampled = RedisQueuedLocks::Logging.should_log?(
|
|
114
|
+
log_sampling_enabled,
|
|
115
|
+
log_sample_this,
|
|
116
|
+
log_sampling_percent,
|
|
117
|
+
log_sampler
|
|
118
|
+
)
|
|
119
|
+
instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
|
|
120
|
+
instr_sampling_enabled,
|
|
121
|
+
instr_sample_this,
|
|
122
|
+
instr_sampling_percent,
|
|
123
|
+
instr_sampler
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
run_non_critical do
|
|
127
|
+
logger.debug do
|
|
128
|
+
"[redis_queued_locks.extend_lock_ttl] " \
|
|
129
|
+
"lock_key => '#{lock_key}' " \
|
|
130
|
+
"rw_mode => '#{read_write_mode}' " \
|
|
131
|
+
"all_read_locks => #{all_read_locks} " \
|
|
132
|
+
"ext_ttl => #{milliseconds} " \
|
|
133
|
+
"extended_locks_count => #{extended_locks_count} " \
|
|
134
|
+
"ext_time => #{ext_time} " \
|
|
135
|
+
"at => #{time_at}"
|
|
86
136
|
end
|
|
137
|
+
end if log_sampled
|
|
138
|
+
|
|
139
|
+
run_non_critical do
|
|
140
|
+
instrumenter.notify('redis_queued_locks.extend_lock_ttl', {
|
|
141
|
+
lock_key: lock_key,
|
|
142
|
+
rw_mode: read_write_mode,
|
|
143
|
+
all_read_locks: all_read_locks,
|
|
144
|
+
ext_ttl: milliseconds,
|
|
145
|
+
extended_locks_count: extended_locks_count,
|
|
146
|
+
ext_time: ext_time,
|
|
147
|
+
at: time_at,
|
|
148
|
+
instrument: instrument
|
|
149
|
+
})
|
|
150
|
+
end if instr_sampled
|
|
151
|
+
|
|
152
|
+
result
|
|
153
|
+
end
|
|
154
|
+
# rubocop:enable Metrics/MethodLength
|
|
155
|
+
|
|
156
|
+
private
|
|
87
157
|
|
|
158
|
+
# Extends the write lock, the read lock of the acquirer or all read locks of the lock.
|
|
159
|
+
#
|
|
160
|
+
# @param redis_client [RedisClient]
|
|
161
|
+
# @param lock_name [String]
|
|
162
|
+
# @param lock_key [String]
|
|
163
|
+
# @param milliseconds [Integer]
|
|
164
|
+
# @param read_write_mode [Symbol]
|
|
165
|
+
# @param all_read_locks [Boolean]
|
|
166
|
+
# @param acquirer_id [String]
|
|
167
|
+
# @param lock_index_enabled [Boolean]
|
|
168
|
+
# @return [Hash<Symbol,Boolean|Symbol|Hash<Symbol,Integer>>]
|
|
169
|
+
#
|
|
170
|
+
# @api private
|
|
171
|
+
# @since 1.21.0
|
|
172
|
+
# rubocop:disable Metrics/MethodLength
|
|
173
|
+
def fully_extend_lock_ttl(
|
|
174
|
+
redis_client,
|
|
175
|
+
lock_name,
|
|
176
|
+
lock_key,
|
|
177
|
+
milliseconds,
|
|
178
|
+
read_write_mode,
|
|
179
|
+
all_read_locks,
|
|
180
|
+
acquirer_id,
|
|
181
|
+
lock_index_enabled
|
|
182
|
+
)
|
|
183
|
+
if read_write_mode == :read
|
|
88
184
|
# NOTE: (RW) all live read locks of the lock
|
|
89
185
|
if all_read_locks
|
|
90
186
|
return extend_all_read_locks_ttl(
|
|
@@ -131,8 +227,6 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
131
227
|
end
|
|
132
228
|
# rubocop:enable Metrics/MethodLength
|
|
133
229
|
|
|
134
|
-
private
|
|
135
|
-
|
|
136
230
|
# Extends the live read lock of the acquirer:
|
|
137
231
|
# - the read lock expiration (readers registry score), the registry TTL and the read lock
|
|
138
232
|
# data TTL are extended;
|
|
@@ -149,7 +243,7 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
149
243
|
#
|
|
150
244
|
# @api private
|
|
151
245
|
# @since 1.18.0
|
|
152
|
-
# @version 1.
|
|
246
|
+
# @version 1.21.0
|
|
153
247
|
# rubocop:disable Metrics/MethodLength
|
|
154
248
|
def extend_read_lock_ttl(
|
|
155
249
|
redis_client,
|
|
@@ -191,6 +285,14 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
191
285
|
end
|
|
192
286
|
end
|
|
193
287
|
end
|
|
288
|
+
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
289
|
+
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
290
|
+
::Timeout::ExitException
|
|
291
|
+
# NOTE: (timeouts) an interrupted WATCH block (the internal timeouts and the user's
|
|
292
|
+
# `Timeout.timeout` without an exception class): the closed connection drops the WATCH
|
|
293
|
+
# state (redis-client sends `UNWATCH` only on `StandardError`);
|
|
294
|
+
rconn.close
|
|
295
|
+
raise
|
|
194
296
|
end
|
|
195
297
|
|
|
196
298
|
# NOTE:
|
|
@@ -223,7 +325,7 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
223
325
|
#
|
|
224
326
|
# @api private
|
|
225
327
|
# @since 1.18.0
|
|
226
|
-
# @version 1.
|
|
328
|
+
# @version 1.21.0
|
|
227
329
|
# rubocop:disable Metrics/MethodLength
|
|
228
330
|
def extend_all_read_locks_ttl(
|
|
229
331
|
redis_client,
|
|
@@ -278,6 +380,14 @@ module RedisQueuedLocks::Acquirer::ExtendLockTTL
|
|
|
278
380
|
end
|
|
279
381
|
end
|
|
280
382
|
end
|
|
383
|
+
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
384
|
+
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
385
|
+
::Timeout::ExitException
|
|
386
|
+
# NOTE: (timeouts) an interrupted WATCH block (the internal timeouts and the user's
|
|
387
|
+
# `Timeout.timeout` without an exception class): the closed connection drops the WATCH
|
|
388
|
+
# state (redis-client sends `UNWATCH` only on `StandardError`);
|
|
389
|
+
rconn.close
|
|
390
|
+
raise
|
|
281
391
|
end
|
|
282
392
|
|
|
283
393
|
# NOTE:
|
|
@@ -32,8 +32,17 @@ module RedisQueuedLocks::Acquirer::Keys
|
|
|
32
32
|
#
|
|
33
33
|
# @api private
|
|
34
34
|
# @since 1.0.0
|
|
35
|
-
# @version 1.
|
|
35
|
+
# @version 1.21.0
|
|
36
36
|
def keys(redis_client, scan_size:, lock_index_enabled:)
|
|
37
|
+
# NOTE: (arguments) the same range as the config key: zero/negative sizes fail SCAN/ZSCAN
|
|
38
|
+
# (`COUNT 0`) and the batching (`each_slice(0)`);
|
|
39
|
+
if !scan_size.is_a?(::Integer) || scan_size <= 0
|
|
40
|
+
raise(
|
|
41
|
+
RedisQueuedLocks::ArgumentError,
|
|
42
|
+
"`:scan_size` argument should be a positive Integer, got #{scan_size.inspect}."
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
37
46
|
Set.new.tap do |keyset|
|
|
38
47
|
# @type var keyset: Set[String]
|
|
39
48
|
if lock_index_enabled
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.18.0
|
|
6
6
|
module RedisQueuedLocks::Acquirer::LockInfo
|
|
7
7
|
class << self
|
|
8
8
|
# @param redis_client [RedisClient]
|
|
@@ -56,46 +56,38 @@ module RedisQueuedLocks::Acquirer::LockInfo
|
|
|
56
56
|
pipeline.call('ZRANGE', lock_readers_key, '0', '-1', 'WITHSCORES')
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# NOTE: the result of MULTI-command is an array of results of each internal command
|
|
68
|
-
# - result[0] (HGETALL) (Hash<String,String>)
|
|
69
|
-
# => (will be mutated further with different value types)
|
|
70
|
-
# - result[1] (PTTL) (Integer)
|
|
71
|
-
# => (without any mutation, integer is atomic)
|
|
72
|
-
# - result[2] (TIME) (Array<String>) (redis time)
|
|
73
|
-
# - result[3] (ZRANGE) (Array<[String,Float]>) (read locks)
|
|
59
|
+
# NOTE: (pipeline layout) the replies of the pipeline commands (`pipelined` always returns
|
|
60
|
+
# an array of the replies):
|
|
61
|
+
# - result[0] (HGETALL) (Hash<String,String>)
|
|
62
|
+
# => (will be mutated further with different value types)
|
|
63
|
+
# - result[1] (PTTL) (Integer)
|
|
64
|
+
# => (without any mutation, integer is atomic)
|
|
65
|
+
# - result[2] (TIME) (Array<String>) (redis time)
|
|
66
|
+
# - result[3] (ZRANGE) (Array<[String,Float]>) (read locks)
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
# rubocop:disable Layout/LineLength
|
|
69
|
+
# @type var result: [Hash[String,String|Float|Integer], Integer, Array[String], Array[[String, Float]]]
|
|
70
|
+
# rubocop:enable Layout/LineLength
|
|
71
|
+
hget_cmd_res = result[0]
|
|
72
|
+
pttl_cmd_res = result[1]
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
end
|
|
74
|
+
if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
|
|
75
|
+
# NOTE: (RW) there is no write lock => check read locks
|
|
76
|
+
read_lock_info(redis_client, lock_name, lock_key, result[2], result[3])
|
|
77
|
+
else
|
|
78
|
+
hget_cmd_res.tap do |lock_data|
|
|
79
|
+
lock_data['lock_key'] = lock_key
|
|
80
|
+
lock_data['rw_mode'] = 'write'
|
|
81
|
+
lock_data['ts'] = Float(lock_data['ts'])
|
|
82
|
+
lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
|
|
83
|
+
lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Float::INFINITY : pttl_cmd_res)
|
|
84
|
+
lock_data['spc_cnt'] = Integer(lock_data['spc_cnt']) if lock_data['spc_cnt']
|
|
85
|
+
lock_data['l_spc_ts'] = Float(lock_data['l_spc_ts']) if lock_data['l_spc_ts']
|
|
86
|
+
lock_data['spc_ext_ttl'] = Integer(lock_data['spc_ext_ttl']) if lock_data['spc_ext_ttl']
|
|
87
|
+
lock_data['l_spc_ext_ini_ttl'] =
|
|
88
|
+
Integer(lock_data['l_spc_ext_ini_ttl']) if lock_data.key?('l_spc_ext_ini_ttl')
|
|
89
|
+
lock_data['l_spc_ext_ts'] =
|
|
90
|
+
Float(lock_data['l_spc_ext_ts']) if lock_data['l_spc_ext_ts']
|
|
99
91
|
end
|
|
100
92
|
end
|
|
101
93
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# NOTE: Lock Series PoC
|
|
4
4
|
# @api private
|
|
5
5
|
# @since 1.16.1
|
|
6
|
+
# @version 1.18.0
|
|
6
7
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
7
8
|
class << self
|
|
8
9
|
# NOTE: Lock Series PoC
|
|
@@ -32,7 +33,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
|
32
33
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
33
34
|
# @api private
|
|
34
35
|
# @since 1.16.1
|
|
35
|
-
# @version 1.
|
|
36
|
+
# @version 1.21.0
|
|
36
37
|
def lock_series_hold_and_release( # steep:ignore
|
|
37
38
|
instrumenter,
|
|
38
39
|
instr_sampled,
|
|
@@ -49,7 +50,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
|
49
50
|
return unless instr_sampled
|
|
50
51
|
instrumenter.notify('redis_queued_locks.lock_series_hold_and_release', {
|
|
51
52
|
lock_keys:, rw_mode:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
52
|
-
})
|
|
53
|
+
}) rescue nil
|
|
53
54
|
end
|
|
54
55
|
end
|
|
55
56
|
end
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
# NOTE: Lock Series PoC
|
|
4
4
|
# @api private
|
|
5
5
|
# @since 1.16.1
|
|
6
|
+
# @version 1.18.0
|
|
6
7
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
7
8
|
class << self
|
|
8
9
|
# NOTE: Lock Series PoC
|
|
9
10
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
10
11
|
# @api private
|
|
11
12
|
# @since 1.16.0
|
|
12
|
-
# @version 1.
|
|
13
|
+
# @version 1.21.0
|
|
13
14
|
def start_lock_series_obtaining( # steep:ignore
|
|
14
15
|
logger,
|
|
15
16
|
log_sampled,
|
|
@@ -30,14 +31,14 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
30
31
|
"hst_id => '#{host_id}' " \
|
|
31
32
|
"acs_strat => '#{access_strategy}' " \
|
|
32
33
|
"rw_mode => '#{rw_mode}'"
|
|
33
|
-
end
|
|
34
|
+
end rescue nil
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# NOTE: Lock Series PoC
|
|
37
38
|
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
38
39
|
# @api private
|
|
39
40
|
# @since 1.16.0
|
|
40
|
-
# @version 1.
|
|
41
|
+
# @version 1.21.0
|
|
41
42
|
def lock_series_obtained( # steep:ignore
|
|
42
43
|
logger,
|
|
43
44
|
log_sampled,
|
|
@@ -60,7 +61,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
60
61
|
"acs_strat => '#{access_strategy}' " \
|
|
61
62
|
"rw_mode => '#{rw_mode}' " \
|
|
62
63
|
"acq_time => #{acq_time} (ms)"
|
|
63
|
-
end
|
|
64
|
+
end rescue nil
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# NOTE: Lock Series PoC
|
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
# rubocop:disable all
|
|
5
5
|
# @api private
|
|
6
6
|
# @since 1.16.0
|
|
7
|
+
# @version 1.20.0
|
|
7
8
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
8
9
|
require_relative 'lock_series_poc/log_visitor'
|
|
9
10
|
require_relative 'lock_series_poc/instr_visitor'
|
|
10
11
|
|
|
11
|
-
# @
|
|
12
|
+
# @since 1.16.0
|
|
12
13
|
extend RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
13
14
|
|
|
14
15
|
class << self
|
|
15
16
|
# @api private
|
|
16
17
|
# @since 1.16.0
|
|
17
|
-
# @version 1.
|
|
18
|
+
# @version 1.21.0
|
|
18
19
|
def lock_series_poc( # steep:ignore
|
|
19
20
|
redis,
|
|
20
21
|
lock_names,
|
|
@@ -66,19 +67,22 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
66
67
|
)
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
# NOTE: Symbol keys are written to the lock data as Strings (the same check as in
|
|
71
|
+
# RedisQueuedLocks::Acquirer::AcquireLock);
|
|
69
72
|
if meta.is_a?(::Hash) && (meta.any? do |key, _value|
|
|
70
|
-
key
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
meta_key = key.is_a?(::Symbol) ? key.name : key
|
|
74
|
+
meta_key == 'acq_id' ||
|
|
75
|
+
meta_key == 'hst_id' ||
|
|
76
|
+
meta_key == 'ts' ||
|
|
77
|
+
meta_key == 'ini_ttl' ||
|
|
78
|
+
meta_key == 'lock_key' ||
|
|
79
|
+
meta_key == 'rem_ttl' ||
|
|
80
|
+
meta_key == 'spc_ext_ttl' ||
|
|
81
|
+
meta_key == 'spc_cnt' ||
|
|
82
|
+
meta_key == 'l_spc_ext_ini_ttl' ||
|
|
83
|
+
meta_key == 'l_spc_ext_ts' ||
|
|
84
|
+
meta_key == 'l_spc_ts' ||
|
|
85
|
+
meta_key == 'rw_mode'
|
|
82
86
|
end)
|
|
83
87
|
raise(
|
|
84
88
|
RedisQueuedLocks::ArgumentError,
|
|
@@ -88,6 +92,34 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
88
92
|
)
|
|
89
93
|
end
|
|
90
94
|
|
|
95
|
+
# NOTE: the series ttl is validated before the lock ttl calculation: each lock ttl of
|
|
96
|
+
# the series includes the retry delay and jitter (a non-positive series ttl can produce
|
|
97
|
+
# a positive lock ttl), the series ttl limits the `timed` block (the same check as in
|
|
98
|
+
# RedisQueuedLocks::Acquirer::AcquireLock);
|
|
99
|
+
if !ttl.is_a?(::Integer) || ttl <= 0
|
|
100
|
+
raise(
|
|
101
|
+
RedisQueuedLocks::ArgumentError,
|
|
102
|
+
"`:ttl` argument should be a positive Integer, got #{ttl.inspect}."
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# NOTE: the retry delay and jitter are parts of the lock ttls of the series (validated before
|
|
107
|
+
# the lock ttl calculation; other options are validated by
|
|
108
|
+
# RedisQueuedLocks::Acquirer::AcquireLock on the first lock of the series, before any lock
|
|
109
|
+
# is obtained);
|
|
110
|
+
if !retry_delay.is_a?(::Integer) || retry_delay < 0
|
|
111
|
+
raise(
|
|
112
|
+
RedisQueuedLocks::ArgumentError,
|
|
113
|
+
"`:retry_delay` argument should be a non-negative Integer, got #{retry_delay.inspect}."
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
if !retry_jitter.is_a?(::Integer) || retry_jitter < 0
|
|
117
|
+
raise(
|
|
118
|
+
RedisQueuedLocks::ArgumentError,
|
|
119
|
+
"`:retry_jitter` argument should be a non-negative Integer, got #{retry_jitter.inspect}."
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
91
123
|
locks_count = lock_names.size
|
|
92
124
|
|
|
93
125
|
lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
|
|
@@ -374,7 +406,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
374
406
|
#
|
|
375
407
|
# @api private
|
|
376
408
|
# @since 1.18.0
|
|
377
|
-
# @version 1.
|
|
409
|
+
# @version 1.21.0
|
|
378
410
|
def release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
379
411
|
return if lock_names.empty?
|
|
380
412
|
|
|
@@ -397,6 +429,14 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
397
429
|
# NOTE: `nil` => the transaction is aborted (a watched lock is changed) => retry
|
|
398
430
|
break unless released.nil?
|
|
399
431
|
end
|
|
432
|
+
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
433
|
+
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
434
|
+
::Timeout::ExitException
|
|
435
|
+
# NOTE: (timeouts) an interrupted WATCH block (the internal timeouts and the user's
|
|
436
|
+
# `Timeout.timeout` without an exception class): the closed connection drops the WATCH
|
|
437
|
+
# state (redis-client sends `UNWATCH` only on `StandardError`);
|
|
438
|
+
conn.close
|
|
439
|
+
raise
|
|
400
440
|
end
|
|
401
441
|
end
|
|
402
442
|
|
|
@@ -25,8 +25,17 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
25
25
|
#
|
|
26
26
|
# @api private
|
|
27
27
|
# @since 1.0.0
|
|
28
|
-
# @version 1.
|
|
28
|
+
# @version 1.21.0
|
|
29
29
|
def locks(redis_client, scan_size:, with_info:, lock_index_enabled:)
|
|
30
|
+
# NOTE: (arguments) the same range as the config key: zero/negative sizes fail SCAN/ZSCAN
|
|
31
|
+
# (`COUNT 0`) and the batching (`each_slice(0)`);
|
|
32
|
+
if !scan_size.is_a?(::Integer) || scan_size <= 0
|
|
33
|
+
raise(
|
|
34
|
+
RedisQueuedLocks::ArgumentError,
|
|
35
|
+
"`:scan_size` argument should be a positive Integer, got #{scan_size.inspect}."
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
30
39
|
redis_client.with do |rconn|
|
|
31
40
|
lock_keys =
|
|
32
41
|
lock_index_enabled ? index_locks(rconn, scan_size) : scan_locks(rconn, scan_size)
|
|
@@ -153,7 +162,7 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
153
162
|
#
|
|
154
163
|
# @api private
|
|
155
164
|
# @since 1.0.0
|
|
156
|
-
# @version 1.
|
|
165
|
+
# @version 1.21.0
|
|
157
166
|
# rubocop:disable Metrics/MethodLength
|
|
158
167
|
def extract_locks_info(redis_client, lock_keys, scan_size)
|
|
159
168
|
Set.new.tap do |seeded_locks|
|
|
@@ -162,6 +171,13 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
162
171
|
# rubocop:enable Layout/LineLength
|
|
163
172
|
|
|
164
173
|
lock_keys.each_slice(scan_size) do |lock_keys_batch|
|
|
174
|
+
# Step 0: lock names of the batch (once per lock: the readers registry keys and the read
|
|
175
|
+
# lock data keys are built from them)
|
|
176
|
+
# @type var lock_names_batch: Array[String]
|
|
177
|
+
lock_names_batch = lock_keys_batch.map do |lock_key|
|
|
178
|
+
RedisQueuedLocks::Resource.lock_name_from_key(lock_key, :lock) #: String
|
|
179
|
+
end
|
|
180
|
+
|
|
165
181
|
# Step 1: extract the state of the batch of locks in one round trip
|
|
166
182
|
# NOTE: (pipeline layout) [0] TIME (read locks are checked in redis time), then
|
|
167
183
|
# `LOCK_STATE_REPLIES` replies per lock in the batch order (the replies of the lock
|
|
@@ -170,10 +186,9 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
170
186
|
# @type var batch_state: Array[untyped]
|
|
171
187
|
batch_state = redis_client.pipelined do |pipeline|
|
|
172
188
|
pipeline.call('TIME')
|
|
173
|
-
lock_keys_batch.
|
|
174
|
-
lock_readers_key =
|
|
175
|
-
|
|
176
|
-
)
|
|
189
|
+
lock_keys_batch.each_with_index do |lock_key, index|
|
|
190
|
+
lock_readers_key =
|
|
191
|
+
RedisQueuedLocks::Resource.prepare_lock_readers(lock_names_batch[index])
|
|
177
192
|
pipeline.call('HGETALL', lock_key)
|
|
178
193
|
pipeline.call('PTTL', lock_key)
|
|
179
194
|
pipeline.call('ZRANGE', lock_readers_key, '0', '-1', 'WITHSCORES')
|
|
@@ -199,12 +214,13 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
199
214
|
# read lock data can be absent if it is dropped during the read lock life;
|
|
200
215
|
# @type var read_locks_data: Array[Hash[String,String]]
|
|
201
216
|
read_locks_data = redis_client.pipelined do |pipeline|
|
|
202
|
-
lock_keys_batch.
|
|
203
|
-
lock_name = lock_key.delete_prefix('rql:lock:')
|
|
217
|
+
lock_keys_batch.each_index do |index|
|
|
204
218
|
live_read_locks[index]&.each do |(acquirer_id, _expiration)|
|
|
205
219
|
pipeline.call(
|
|
206
220
|
'HGETALL',
|
|
207
|
-
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
221
|
+
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
222
|
+
lock_names_batch[index], acquirer_id
|
|
223
|
+
)
|
|
208
224
|
)
|
|
209
225
|
end
|
|
210
226
|
end
|
|
@@ -232,6 +248,7 @@ module RedisQueuedLocks::Acquirer::Locks
|
|
|
232
248
|
pttl_cmd_res = batch_state[replies_at + 1] # NOTE: PTTL result (integer)
|
|
233
249
|
|
|
234
250
|
hget_cmd_res.tap do |lock_data|
|
|
251
|
+
lock_data['lock_key'] = lock_key
|
|
235
252
|
lock_data['rw_mode'] = 'write'
|
|
236
253
|
lock_data['ts'] = Float(lock_data['ts'])
|
|
237
254
|
lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
|
|
@@ -15,8 +15,17 @@ module RedisQueuedLocks::Acquirer::Queues
|
|
|
15
15
|
#
|
|
16
16
|
# @api private
|
|
17
17
|
# @since 1.0.0
|
|
18
|
-
# @version 1.
|
|
18
|
+
# @version 1.21.0
|
|
19
19
|
def queues(redis_client, scan_size:, with_info:, lock_index_enabled:)
|
|
20
|
+
# NOTE: (arguments) the same range as the config key: zero/negative sizes fail SCAN/ZSCAN
|
|
21
|
+
# (`COUNT 0`) and the batching (`each_slice(0)`);
|
|
22
|
+
if !scan_size.is_a?(::Integer) || scan_size <= 0
|
|
23
|
+
raise(
|
|
24
|
+
RedisQueuedLocks::ArgumentError,
|
|
25
|
+
"`:scan_size` argument should be a positive Integer, got #{scan_size.inspect}."
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
20
29
|
redis_client.with do |rconn|
|
|
21
30
|
lock_queues =
|
|
22
31
|
lock_index_enabled ? index_queues(rconn, scan_size) : scan_queues(rconn, scan_size)
|