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