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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.13.0
|
|
5
|
+
# @version 1.22.0
|
|
5
6
|
# rubocop:disable Metrics/ClassLength
|
|
6
7
|
class RedisQueuedLocks::Config
|
|
7
8
|
require_relative 'config/dsl'
|
|
@@ -31,6 +32,8 @@ class RedisQueuedLocks::Config
|
|
|
31
32
|
setting('is_timed_by_default', false)
|
|
32
33
|
setting('default_conflict_strategy', :wait_for_lock)
|
|
33
34
|
setting('default_access_strategy', :queued)
|
|
35
|
+
setting('lock_priorities', { lowest: -100, low: -50, normal: 0, high: 50, highest: 100 }.freeze)
|
|
36
|
+
setting('default_lock_priority', 0)
|
|
34
37
|
setting('log_sampling_enabled', false)
|
|
35
38
|
setting('log_sampling_percent', 15)
|
|
36
39
|
setting('log_sampler', RedisQueuedLocks::Logging::Sampler)
|
|
@@ -114,6 +117,27 @@ class RedisQueuedLocks::Config
|
|
|
114
117
|
val == :random
|
|
115
118
|
# rubocop:enable Layout/MultilineOperationIndentation
|
|
116
119
|
end
|
|
120
|
+
validate('lock_priorities') do |val|
|
|
121
|
+
val.is_a?(Hash) && val.all? do |name, priority|
|
|
122
|
+
# rubocop:disable Layout/MultilineOperationIndentation
|
|
123
|
+
name.is_a?(Symbol) &&
|
|
124
|
+
priority.is_a?(Integer) &&
|
|
125
|
+
priority >= RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY &&
|
|
126
|
+
priority <= RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY
|
|
127
|
+
# rubocop:enable Layout/MultilineOperationIndentation
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
# NOTE: a Symbol is a priority name of `config['lock_priorities']`: a validator gets one value,
|
|
131
|
+
# so the name is checked by the configuration itself (`prevent_default_priority__unknown_name`:
|
|
132
|
+
# once after a configuration block, the keys can be assigned in any order; before a direct
|
|
133
|
+
# assignment is applied);
|
|
134
|
+
validate('default_lock_priority') do |val|
|
|
135
|
+
val.is_a?(Symbol) || (
|
|
136
|
+
val.is_a?(Integer) &&
|
|
137
|
+
val >= RedisQueuedLocks::Resource::MIN_LOCK_PRIORITY &&
|
|
138
|
+
val <= RedisQueuedLocks::Resource::MAX_LOCK_PRIORITY
|
|
139
|
+
)
|
|
140
|
+
end
|
|
117
141
|
|
|
118
142
|
# @return [Hash<Symbol,Any>]
|
|
119
143
|
#
|
|
@@ -123,10 +147,14 @@ class RedisQueuedLocks::Config
|
|
|
123
147
|
|
|
124
148
|
# @return [void]
|
|
125
149
|
#
|
|
150
|
+
# @raise [RedisQueuedLocks::ConfigValidationError]
|
|
151
|
+
#
|
|
126
152
|
# @api private
|
|
127
153
|
# @since 1.13.0
|
|
154
|
+
# @version 1.22.0
|
|
128
155
|
def initialize(&configuration)
|
|
129
156
|
@config_state = {}
|
|
157
|
+
@configuring = false
|
|
130
158
|
config_setters.each_value { |setter| setter.call(@config_state) }
|
|
131
159
|
configure(&configuration)
|
|
132
160
|
end
|
|
@@ -136,10 +164,40 @@ class RedisQueuedLocks::Config
|
|
|
136
164
|
# @yieldparam [?]
|
|
137
165
|
# @return [void]
|
|
138
166
|
#
|
|
167
|
+
# @raise [RedisQueuedLocks::ConfigValidationError]
|
|
168
|
+
#
|
|
139
169
|
# @api private
|
|
140
170
|
# @since 1.13.0
|
|
171
|
+
# @version 1.22.0
|
|
141
172
|
def configure(&configuration)
|
|
142
|
-
|
|
173
|
+
return unless block_given?
|
|
174
|
+
|
|
175
|
+
# NOTE: (priorities) the keys of the block can be assigned in any order: the priority name of
|
|
176
|
+
# `default_lock_priority` is checked against `lock_priorities` once, after the block (after
|
|
177
|
+
# the outer block for nested blocks); an inconsistent pair is never applied: both keys keep
|
|
178
|
+
# their values from before the block (also when the block fails: its error is raised);
|
|
179
|
+
lock_priorities = config_state['lock_priorities']
|
|
180
|
+
default_lock_priority = config_state['default_lock_priority']
|
|
181
|
+
nested = @configuring
|
|
182
|
+
completed = false
|
|
183
|
+
@configuring = true
|
|
184
|
+
begin
|
|
185
|
+
yield(self)
|
|
186
|
+
completed = true
|
|
187
|
+
ensure
|
|
188
|
+
@configuring = nested
|
|
189
|
+
unless nested
|
|
190
|
+
begin
|
|
191
|
+
prevent_default_priority__unknown_name(
|
|
192
|
+
config_state['lock_priorities'], config_state['default_lock_priority']
|
|
193
|
+
)
|
|
194
|
+
rescue RedisQueuedLocks::ConfigValidationError
|
|
195
|
+
config_state['lock_priorities'] = lock_priorities
|
|
196
|
+
config_state['default_lock_priority'] = default_lock_priority
|
|
197
|
+
raise if completed
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
143
201
|
end
|
|
144
202
|
|
|
145
203
|
# @param config_key [String]
|
|
@@ -163,9 +221,21 @@ class RedisQueuedLocks::Config
|
|
|
163
221
|
#
|
|
164
222
|
# @api public
|
|
165
223
|
# @since 1.13.0
|
|
224
|
+
# @version 1.22.0
|
|
166
225
|
def []=(config_key, config_value)
|
|
167
226
|
prevent_key__non_existent(config_key)
|
|
168
227
|
prevent_key__invalid_type(config_key, config_value)
|
|
228
|
+
# NOTE: (priorities) a direct assignment (out of a configuration block) keeps the priority name
|
|
229
|
+
# of `default_lock_priority` a key of `lock_priorities` (a configuration block checks it once
|
|
230
|
+
# after the block, see `configure`);
|
|
231
|
+
unless @configuring
|
|
232
|
+
case config_key
|
|
233
|
+
when 'lock_priorities'
|
|
234
|
+
prevent_default_priority__unknown_name(config_value, config_state['default_lock_priority'])
|
|
235
|
+
when 'default_lock_priority'
|
|
236
|
+
prevent_default_priority__unknown_name(config_state['lock_priorities'], config_value)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
169
239
|
config_state[config_key] = config_value
|
|
170
240
|
end
|
|
171
241
|
|
|
@@ -236,5 +306,27 @@ class RedisQueuedLocks::Config
|
|
|
236
306
|
)
|
|
237
307
|
end
|
|
238
308
|
end
|
|
309
|
+
|
|
310
|
+
# A priority name of the default lock request priority should be a priority name of
|
|
311
|
+
# `lock_priorities` (an unknown name would fail each lock acquirement with the default priority).
|
|
312
|
+
#
|
|
313
|
+
# @param lock_priorities [Hash<Symbol,Integer>] The priority names.
|
|
314
|
+
# @param default_lock_priority [Integer,Symbol] The default lock request priority.
|
|
315
|
+
# @return [void]
|
|
316
|
+
#
|
|
317
|
+
# @raise [RedisQueuedLocks::ConfigValidationError]
|
|
318
|
+
#
|
|
319
|
+
# @api private
|
|
320
|
+
# @since 1.22.0
|
|
321
|
+
def prevent_default_priority__unknown_name(lock_priorities, default_lock_priority)
|
|
322
|
+
if default_lock_priority.is_a?(Symbol) && !lock_priorities.key?(default_lock_priority)
|
|
323
|
+
raise(
|
|
324
|
+
RedisQueuedLocks::ConfigValidationError,
|
|
325
|
+
"The priority name #{default_lock_priority.inspect} of the 'default_lock_priority' " \
|
|
326
|
+
"config key is not a key of the 'lock_priorities' config key " \
|
|
327
|
+
"(#{lock_priorities.keys.map(&:inspect).join(', ')})."
|
|
328
|
+
)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
239
331
|
end
|
|
240
332
|
# rubocop:enable Metrics/ClassLength
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.22.0
|
|
6
6
|
# rubocop:disable Metrics/ModuleLength
|
|
7
7
|
module RedisQueuedLocks::Resource
|
|
8
8
|
# @return [String]
|
|
@@ -90,6 +90,44 @@ module RedisQueuedLocks::Resource
|
|
|
90
90
|
# @since 1.3.0
|
|
91
91
|
REDIS_TIMESHIFT_ERROR = 2
|
|
92
92
|
|
|
93
|
+
# The lowest lock request priority (the `priority` option of `#lock`).
|
|
94
|
+
#
|
|
95
|
+
# @return [Integer]
|
|
96
|
+
#
|
|
97
|
+
# @api private
|
|
98
|
+
# @since 1.22.0
|
|
99
|
+
MIN_LOCK_PRIORITY = -100
|
|
100
|
+
|
|
101
|
+
# The highest lock request priority (the `priority` option of `#lock`).
|
|
102
|
+
#
|
|
103
|
+
# @return [Integer]
|
|
104
|
+
#
|
|
105
|
+
# @api private
|
|
106
|
+
# @since 1.22.0
|
|
107
|
+
MAX_LOCK_PRIORITY = 100
|
|
108
|
+
|
|
109
|
+
# Lock request priority band: the position of the lock request in the lock request queue (the
|
|
110
|
+
# score) is the enqueue time (epoch, seconds) shifted by `priority * LOCK_PRIORITY_BAND` down:
|
|
111
|
+
# - each priority gets its own range of positions (the band): requests of the higher priority
|
|
112
|
+
# are always ahead of the requests of the lower priority, requests of the same priority are
|
|
113
|
+
# ordered by the enqueue time (FIFO);
|
|
114
|
+
# - 2**32 seconds (~136 years) is greater than any enqueue time (epoch) up to the year 2106,
|
|
115
|
+
# so the bands never overlap and the priority is decoded from the position unambiguously;
|
|
116
|
+
# - the default priority (`0`) keeps the position equal to the enqueue time (the positions of
|
|
117
|
+
# the RQL versions without priorities);
|
|
118
|
+
# - the band of the priority `p` is `(-p * LOCK_PRIORITY_BAND, (1 - p) * LOCK_PRIORITY_BAND)`;
|
|
119
|
+
# - the enqueue time is decoded from the position of any priority, so dead lock requests of any
|
|
120
|
+
# band are recognized by the position (the time barrier of the band, see
|
|
121
|
+
# `lock_priority_band_dead_score`) without the knowledge of the priority range;
|
|
122
|
+
# - the precision of the positions (the FIFO precision inside a priority) depends on the
|
|
123
|
+
# shift: ~1 µs for -1..1, ~61 µs for -100..100 (`MIN_LOCK_PRIORITY`..`MAX_LOCK_PRIORITY`):
|
|
124
|
+
# requests of the same priority made within this interval are ordered by acquirer ids;
|
|
125
|
+
# @return [Integer] In seconds.
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
# @since 1.22.0
|
|
129
|
+
LOCK_PRIORITY_BAND = 4_294_967_296
|
|
130
|
+
|
|
93
131
|
# rubocop:disable Metrics/ClassLength
|
|
94
132
|
class << self
|
|
95
133
|
# Returns 16-byte unique identifier. It is used for uniquely
|
|
@@ -276,6 +314,22 @@ module RedisQueuedLocks::Resource
|
|
|
276
314
|
::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
277
315
|
end
|
|
278
316
|
|
|
317
|
+
# Is the lock name of the lock index expired (all the keys of the lock are expired)?
|
|
318
|
+
#
|
|
319
|
+
# NOTE: the lock index replies of ZSCAN have String scores: `+inf` (a key without TTL indexed
|
|
320
|
+
# by `ReindexLocks`) is `"inf"`, and `String#to_f` turns it into `0.0` (the name would be
|
|
321
|
+
# skipped as an expired one by every lock index lookup): it never expires;
|
|
322
|
+
#
|
|
323
|
+
# @param lock_index_score [String] The score of the lock name (a ZSCAN reply).
|
|
324
|
+
# @param lock_index_time [Integer] The current client time (see `lock_index_time`).
|
|
325
|
+
# @return [Boolean]
|
|
326
|
+
#
|
|
327
|
+
# @api private
|
|
328
|
+
# @since 1.22.0
|
|
329
|
+
def lock_index_score_expired?(lock_index_score, lock_index_time)
|
|
330
|
+
lock_index_score != 'inf' && lock_index_score.to_f < lock_index_time
|
|
331
|
+
end
|
|
332
|
+
|
|
279
333
|
# @param lock_readers_key [String]
|
|
280
334
|
# @return [String]
|
|
281
335
|
#
|
|
@@ -327,16 +381,23 @@ module RedisQueuedLocks::Resource
|
|
|
327
381
|
(Integer(redis_time[0]) * 1_000) + (Integer(redis_time[1]) / 1_000)
|
|
328
382
|
end
|
|
329
383
|
|
|
330
|
-
# @
|
|
384
|
+
# @param priority [Integer]
|
|
385
|
+
# The lock request priority (from `MIN_LOCK_PRIORITY` to `MAX_LOCK_PRIORITY`).
|
|
386
|
+
# @return [Float]
|
|
387
|
+
# Redis's <Set> score: the time (epoch, as a float) shifted by the priority band (see
|
|
388
|
+
# `LOCK_PRIORITY_BAND`; the default priority `0` keeps the time).
|
|
331
389
|
#
|
|
332
390
|
# @api private
|
|
333
391
|
# @since 1.0.0
|
|
334
|
-
|
|
335
|
-
|
|
392
|
+
# @version 1.22.0
|
|
393
|
+
def calc_initial_acquirer_position(priority)
|
|
394
|
+
Time.now.to_f - (priority * LOCK_PRIORITY_BAND)
|
|
336
395
|
end
|
|
337
396
|
|
|
338
397
|
# @param queue_ttl [Numeric] In seconds
|
|
339
|
-
# @return [Float]
|
|
398
|
+
# @return [Float]
|
|
399
|
+
# Redis's <Set> score barrier for acquirers that should be removed from queue (the time
|
|
400
|
+
# barrier: the barrier of the default priority band, see `lock_priority_band_dead_score`).
|
|
340
401
|
#
|
|
341
402
|
# @api private
|
|
342
403
|
# @since 1.0.0
|
|
@@ -344,6 +405,57 @@ module RedisQueuedLocks::Resource
|
|
|
344
405
|
Time.now.to_f - queue_ttl
|
|
345
406
|
end
|
|
346
407
|
|
|
408
|
+
# The lowest position (score) of the lock requests of the given priority band (see
|
|
409
|
+
# `LOCK_PRIORITY_BAND`): the lower bound of the dead lock request removal (`ZREMRANGEBYSCORE`).
|
|
410
|
+
#
|
|
411
|
+
# @param priority [Integer] Any priority (the bands of all priorities have the same layout).
|
|
412
|
+
# @return [Integer]
|
|
413
|
+
# The lower edge of the band (the positions of the band never reach its edges: an enqueue
|
|
414
|
+
# time is always greater than 0 and less than `LOCK_PRIORITY_BAND`).
|
|
415
|
+
#
|
|
416
|
+
# @api private
|
|
417
|
+
# @since 1.22.0
|
|
418
|
+
def lock_priority_band_floor(priority)
|
|
419
|
+
-priority * LOCK_PRIORITY_BAND
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Requests of the given priority band queued at this position or earlier are dead (the upper
|
|
423
|
+
# bound of the dead lock request removal, see `LOCK_PRIORITY_BAND`).
|
|
424
|
+
#
|
|
425
|
+
# @param dead_score [Float] The time barrier (see `acquirer_dead_score`).
|
|
426
|
+
# @param priority [Integer]
|
|
427
|
+
# @return [Float]
|
|
428
|
+
#
|
|
429
|
+
# @api private
|
|
430
|
+
# @since 1.22.0
|
|
431
|
+
def lock_priority_band_dead_score(dead_score, priority)
|
|
432
|
+
dead_score - (priority * LOCK_PRIORITY_BAND)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Priority of the lock request by its position in the lock request queue (the score).
|
|
436
|
+
#
|
|
437
|
+
# NOTE: (infinite positions) RQL never writes infinite positions (manual writes of the lock
|
|
438
|
+
# request queues): they are out of all bands and are decoded to the bounds of the priority
|
|
439
|
+
# range (the floor of an infinite Float raises `FloatDomainError`):
|
|
440
|
+
# - `-inf`: `MAX_LOCK_PRIORITY` (ahead of all lock requests; such a request is dead: its
|
|
441
|
+
# enqueue time is unknown, it is dropped like before priorities);
|
|
442
|
+
# - `+inf`: `MIN_LOCK_PRIORITY` (behind all lock requests; never dead);
|
|
443
|
+
#
|
|
444
|
+
# @param acquirer_position [Numeric] The position (the score) of the lock request.
|
|
445
|
+
# @return [Integer] See `LOCK_PRIORITY_BAND`.
|
|
446
|
+
#
|
|
447
|
+
# @api private
|
|
448
|
+
# @since 1.22.0
|
|
449
|
+
def lock_request_priority(acquirer_position)
|
|
450
|
+
if acquirer_position == -Float::INFINITY
|
|
451
|
+
MAX_LOCK_PRIORITY
|
|
452
|
+
elsif acquirer_position == Float::INFINITY
|
|
453
|
+
MIN_LOCK_PRIORITY
|
|
454
|
+
else
|
|
455
|
+
-(acquirer_position / LOCK_PRIORITY_BAND).floor
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
347
459
|
# @param zombie_ttl [Float] In seconds with milliseconds.
|
|
348
460
|
# @return [Float]
|
|
349
461
|
#
|
|
@@ -354,8 +466,10 @@ module RedisQueuedLocks::Resource
|
|
|
354
466
|
end
|
|
355
467
|
|
|
356
468
|
# @param acquirer_position [Float]
|
|
357
|
-
# A time (epoch, seconds.milliseconds) that represents
|
|
358
|
-
# the acquirer position in lock request queue.
|
|
469
|
+
# A time (epoch, seconds.milliseconds) shifted by the priority band that represents
|
|
470
|
+
# the acquirer position in lock request queue (see `calc_initial_acquirer_position`).
|
|
471
|
+
# @param priority [Integer]
|
|
472
|
+
# The lock request priority (the band of the position).
|
|
359
473
|
# @param queue_ttl [Integer]
|
|
360
474
|
# In second.
|
|
361
475
|
# @return [Boolean]
|
|
@@ -363,8 +477,9 @@ module RedisQueuedLocks::Resource
|
|
|
363
477
|
#
|
|
364
478
|
# @api private
|
|
365
479
|
# @since 1.0.0
|
|
366
|
-
|
|
367
|
-
|
|
480
|
+
# @version 1.22.0
|
|
481
|
+
def dead_score_reached?(acquirer_position, priority, queue_ttl)
|
|
482
|
+
(acquirer_position + (priority * LOCK_PRIORITY_BAND) + queue_ttl) < Time.now.to_f
|
|
368
483
|
end
|
|
369
484
|
|
|
370
485
|
# NOTE: (debug) not used by the library: a manual runtime check (in a console) of the lock that
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.9.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.21.1
|
|
6
6
|
# rubocop:disable Metrics/ClassLength
|
|
7
7
|
class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
8
8
|
# @since 1.9.0
|
|
@@ -70,7 +70,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
|
70
70
|
return unless enabled?
|
|
71
71
|
|
|
72
72
|
sync.synchronize do
|
|
73
|
-
|
|
73
|
+
swarm_element__terminate
|
|
74
74
|
swarm!
|
|
75
75
|
swarm_loop__start
|
|
76
76
|
end
|
|
@@ -99,7 +99,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
|
99
99
|
# @since 1.9.0
|
|
100
100
|
def try_kill!
|
|
101
101
|
sync.synchronize do
|
|
102
|
-
|
|
102
|
+
swarm_element__terminate
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
@@ -157,7 +157,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
|
157
157
|
# => 1) init (swarm!): create a thread, main loop is not started;
|
|
158
158
|
# => 2) start (swarm_loop__start): run the main loop inside the created thread;
|
|
159
159
|
# => 3) stop (swarm_loop__stop): stop the main loop inside the created thread;
|
|
160
|
-
# => 4) terminate (
|
|
160
|
+
# => 4) terminate (swarm_element__terminate): kill the created thread;
|
|
161
161
|
# In addition you should implement `#spawn_main_loop!` method that will be the main logic
|
|
162
162
|
# of the your swarm element.
|
|
163
163
|
#
|
|
@@ -341,7 +341,8 @@ class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
|
341
341
|
#
|
|
342
342
|
# @api private
|
|
343
343
|
# @since 1.9.0
|
|
344
|
-
|
|
344
|
+
# @version 1.21.1
|
|
345
|
+
def swarm_element__terminate
|
|
345
346
|
return if idle? || swarmed__dead?
|
|
346
347
|
sync.synchronize do
|
|
347
348
|
main_loop&.kill
|
|
@@ -232,7 +232,8 @@ module RedisQueuedLocks::Swarm::ZombieInfo
|
|
|
232
232
|
# names, the full keyspace is never scanned): write locks and readers registries are processed
|
|
233
233
|
# in batches of `lock_scan_size` locks with one round trip per batch and per key family
|
|
234
234
|
# (batches are collected across ZSCAN pages). Expired lock names of the index are skipped
|
|
235
|
-
# (all their keys are expired
|
|
235
|
+
# (all their keys are expired; `+inf` names, keys without TTL, never expire: see
|
|
236
|
+
# `RedisQueuedLocks::Resource.lock_index_score_expired?`).
|
|
236
237
|
#
|
|
237
238
|
# @param rconn [RedisClient] redis connection obtained via `#with` from RedisClient instance;
|
|
238
239
|
# @param zombie_hosts [Set<String>]
|
|
@@ -244,6 +245,7 @@ module RedisQueuedLocks::Swarm::ZombieInfo
|
|
|
244
245
|
#
|
|
245
246
|
# @api private
|
|
246
247
|
# @since 1.20.0
|
|
248
|
+
# @version 1.22.0
|
|
247
249
|
def each_lock_index_zombie_lock(rconn, zombie_hosts, lock_scan_size, &block)
|
|
248
250
|
# @type var lock_keys: Array[String]
|
|
249
251
|
lock_keys = []
|
|
@@ -255,7 +257,9 @@ module RedisQueuedLocks::Swarm::ZombieInfo
|
|
|
255
257
|
RedisQueuedLocks::Resource::LOCK_INDEX_KEY, count: lock_scan_size
|
|
256
258
|
) do |lock_name, lock_index_score|
|
|
257
259
|
# NOTE: the expired lock name (not dropped from the index yet): its keys are expired
|
|
258
|
-
next if
|
|
260
|
+
next if RedisQueuedLocks::Resource.lock_index_score_expired?(
|
|
261
|
+
lock_index_score, lock_index_time
|
|
262
|
+
)
|
|
259
263
|
|
|
260
264
|
lock_keys << RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
261
265
|
lock_readers_keys << RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
|
data/rbs_collection.lock.yaml
CHANGED
data/rbs_collection.yaml
CHANGED
|
@@ -2,7 +2,7 @@ sources:
|
|
|
2
2
|
- type: git
|
|
3
3
|
name: ruby/gem_rbs_collection
|
|
4
4
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
5
|
-
revision:
|
|
5
|
+
revision: c69c75ba79d7c19870f8d0e6fa7fe2c1d356e5ae # NOTE: pinned for reproducible checks; bump deliberately (`bundle exec rbs collection update`)
|
|
6
6
|
repo_dir: gems
|
|
7
7
|
|
|
8
8
|
path: .gem_rbs_collection
|
|
@@ -13,3 +13,4 @@ gems:
|
|
|
13
13
|
- name: timeout
|
|
14
14
|
- name: logger
|
|
15
15
|
- name: monitor
|
|
16
|
+
- name: json # NOTE: used by the runtime type checking only, not by the library code
|
data/redis_queued_locks.gemspec
CHANGED
|
@@ -14,20 +14,27 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
'Distributed locks with "prioritized lock acquisition queue" ' \
|
|
15
15
|
'capabilities based on the Redis Database.'
|
|
16
16
|
|
|
17
|
-
spec.description =
|
|
18
|
-
|
|
19
|
-
"based on the Redis Database
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
spec.description = <<~GEMSPEC_DESCRIPTION
|
|
18
|
+
RedisQueuedLocks (aka RQL, aka RQL-lock) - distributed locks with
|
|
19
|
+
"prioritized lock acquisition queue" capabilities based on the Redis Database.
|
|
20
|
+
|
|
21
|
+
Each lock request is put into the request queue
|
|
22
|
+
(each lock is hosted by its own queue separately from other queues)
|
|
23
|
+
and processed in order of their priority: requests of a higher priority go first,
|
|
24
|
+
requests of the same priority are processed in FIFO order.
|
|
25
|
+
Each lock request lives some period of time (RTTL) (with requeue capabilities)
|
|
26
|
+
which guarantees the request queue will never be stacked.
|
|
27
|
+
|
|
28
|
+
In addition to the classic queued (FIFO) strategy RQL supports random (RANDOM)
|
|
29
|
+
lock obtaining strategy when any acquirer from the lock queue
|
|
30
|
+
can obtain the lock regardless the position in the queue.
|
|
31
|
+
|
|
32
|
+
Supports lock request prioritization, Read/Write locks and Reentrant locks.
|
|
33
|
+
|
|
34
|
+
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl,
|
|
35
|
+
queue ttl, lock attempts limit, fast failing, etc), zombie locks elimination,
|
|
36
|
+
lock indexing, logging and instrumentation (and much more).
|
|
37
|
+
GEMSPEC_DESCRIPTION
|
|
31
38
|
|
|
32
39
|
spec.homepage = 'https://github.com/0exp/redis_queued_locks'
|
|
33
40
|
spec.license = 'MIT'
|
data/sig/manifest.yml
CHANGED
|
@@ -9,13 +9,13 @@ module RedisQueuedLocks
|
|
|
9
9
|
RC::client redis,
|
|
10
10
|
RQL::loggerObj logger,
|
|
11
11
|
String lock_key,
|
|
12
|
-
|
|
12
|
+
RQL::readWriteMode rw_mode,
|
|
13
13
|
String lock_key_queue,
|
|
14
14
|
String read_lock_key_queue,
|
|
15
15
|
Integer queue_ttl,
|
|
16
16
|
String acquirer_id,
|
|
17
17
|
String host_id,
|
|
18
|
-
|
|
18
|
+
RQL::accessStrategy access_strategy,
|
|
19
19
|
bool log_sampled,
|
|
20
20
|
bool instr_sampled
|
|
21
21
|
) -> { ok: bool, result: Integer }
|
|
@@ -8,7 +8,8 @@ module RedisQueuedLocks
|
|
|
8
8
|
RQL::instrObj instrumenter,
|
|
9
9
|
bool instr_sampled,
|
|
10
10
|
String lock_key,
|
|
11
|
-
|
|
11
|
+
RQL::readWriteMode rw_mode,
|
|
12
|
+
Integer priority,
|
|
12
13
|
Integer? ttl,
|
|
13
14
|
String acq_id,
|
|
14
15
|
String hst_id,
|
|
@@ -21,7 +22,8 @@ module RedisQueuedLocks
|
|
|
21
22
|
RQL::instrObj instrumenter,
|
|
22
23
|
bool instr_sampled,
|
|
23
24
|
String lock_key,
|
|
24
|
-
|
|
25
|
+
RQL::readWriteMode rw_mode,
|
|
26
|
+
Integer priority,
|
|
25
27
|
Integer? ttl,
|
|
26
28
|
String acq_id,
|
|
27
29
|
String hst_id,
|
|
@@ -34,7 +36,8 @@ module RedisQueuedLocks
|
|
|
34
36
|
RQL::instrObj instrumenter,
|
|
35
37
|
bool instr_sampled,
|
|
36
38
|
String lock_key,
|
|
37
|
-
|
|
39
|
+
RQL::readWriteMode rw_mode,
|
|
40
|
+
Integer priority,
|
|
38
41
|
Integer? ttl,
|
|
39
42
|
String acq_id,
|
|
40
43
|
String hst_id,
|
|
@@ -47,7 +50,8 @@ module RedisQueuedLocks
|
|
|
47
50
|
RQL::instrObj instrumenter,
|
|
48
51
|
bool instr_sampled,
|
|
49
52
|
String lock_key,
|
|
50
|
-
|
|
53
|
+
RQL::readWriteMode rw_mode,
|
|
54
|
+
Integer priority,
|
|
51
55
|
Integer? ttl,
|
|
52
56
|
String acq_id,
|
|
53
57
|
String hst_id,
|
|
@@ -61,7 +65,8 @@ module RedisQueuedLocks
|
|
|
61
65
|
RQL::instrObj instrumenter,
|
|
62
66
|
bool instr_sampled,
|
|
63
67
|
String lock_key,
|
|
64
|
-
|
|
68
|
+
RQL::readWriteMode rw_mode,
|
|
69
|
+
Integer priority,
|
|
65
70
|
Integer? ttl,
|
|
66
71
|
String acq_id,
|
|
67
72
|
String hst_id,
|
|
@@ -75,7 +80,8 @@ module RedisQueuedLocks
|
|
|
75
80
|
RQL::instrObj instrumenter,
|
|
76
81
|
bool instr_sampled,
|
|
77
82
|
String lock_key,
|
|
78
|
-
|
|
83
|
+
RQL::readWriteMode rw_mode,
|
|
84
|
+
Integer priority,
|
|
79
85
|
Integer ttl,
|
|
80
86
|
String acq_id,
|
|
81
87
|
String hst_id,
|
|
@@ -11,8 +11,9 @@ module RedisQueuedLocks
|
|
|
11
11
|
Integer queue_ttl,
|
|
12
12
|
String acquirer_id,
|
|
13
13
|
String host_id,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
RQL::accessStrategy access_strategy,
|
|
15
|
+
RQL::readWriteMode rw_mode,
|
|
16
|
+
Integer priority
|
|
16
17
|
) -> void
|
|
17
18
|
|
|
18
19
|
def self.start_try_to_lock_cycle: (
|
|
@@ -22,8 +23,9 @@ module RedisQueuedLocks
|
|
|
22
23
|
Integer queue_ttl,
|
|
23
24
|
String acquirer_id,
|
|
24
25
|
String host_id,
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
RQL::accessStrategy access_strategy,
|
|
27
|
+
RQL::readWriteMode rw_mode,
|
|
28
|
+
Integer priority
|
|
27
29
|
) -> void
|
|
28
30
|
|
|
29
31
|
def self.dead_score_reached__reset_acquirer_position: (
|
|
@@ -33,8 +35,9 @@ module RedisQueuedLocks
|
|
|
33
35
|
Integer queue_ttl,
|
|
34
36
|
String acquirer_id,
|
|
35
37
|
String host_id,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
RQL::accessStrategy access_strategy,
|
|
39
|
+
RQL::readWriteMode rw_mode,
|
|
40
|
+
Integer priority
|
|
38
41
|
) -> void
|
|
39
42
|
|
|
40
43
|
def self.extendable_reentrant_lock_obtained: (
|
|
@@ -45,8 +48,9 @@ module RedisQueuedLocks
|
|
|
45
48
|
String acquirer_id,
|
|
46
49
|
String host_id,
|
|
47
50
|
Integer|Float acq_time,
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
RQL::accessStrategy access_strategy,
|
|
52
|
+
RQL::readWriteMode rw_mode,
|
|
53
|
+
Integer priority
|
|
50
54
|
) -> void
|
|
51
55
|
|
|
52
56
|
def self.reentrant_lock_obtained: (
|
|
@@ -57,8 +61,9 @@ module RedisQueuedLocks
|
|
|
57
61
|
String acquirer_id,
|
|
58
62
|
String host_id,
|
|
59
63
|
Integer|Float acq_time,
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
RQL::accessStrategy access_strategy,
|
|
65
|
+
RQL::readWriteMode rw_mode,
|
|
66
|
+
Integer priority
|
|
62
67
|
) -> void
|
|
63
68
|
|
|
64
69
|
def self.lock_obtained: (
|
|
@@ -69,8 +74,9 @@ module RedisQueuedLocks
|
|
|
69
74
|
String acquirer_id,
|
|
70
75
|
String host_id,
|
|
71
76
|
Integer|Float acq_time,
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
RQL::accessStrategy access_strategy,
|
|
78
|
+
RQL::readWriteMode rw_mode,
|
|
79
|
+
Integer priority
|
|
74
80
|
) -> void
|
|
75
81
|
|
|
76
82
|
def self.lock_obtaining_failed: (
|
|
@@ -81,8 +87,9 @@ module RedisQueuedLocks
|
|
|
81
87
|
String acquirer_id,
|
|
82
88
|
String host_id,
|
|
83
89
|
Integer|Float acq_time,
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
RQL::accessStrategy access_strategy,
|
|
91
|
+
RQL::readWriteMode rw_mode,
|
|
92
|
+
Integer priority,
|
|
86
93
|
Symbol result
|
|
87
94
|
) -> void
|
|
88
95
|
end
|