redis_queued_locks 1.21.0 → 1.23.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 +294 -51
- data/.claude/rules/acquirer.md +16 -11
- 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 +14 -15
- data/.claude/rules/swarm.md +3 -4
- data/.claude/rules/tests.md +9 -9
- data/.claude/rules/type-checking.md +14 -16
- data/.claude/rules/visitors.md +11 -9
- data/.claude/skills/rql-code-review/SKILL.md +129 -47
- data/.ruby-version +1 -1
- data/CHANGELOG.md +122 -245
- data/CLAUDE.md +12 -10
- data/README.md +516 -175
- data/Rakefile +11 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
- 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 +945 -272
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +82 -32
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +15 -13
- data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
- data/lib/redis_queued_locks/acquirer/lock_series/instr_visitor.rb +243 -0
- data/lib/redis_queued_locks/acquirer/lock_series/log_visitor.rb +472 -0
- data/lib/redis_queued_locks/acquirer/lock_series/release_lock_series.rb +181 -0
- data/lib/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rb +1051 -0
- data/lib/redis_queued_locks/acquirer/lock_series.rb +948 -0
- 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/acquirer.rb +1 -1
- data/lib/redis_queued_locks/client.rb +116 -44
- 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/redis_queued_locks.gemspec +21 -14
- 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 +160 -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/lock_series/instr_visitor.rbs +102 -0
- data/sig/redis_queued_locks/acquirer/lock_series/log_visitor.rbs +154 -0
- data/sig/redis_queued_locks/acquirer/lock_series/release_lock_series.rbs +37 -0
- data/sig/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rbs +198 -0
- data/sig/redis_queued_locks/acquirer/lock_series.rbs +106 -0
- data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +77 -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 +35 -13
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -56
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -95
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -484
- /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
|
@@ -1,484 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# NOTE: Lock Series PoC
|
|
4
|
-
# rubocop:disable all
|
|
5
|
-
# @api private
|
|
6
|
-
# @since 1.16.0
|
|
7
|
-
# @version 1.20.0
|
|
8
|
-
module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
9
|
-
require_relative 'lock_series_poc/log_visitor'
|
|
10
|
-
require_relative 'lock_series_poc/instr_visitor'
|
|
11
|
-
|
|
12
|
-
# @since 1.16.0
|
|
13
|
-
extend RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
|
|
14
|
-
|
|
15
|
-
class << self
|
|
16
|
-
# @api private
|
|
17
|
-
# @since 1.16.0
|
|
18
|
-
# @version 1.21.0
|
|
19
|
-
def lock_series_poc( # steep:ignore
|
|
20
|
-
redis,
|
|
21
|
-
lock_names,
|
|
22
|
-
detailed_result:,
|
|
23
|
-
process_id:,
|
|
24
|
-
thread_id:,
|
|
25
|
-
fiber_id:,
|
|
26
|
-
ractor_id:,
|
|
27
|
-
ttl:,
|
|
28
|
-
queue_ttl:,
|
|
29
|
-
lock_index_enabled:,
|
|
30
|
-
lock_index_grace_period:,
|
|
31
|
-
timeout:,
|
|
32
|
-
timed:,
|
|
33
|
-
retry_count:,
|
|
34
|
-
retry_delay:,
|
|
35
|
-
retry_jitter:,
|
|
36
|
-
raise_errors:,
|
|
37
|
-
instrumenter:,
|
|
38
|
-
identity:,
|
|
39
|
-
fail_fast:,
|
|
40
|
-
meta:,
|
|
41
|
-
detailed_acq_timeout_error:,
|
|
42
|
-
instrument:,
|
|
43
|
-
logger:,
|
|
44
|
-
log_lock_try:,
|
|
45
|
-
conflict_strategy:,
|
|
46
|
-
read_write_mode:,
|
|
47
|
-
access_strategy:,
|
|
48
|
-
log_sampling_enabled:,
|
|
49
|
-
log_sampling_percent:,
|
|
50
|
-
log_sampler:,
|
|
51
|
-
log_sample_this:,
|
|
52
|
-
instr_sampling_enabled:,
|
|
53
|
-
instr_sampling_percent:,
|
|
54
|
-
instr_sampler:,
|
|
55
|
-
instr_sample_this:,
|
|
56
|
-
&block
|
|
57
|
-
)
|
|
58
|
-
# NOTE: (RW) the lock mode is the same for all locks of the series
|
|
59
|
-
# (see RedisQueuedLocks::Acquirer::AcquireLock for read_write_mode validations);
|
|
60
|
-
|
|
61
|
-
case meta
|
|
62
|
-
when Hash, NilClass then nil
|
|
63
|
-
else
|
|
64
|
-
raise(
|
|
65
|
-
RedisQueuedLocks::ArgumentError,
|
|
66
|
-
"`:meta` argument should be a type of NilClass or Hash, got #{meta.class}."
|
|
67
|
-
)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# NOTE: Symbol keys are written to the lock data as Strings (the same check as in
|
|
71
|
-
# RedisQueuedLocks::Acquirer::AcquireLock);
|
|
72
|
-
if meta.is_a?(::Hash) && (meta.any? do |key, _value|
|
|
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'
|
|
86
|
-
end)
|
|
87
|
-
raise(
|
|
88
|
-
RedisQueuedLocks::ArgumentError,
|
|
89
|
-
'`:meta` keys can not overlap reserved lock data keys ' \
|
|
90
|
-
'"acq_id", "hst_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
|
|
91
|
-
'"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts", "rw_mode"'
|
|
92
|
-
)
|
|
93
|
-
end
|
|
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
|
-
|
|
123
|
-
locks_count = lock_names.size
|
|
124
|
-
|
|
125
|
-
lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
|
|
126
|
-
lock_ttl =
|
|
127
|
-
ttl * (locks_count - position) +
|
|
128
|
-
retry_delay * (locks_count - position) +
|
|
129
|
-
retry_jitter * (locks_count - position)
|
|
130
|
-
|
|
131
|
-
{ lock_name: lock_name, ttl: lock_ttl }
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
log_sampled = RedisQueuedLocks::Logging.should_log?(
|
|
135
|
-
log_sampling_enabled,
|
|
136
|
-
log_sample_this,
|
|
137
|
-
log_sampling_percent,
|
|
138
|
-
log_sampler
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
|
|
142
|
-
instr_sampling_enabled,
|
|
143
|
-
instr_sample_this,
|
|
144
|
-
instr_sampling_percent,
|
|
145
|
-
instr_sampler
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
lock_keys_for_instrumentation = lock_names.map do |lock_name|
|
|
149
|
-
RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
acquirer_id_for_instrumentation = RedisQueuedLocks::Resource.acquirer_identifier(
|
|
153
|
-
process_id,
|
|
154
|
-
thread_id,
|
|
155
|
-
fiber_id,
|
|
156
|
-
ractor_id,
|
|
157
|
-
identity
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
host_id_for_instrumentation = RedisQueuedLocks::Resource.host_identifier(
|
|
161
|
-
process_id,
|
|
162
|
-
thread_id,
|
|
163
|
-
ractor_id,
|
|
164
|
-
identity
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.start_lock_series_obtaining( # steep:ignore
|
|
168
|
-
logger, log_sampled, lock_keys_for_instrumentation,
|
|
169
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
|
|
170
|
-
read_write_mode
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
174
|
-
successfully_acquired_locks = [] # steep:ignore
|
|
175
|
-
failed_locks_and_errors = {} # steep:ignore
|
|
176
|
-
failed_on_lock = nil
|
|
177
|
-
# NOTE: locks of the failed series are released once (exceptional or not)
|
|
178
|
-
is_failed_series_released = false
|
|
179
|
-
|
|
180
|
-
lock_acquirement_operations.map do |lock_operation_options|
|
|
181
|
-
result =
|
|
182
|
-
begin
|
|
183
|
-
RedisQueuedLocks::Acquirer::AcquireLock.acquire_lock(
|
|
184
|
-
redis,
|
|
185
|
-
lock_operation_options[:lock_name],
|
|
186
|
-
process_id:,
|
|
187
|
-
thread_id:,
|
|
188
|
-
fiber_id:,
|
|
189
|
-
ractor_id:,
|
|
190
|
-
ttl: lock_operation_options[:ttl],
|
|
191
|
-
queue_ttl:,
|
|
192
|
-
lock_index_enabled:,
|
|
193
|
-
lock_index_grace_period:,
|
|
194
|
-
timeout:,
|
|
195
|
-
timed:,
|
|
196
|
-
retry_count:,
|
|
197
|
-
retry_delay:,
|
|
198
|
-
retry_jitter:,
|
|
199
|
-
raise_errors:,
|
|
200
|
-
instrumenter:,
|
|
201
|
-
identity:,
|
|
202
|
-
fail_fast:,
|
|
203
|
-
meta:,
|
|
204
|
-
detailed_acq_timeout_error:,
|
|
205
|
-
instrument:,
|
|
206
|
-
logger:,
|
|
207
|
-
log_lock_try:,
|
|
208
|
-
conflict_strategy:,
|
|
209
|
-
read_write_mode:,
|
|
210
|
-
access_strategy:,
|
|
211
|
-
log_sampling_enabled:,
|
|
212
|
-
log_sampling_percent:,
|
|
213
|
-
log_sampler:,
|
|
214
|
-
log_sample_this:,
|
|
215
|
-
instr_sampling_enabled:,
|
|
216
|
-
instr_sampling_percent:,
|
|
217
|
-
instr_sampler:,
|
|
218
|
-
instr_sample_this:
|
|
219
|
-
)
|
|
220
|
-
rescue RedisQueuedLocks::LockAlreadyObtainedError,
|
|
221
|
-
RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
222
|
-
RedisQueuedLocks::LockAcquirementTimeoutError,
|
|
223
|
-
RedisQueuedLocks::LockAcquirementRetryLimitError,
|
|
224
|
-
RedisQueuedLocks::ConflictLockObtainError => error
|
|
225
|
-
# NOTE: release all previously acquired locks if any next lock is already locked
|
|
226
|
-
release_lock_series( # steep:ignore
|
|
227
|
-
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
228
|
-
)
|
|
229
|
-
is_failed_series_released = true
|
|
230
|
-
|
|
231
|
-
raise(error) if raise_errors
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
if result[:ok]
|
|
235
|
-
successfully_acquired_locks << {
|
|
236
|
-
lock_name: lock_operation_options[:lock_name],
|
|
237
|
-
ok: result[:ok],
|
|
238
|
-
result: result[:result]
|
|
239
|
-
}
|
|
240
|
-
else
|
|
241
|
-
failed_on_lock = lock_operation_options[:lock_name]
|
|
242
|
-
failed_locks_and_errors[lock_operation_options[:lock_name]] = result
|
|
243
|
-
# NOTE: release all previously acquired locks (non-exceptional failure: raise_errors: false)
|
|
244
|
-
unless is_failed_series_released
|
|
245
|
-
release_lock_series( # steep:ignore
|
|
246
|
-
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
247
|
-
)
|
|
248
|
-
is_failed_series_released = true
|
|
249
|
-
end
|
|
250
|
-
break
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
if (successfully_acquired_locks.size == lock_names.size && (successfully_acquired_locks.all? { |res| res[:ok] }))
|
|
255
|
-
acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
256
|
-
acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
|
|
257
|
-
ts = Time.now.to_f
|
|
258
|
-
|
|
259
|
-
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.lock_series_obtained( # steep:ignore
|
|
260
|
-
logger, log_sampled, lock_keys_for_instrumentation,
|
|
261
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
262
|
-
acq_time, access_strategy, read_write_mode
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_obtained( # steep:ignore
|
|
266
|
-
instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode,
|
|
267
|
-
ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time, instrument
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
yield_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
271
|
-
ttl_shift = (
|
|
272
|
-
(yield_time - acq_end_time) / 1_000.0 -
|
|
273
|
-
RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
|
|
274
|
-
).ceil(2)
|
|
275
|
-
|
|
276
|
-
yield_result = nil
|
|
277
|
-
is_lock_manually_released = nil
|
|
278
|
-
hold_time = nil
|
|
279
|
-
|
|
280
|
-
begin
|
|
281
|
-
yield_result = yield_expire( # steep:ignore
|
|
282
|
-
redis,
|
|
283
|
-
logger,
|
|
284
|
-
lock_keys_for_instrumentation.last,
|
|
285
|
-
RedisQueuedLocks::Resource.prepare_lock_readers(lock_names.last),
|
|
286
|
-
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
287
|
-
lock_names.last, acquirer_id_for_instrumentation
|
|
288
|
-
),
|
|
289
|
-
read_write_mode,
|
|
290
|
-
acquirer_id_for_instrumentation,
|
|
291
|
-
host_id_for_instrumentation,
|
|
292
|
-
access_strategy,
|
|
293
|
-
timed,
|
|
294
|
-
ttl_shift,
|
|
295
|
-
ttl,
|
|
296
|
-
queue_ttl,
|
|
297
|
-
meta,
|
|
298
|
-
log_sampled,
|
|
299
|
-
instr_sampled,
|
|
300
|
-
false, # should_expire (expire manually)
|
|
301
|
-
false, # should_decrease (expire manually)
|
|
302
|
-
&block
|
|
303
|
-
)
|
|
304
|
-
ensure
|
|
305
|
-
is_lock_manually_released = false
|
|
306
|
-
|
|
307
|
-
# expire locks manually
|
|
308
|
-
if block_given?
|
|
309
|
-
release_lock_series( # steep:ignore
|
|
310
|
-
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
311
|
-
)
|
|
312
|
-
is_lock_manually_released = true
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
rel_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
316
|
-
hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
|
|
317
|
-
ts = Time.now.to_f
|
|
318
|
-
|
|
319
|
-
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.expire_lock_series( # steep:ignore
|
|
320
|
-
logger, log_sampled, lock_keys_for_instrumentation,
|
|
321
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
|
|
322
|
-
read_write_mode
|
|
323
|
-
) if is_lock_manually_released
|
|
324
|
-
|
|
325
|
-
RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_hold_and_release( # steep:ignore
|
|
326
|
-
instrumenter,
|
|
327
|
-
instr_sampled,
|
|
328
|
-
lock_keys_for_instrumentation,
|
|
329
|
-
read_write_mode,
|
|
330
|
-
ttl,
|
|
331
|
-
acquirer_id_for_instrumentation,
|
|
332
|
-
host_id_for_instrumentation,
|
|
333
|
-
ts,
|
|
334
|
-
acq_time,
|
|
335
|
-
hold_time,
|
|
336
|
-
instrument
|
|
337
|
-
) if is_lock_manually_released
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
if detailed_result
|
|
341
|
-
{
|
|
342
|
-
yield_result: yield_result,
|
|
343
|
-
locks_release_strategy: block_given? ? :immediate_release_after_yield : :redis_key_ttl,
|
|
344
|
-
locks_released_at: block_given? ? ts : nil,
|
|
345
|
-
locks_acq_time: acq_time,
|
|
346
|
-
locks_hold_time: is_lock_manually_released ? hold_time : nil,
|
|
347
|
-
lock_series: lock_names,
|
|
348
|
-
rql_lock_series: lock_keys_for_instrumentation
|
|
349
|
-
}
|
|
350
|
-
else
|
|
351
|
-
yield_result
|
|
352
|
-
end
|
|
353
|
-
else
|
|
354
|
-
acquired_locks = successfully_acquired_locks.map { |state| state[:lock_name] }
|
|
355
|
-
missing_locks = lock_names - acquired_locks
|
|
356
|
-
|
|
357
|
-
{
|
|
358
|
-
ok: false,
|
|
359
|
-
result: {
|
|
360
|
-
error: :failed_to_acquire_lock_series,
|
|
361
|
-
detailed_errors: failed_locks_and_errors,
|
|
362
|
-
lock_series: lock_names,
|
|
363
|
-
acquired_locks:,
|
|
364
|
-
missing_locks:,
|
|
365
|
-
failed_on_lock:,
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
private
|
|
372
|
-
|
|
373
|
-
# Releases locks of the series that are obtained by the series itself:
|
|
374
|
-
# - reentrant locks (already obtained by the outer logic of the current acquirer) are kept;
|
|
375
|
-
# - only locks of the current acquirer are released (owner check);
|
|
376
|
-
#
|
|
377
|
-
# @param redis [RedisClient]
|
|
378
|
-
# @param read_write_mode [Symbol]
|
|
379
|
-
# @param successfully_acquired_locks [Array<Hash<Symbol,Any>>]
|
|
380
|
-
# @param acquirer_id [String]
|
|
381
|
-
# @return [void]
|
|
382
|
-
#
|
|
383
|
-
# @api private
|
|
384
|
-
# @since 1.18.0
|
|
385
|
-
def release_lock_series(redis, read_write_mode, successfully_acquired_locks, acquirer_id) # steep:ignore
|
|
386
|
-
lock_names = obtained_lock_names(successfully_acquired_locks) # steep:ignore
|
|
387
|
-
|
|
388
|
-
if read_write_mode == :read
|
|
389
|
-
release_read_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
390
|
-
else
|
|
391
|
-
release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
# Releases write locks of the given acquirer (compare-and-delete: the lock can be expired
|
|
396
|
-
# and obtained by another acquirer when the series logic exceeds the lock ttl) in one
|
|
397
|
-
# transaction for the whole series (three round trips: WATCH, lock owners, MULTI/EXEC):
|
|
398
|
-
# - an aborted transaction (one of the watched locks is changed) is retried with the locks
|
|
399
|
-
# that were still owned by the acquirer at the last check: the locks of other acquirers
|
|
400
|
-
# are neither deleted nor watched, so the retries converge;
|
|
401
|
-
#
|
|
402
|
-
# @param redis [RedisClient]
|
|
403
|
-
# @param lock_names [Array<String>]
|
|
404
|
-
# @param acquirer_id [String]
|
|
405
|
-
# @return [void]
|
|
406
|
-
#
|
|
407
|
-
# @api private
|
|
408
|
-
# @since 1.18.0
|
|
409
|
-
# @version 1.21.0
|
|
410
|
-
def release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
411
|
-
return if lock_names.empty?
|
|
412
|
-
|
|
413
|
-
own_lock_keys = lock_names.map do |lock_name|
|
|
414
|
-
RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
redis.with do |conn|
|
|
418
|
-
until own_lock_keys.empty?
|
|
419
|
-
released = conn.multi(watch: own_lock_keys) do |transaction|
|
|
420
|
-
# NOTE: (pipeline layout) one reply (lock owner) per lock in the series order
|
|
421
|
-
lock_owners = conn.pipelined do |pipeline|
|
|
422
|
-
own_lock_keys.each { |lock_key| pipeline.call('HGET', lock_key, 'acq_id') }
|
|
423
|
-
end
|
|
424
|
-
own_lock_keys = own_lock_keys.select.with_index do |_lock_key, index|
|
|
425
|
-
lock_owners[index] == acquirer_id
|
|
426
|
-
end
|
|
427
|
-
own_lock_keys.each { |lock_key| transaction.call('DEL', lock_key) }
|
|
428
|
-
end
|
|
429
|
-
# NOTE: `nil` => the transaction is aborted (a watched lock is changed) => retry
|
|
430
|
-
break unless released.nil?
|
|
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
|
|
440
|
-
end
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
# Locks of the series that are obtained by the series itself (reentrant locks are obtained
|
|
444
|
-
# by the outer logic of the current acquirer and should be released by it).
|
|
445
|
-
#
|
|
446
|
-
# @param successfully_acquired_locks [Array<Hash<Symbol,Any>>]
|
|
447
|
-
# @return [Array<String>]
|
|
448
|
-
#
|
|
449
|
-
# @api private
|
|
450
|
-
# @since 1.18.0
|
|
451
|
-
def obtained_lock_names(successfully_acquired_locks) # steep:ignore
|
|
452
|
-
successfully_acquired_locks.filter_map do |operation_result|
|
|
453
|
-
operation_result[:lock_name] if operation_result[:result][:process] == :lock_obtaining
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
# Releases read locks of the given acquirer (other readers are not affected).
|
|
458
|
-
#
|
|
459
|
-
# @param redis [RedisClient]
|
|
460
|
-
# @param lock_names [Array<String>]
|
|
461
|
-
# @param acquirer_id [String]
|
|
462
|
-
# @return [void]
|
|
463
|
-
#
|
|
464
|
-
# @api private
|
|
465
|
-
# @since 1.18.0
|
|
466
|
-
def release_read_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
467
|
-
return if lock_names.empty?
|
|
468
|
-
|
|
469
|
-
redis.with do |conn|
|
|
470
|
-
conn.multi do |transaction|
|
|
471
|
-
lock_names.each do |lock_name|
|
|
472
|
-
transaction.call(
|
|
473
|
-
'ZREM', RedisQueuedLocks::Resource.prepare_lock_readers(lock_name), acquirer_id
|
|
474
|
-
)
|
|
475
|
-
transaction.call(
|
|
476
|
-
'DEL', RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
477
|
-
)
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
end
|
|
481
|
-
end
|
|
482
|
-
end
|
|
483
|
-
end
|
|
484
|
-
# rubocop:enable all
|
|
File without changes
|