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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Releases the lock series: only the locks that are obtained by the series itself and only if they
|
|
4
|
+
# are still owned by the current acquirer; the reentrant locks (obtained by the outer logic of
|
|
5
|
+
# the current acquirer) are kept and the extended ones get their ttl back.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
# @since 1.23.0
|
|
9
|
+
module RedisQueuedLocks::Acquirer::LockSeries::ReleaseLockSeries
|
|
10
|
+
# @param redis [RedisClient]
|
|
11
|
+
# @param lock_names [Array<String>]
|
|
12
|
+
# @param lock_processes [Array<Symbol,NilClass>]
|
|
13
|
+
# - The way each lock of the series is held (the order of `lock_names`):
|
|
14
|
+
# - `:lock_obtaining` - obtained by the series itself (released);
|
|
15
|
+
# - `:extendable_conflict_work_through` - extended reentrant lock (its ttl is decreased);
|
|
16
|
+
# - `:conflict_work_through` - reentrant lock (kept as is);
|
|
17
|
+
# @param held_rw_modes [Array<Symbol>]
|
|
18
|
+
# The mode of the lock that is really held by the acquirer (the order of `lock_names`).
|
|
19
|
+
# @param acquirer_id [String]
|
|
20
|
+
# @param decreased_ttl [Integer]
|
|
21
|
+
# The ttl to take back from the extended reentrant locks (in milliseconds): the extension
|
|
22
|
+
# without the time spent under the locks; a non-positive value keeps these locks as is.
|
|
23
|
+
# @return [void]
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
# @since 1.23.0
|
|
27
|
+
def release_lock_series(
|
|
28
|
+
redis,
|
|
29
|
+
lock_names,
|
|
30
|
+
lock_processes,
|
|
31
|
+
held_rw_modes,
|
|
32
|
+
acquirer_id,
|
|
33
|
+
decreased_ttl
|
|
34
|
+
)
|
|
35
|
+
# @type var write_lock_names: Array[String]
|
|
36
|
+
write_lock_names = []
|
|
37
|
+
# @type var read_lock_names: Array[String]
|
|
38
|
+
read_lock_names = []
|
|
39
|
+
# @type var extended_write_lock_names: Array[String]
|
|
40
|
+
extended_write_lock_names = []
|
|
41
|
+
# @type var extended_read_lock_names: Array[String]
|
|
42
|
+
extended_read_lock_names = []
|
|
43
|
+
|
|
44
|
+
lock_names.each_with_index do |lock_name, index|
|
|
45
|
+
read_lock = held_rw_modes[index] == :read
|
|
46
|
+
|
|
47
|
+
case lock_processes[index]
|
|
48
|
+
when :lock_obtaining
|
|
49
|
+
(read_lock ? read_lock_names : write_lock_names) << lock_name
|
|
50
|
+
when :extendable_conflict_work_through
|
|
51
|
+
(read_lock ? extended_read_lock_names : extended_write_lock_names) << lock_name
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# NOTE: a non-positive decreased ttl: the spent time exceeds the extension
|
|
56
|
+
if decreased_ttl <= 0
|
|
57
|
+
extended_read_lock_names.clear
|
|
58
|
+
extended_write_lock_names.clear
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
release_write_lock_series(
|
|
62
|
+
redis, write_lock_names, extended_write_lock_names, acquirer_id, decreased_ttl
|
|
63
|
+
)
|
|
64
|
+
release_read_lock_series(
|
|
65
|
+
redis, read_lock_names, extended_read_lock_names, acquirer_id, decreased_ttl
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Releases the write locks (and decreases the ttl of the extended ones) of the given acquirer
|
|
72
|
+
# (compare-and-delete: the lock can be expired and obtained by another acquirer when the series
|
|
73
|
+
# logic exceeds the lock ttl) in one transaction for the whole series (three round trips: WATCH,
|
|
74
|
+
# lock owners, MULTI/EXEC):
|
|
75
|
+
# - an aborted transaction (one of the watched locks is changed) is retried with the locks
|
|
76
|
+
# that were still owned by the acquirer at the last check: the locks of other acquirers
|
|
77
|
+
# are neither touched nor watched, so the retries converge;
|
|
78
|
+
#
|
|
79
|
+
# @param redis [RedisClient]
|
|
80
|
+
# @param lock_names [Array<String>] The locks to release.
|
|
81
|
+
# @param extended_lock_names [Array<String>] The extended reentrant locks.
|
|
82
|
+
# @param acquirer_id [String]
|
|
83
|
+
# @param decreased_ttl [Integer] In milliseconds.
|
|
84
|
+
# @return [void]
|
|
85
|
+
#
|
|
86
|
+
# @api private
|
|
87
|
+
# @since 1.23.0
|
|
88
|
+
# rubocop:disable Metrics/MethodLength
|
|
89
|
+
def release_write_lock_series(redis, lock_names, extended_lock_names, acquirer_id, decreased_ttl)
|
|
90
|
+
return if lock_names.empty? && extended_lock_names.empty?
|
|
91
|
+
|
|
92
|
+
own_lock_keys = lock_names.map do |lock_name|
|
|
93
|
+
RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
94
|
+
end
|
|
95
|
+
own_extended_lock_keys = extended_lock_names.map do |lock_name|
|
|
96
|
+
RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
redis.with do |conn|
|
|
100
|
+
until own_lock_keys.empty? && own_extended_lock_keys.empty?
|
|
101
|
+
released = conn.multi(watch: own_lock_keys + own_extended_lock_keys) do |transaction|
|
|
102
|
+
# NOTE: (pipeline layout) one reply (lock owner) per lock: the released locks first
|
|
103
|
+
# @type var lock_owners: Array[String?]
|
|
104
|
+
lock_owners = conn.pipelined do |pipeline|
|
|
105
|
+
own_lock_keys.each { |lock_key| pipeline.call('HGET', lock_key, 'acq_id') }
|
|
106
|
+
own_extended_lock_keys.each { |lock_key| pipeline.call('HGET', lock_key, 'acq_id') }
|
|
107
|
+
end
|
|
108
|
+
extended_locks_at = own_lock_keys.size
|
|
109
|
+
own_lock_keys = own_lock_keys.select.with_index do |_lock_key, index|
|
|
110
|
+
lock_owners[index] == acquirer_id
|
|
111
|
+
end
|
|
112
|
+
own_extended_lock_keys = own_extended_lock_keys.select.with_index do |_lock_key, index|
|
|
113
|
+
lock_owners[extended_locks_at + index] == acquirer_id
|
|
114
|
+
end
|
|
115
|
+
own_lock_keys.each { |lock_key| transaction.call('DEL', lock_key) }
|
|
116
|
+
# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
|
|
117
|
+
own_extended_lock_keys.each do |lock_key|
|
|
118
|
+
transaction.call(
|
|
119
|
+
'EVAL',
|
|
120
|
+
RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::DECREASE_LOCK_PTTL,
|
|
121
|
+
1,
|
|
122
|
+
lock_key,
|
|
123
|
+
decreased_ttl
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
# NOTE: `nil` => the transaction is aborted (a watched lock is changed) => retry
|
|
128
|
+
break unless released == nil
|
|
129
|
+
end
|
|
130
|
+
rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
|
|
131
|
+
RedisQueuedLocks::TimedLockIntermediateTimeoutError,
|
|
132
|
+
::Timeout::ExitException
|
|
133
|
+
# NOTE: (timeouts) an interrupted WATCH block (the internal timeouts and the user's
|
|
134
|
+
# `Timeout.timeout` without an exception class): the closed connection drops the WATCH
|
|
135
|
+
# state (redis-client sends `UNWATCH` only on `StandardError`);
|
|
136
|
+
conn.close
|
|
137
|
+
raise
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
# rubocop:enable Metrics/MethodLength
|
|
141
|
+
|
|
142
|
+
# Releases the read locks (and decreases the extended ones) of the given acquirer in one
|
|
143
|
+
# transaction (other readers are not affected).
|
|
144
|
+
#
|
|
145
|
+
# @param redis [RedisClient]
|
|
146
|
+
# @param lock_names [Array<String>] The locks to release.
|
|
147
|
+
# @param extended_lock_names [Array<String>] The extended reentrant locks.
|
|
148
|
+
# @param acquirer_id [String]
|
|
149
|
+
# @param decreased_ttl [Integer] In milliseconds.
|
|
150
|
+
# @return [void]
|
|
151
|
+
#
|
|
152
|
+
# @api private
|
|
153
|
+
# @since 1.23.0
|
|
154
|
+
def release_read_lock_series(redis, lock_names, extended_lock_names, acquirer_id, decreased_ttl)
|
|
155
|
+
return if lock_names.empty? && extended_lock_names.empty?
|
|
156
|
+
|
|
157
|
+
redis.multi do |transaction|
|
|
158
|
+
lock_names.each do |lock_name|
|
|
159
|
+
transaction.call(
|
|
160
|
+
'ZREM', RedisQueuedLocks::Resource.prepare_lock_readers(lock_name), acquirer_id
|
|
161
|
+
)
|
|
162
|
+
transaction.call(
|
|
163
|
+
'DEL', RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
# NOTE:
|
|
167
|
+
# - XX => do nothing if the read lock is already expired and removed from the registry;
|
|
168
|
+
# - the read lock data TTL is not decreased (it is not used without the read lock);
|
|
169
|
+
extended_lock_names.each do |lock_name|
|
|
170
|
+
transaction.call(
|
|
171
|
+
'ZADD',
|
|
172
|
+
RedisQueuedLocks::Resource.prepare_lock_readers(lock_name),
|
|
173
|
+
'XX',
|
|
174
|
+
'INCR',
|
|
175
|
+
-decreased_ttl,
|
|
176
|
+
acquirer_id
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|