redis_queued_locks 1.17.0 → 1.18.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 +86 -10
- data/.claude/rules/acquirer.md +31 -11
- data/.claude/rules/arguments.md +2 -1
- data/.claude/rules/git.md +46 -0
- data/.claude/rules/logic.md +9 -3
- data/.claude/rules/swarm.md +4 -3
- data/.claude/rules/tests.md +9 -3
- data/.claude/rules/type-checking.md +10 -2
- data/.claude/rules/visitors.md +12 -7
- data/CHANGELOG.md +62 -0
- data/CLAUDE.md +7 -2
- data/README.md +348 -79
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +6 -3
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +9 -7
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +20 -10
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +34 -16
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +240 -28
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +398 -129
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +12 -6
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +32 -8
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +73 -20
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +9 -1
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +182 -3
- data/lib/redis_queued_locks/acquirer/is_locked.rb +19 -2
- data/lib/redis_queued_locks/acquirer/is_queued.rb +6 -2
- data/lib/redis_queued_locks/acquirer/lock_info.rb +96 -6
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +8 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -6
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +126 -27
- data/lib/redis_queued_locks/acquirer/locks.rb +103 -6
- data/lib/redis_queued_locks/acquirer/queue_info.rb +38 -9
- data/lib/redis_queued_locks/acquirer/queues.rb +28 -4
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +35 -1
- data/lib/redis_queued_locks/acquirer/release_lock.rb +40 -9
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +43 -3
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +143 -0
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +129 -14
- data/lib/redis_queued_locks/resource.rb +117 -9
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +38 -0
- data/lib/redis_queued_locks/swarm/zombie_info.rb +42 -0
- data/lib/redis_queued_locks/version.rb +1 -1
- data/rbs_collection.lock.yaml +10 -2
- data/rbs_collection.yaml +0 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +0 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +5 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +12 -6
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +74 -7
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +4 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +4 -2
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +19 -1
- data/sig/redis_queued_locks/acquirer/lock_info.rbs +9 -1
- data/sig/redis_queued_locks/acquirer/locks.rbs +10 -1
- data/sig/redis_queued_locks/acquirer/queue_info.rbs +2 -4
- data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +44 -0
- data/sig/redis_queued_locks/client.rbs +22 -2
- data/sig/redis_queued_locks/resource.rbs +9 -2
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +5 -0
- metadata +4 -1
|
@@ -2,17 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
+
# @version 1.18.0
|
|
5
6
|
module RedisQueuedLocks::Acquirer::IsLocked
|
|
6
7
|
class << self
|
|
7
8
|
# @param redis_client [RedisClient]
|
|
8
9
|
# @param lock_name [String]
|
|
9
|
-
# @return [Boolean]
|
|
10
|
+
# @return [Boolean] Is the lock obtained by a writer or by any reader (live read lock).
|
|
10
11
|
#
|
|
11
12
|
# @api private
|
|
12
13
|
# @since 1.0.0
|
|
14
|
+
# @version 1.18.0
|
|
13
15
|
def locked?(redis_client, lock_name)
|
|
14
16
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
15
|
-
|
|
17
|
+
lock_readers_key = RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
|
|
18
|
+
|
|
19
|
+
# @type var result: [Integer, Array[String], Array[[String, Float]]]
|
|
20
|
+
result = redis_client.pipelined do |pipeline|
|
|
21
|
+
pipeline.call('EXISTS', lock_key)
|
|
22
|
+
pipeline.call('TIME')
|
|
23
|
+
pipeline.call('ZRANGE', lock_readers_key, '-1', '-1', 'WITHSCORES')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# NOTE: write lock
|
|
27
|
+
return true if result[0] == 1
|
|
28
|
+
|
|
29
|
+
# NOTE: (RW) read lock (expired read locks can live in the readers registry till the cleanup)
|
|
30
|
+
longest_read_lock = result[2].first
|
|
31
|
+
longest_read_lock != nil &&
|
|
32
|
+
longest_read_lock[1] > RedisQueuedLocks::Resource.redis_time_ms(result[1])
|
|
16
33
|
end
|
|
17
34
|
end
|
|
18
35
|
end
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
+
# @version 1.18.0
|
|
5
6
|
module RedisQueuedLocks::Acquirer::IsQueued
|
|
6
7
|
class << self
|
|
7
8
|
# @param redis_client [RedisClient]
|
|
8
9
|
# @param lock_name [String]
|
|
9
|
-
# @return [Boolean]
|
|
10
|
+
# @return [Boolean] Are there any write or read lock requests.
|
|
10
11
|
#
|
|
11
12
|
# @api private
|
|
12
13
|
# @since 1.0.0
|
|
14
|
+
# @version 1.18.0
|
|
13
15
|
def queued?(redis_client, lock_name)
|
|
14
16
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
|
15
|
-
|
|
17
|
+
read_lock_key_queue = RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
|
|
18
|
+
# NOTE: EXISTS returns the number of existing keys
|
|
19
|
+
redis_client.call('EXISTS', lock_key_queue, read_lock_key_queue) > 0
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
+
# @version 1.18.0
|
|
5
6
|
module RedisQueuedLocks::Acquirer::LockInfo
|
|
6
7
|
class << self
|
|
7
8
|
# @param redis_client [RedisClient]
|
|
8
9
|
# @param lock_name [String]
|
|
9
|
-
# @return [Hash<String,String|Numeric
|
|
10
|
-
# - `nil` is returned when lock key does not exist or expired;
|
|
11
|
-
# - result format: {
|
|
10
|
+
# @return [Hash<String,String|Numeric|Array<Hash<String,String|Numeric>>>,NilClass]
|
|
11
|
+
# - `nil` is returned when lock key does not exist or expired (and there are no read locks);
|
|
12
|
+
# - result format (write lock): {
|
|
12
13
|
# 'lock_key' => "rql:lock:your_lockname", # acquired lock key
|
|
14
|
+
# 'rw_mode' => 'write', # lock mode
|
|
13
15
|
# 'acq_id' => "rql:acq:123/456/789/987/uniqstring", # lock acquirer identifier
|
|
14
16
|
# 'hst_id' => "rql:hst:123/456/987/uniqstring", # lock host identifier
|
|
15
17
|
# 'ts' => 123456789.2649841, # <locked at> time stamp (epoch, seconds.microseconds)
|
|
@@ -22,17 +24,36 @@ module RedisQueuedLocks::Acquirer::LockInfo
|
|
|
22
24
|
# 'l_spc_ext_ini_ttl' => 5000, # (milliseconds) the last ttl of reentrant lock
|
|
23
25
|
# 'l_spc_ext_ts' => 123456.789 # (epoch) <extendable reentrant lock obtained at> timestamp
|
|
24
26
|
# }
|
|
27
|
+
# - result format (read locks, when there is no write lock): {
|
|
28
|
+
# 'lock_key' => "rql:lock:your_lockname", # lock key
|
|
29
|
+
# 'rw_mode' => 'read', # lock mode
|
|
30
|
+
# 'rem_ttl' => 123456789, # remaining ttl of the longest read lock (milliseconds)
|
|
31
|
+
# 'readers' => [ # live read locks
|
|
32
|
+
# {
|
|
33
|
+
# 'acq_id' => "rql:acq:123/456/789/987/uniqstring", # read lock acquirer identifier
|
|
34
|
+
# 'hst_id' => "rql:hst:123/456/987/uniqstring", # read lock host identifier
|
|
35
|
+
# 'ts' => 123456789.2649841, # <locked at> time stamp (epoch, seconds.microseconds)
|
|
36
|
+
# 'ini_ttl' => 123456789, # initial read lock ttl (milliseconds)
|
|
37
|
+
# 'rem_ttl' => 123456789, # remaining read lock ttl (milliseconds)
|
|
38
|
+
# <custom metadata (`meta`) and additional keys for reentrant locks (see above)>
|
|
39
|
+
# },
|
|
40
|
+
# ...
|
|
41
|
+
# ]
|
|
42
|
+
# }
|
|
25
43
|
#
|
|
26
44
|
# @api private
|
|
27
45
|
# @since 1.0.0
|
|
28
|
-
# @version 1.
|
|
46
|
+
# @version 1.18.0
|
|
29
47
|
# rubocop:disable Metrics/MethodLength
|
|
30
48
|
def lock_info(redis_client, lock_name)
|
|
31
49
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
50
|
+
lock_readers_key = RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
|
|
32
51
|
|
|
33
52
|
result = redis_client.pipelined do |pipeline|
|
|
34
53
|
pipeline.call('HGETALL', lock_key)
|
|
35
54
|
pipeline.call('PTTL', lock_key)
|
|
55
|
+
pipeline.call('TIME')
|
|
56
|
+
pipeline.call('ZRANGE', lock_readers_key, '0', '-1', 'WITHSCORES')
|
|
36
57
|
end
|
|
37
58
|
|
|
38
59
|
if result == nil
|
|
@@ -48,16 +69,22 @@ module RedisQueuedLocks::Acquirer::LockInfo
|
|
|
48
69
|
# => (will be mutated further with different value types)
|
|
49
70
|
# - result[1] (PTTL) (Integer)
|
|
50
71
|
# => (without any mutation, integer is atomic)
|
|
72
|
+
# - result[2] (TIME) (Array<String>) (redis time)
|
|
73
|
+
# - result[3] (ZRANGE) (Array<[String,Float]>) (read locks)
|
|
51
74
|
|
|
52
|
-
#
|
|
75
|
+
# rubocop:disable Layout/LineLength
|
|
76
|
+
# @type var result: [Hash[String,String|Float|Integer], Integer, Array[String], Array[[String, Float]]]
|
|
77
|
+
# rubocop:enable Layout/LineLength
|
|
53
78
|
hget_cmd_res = result[0]
|
|
54
79
|
pttl_cmd_res = result[1]
|
|
55
80
|
|
|
56
81
|
if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
|
|
57
|
-
|
|
82
|
+
# NOTE: (RW) there is no write lock => check read locks
|
|
83
|
+
read_lock_info(redis_client, lock_name, lock_key, result[2], result[3])
|
|
58
84
|
else
|
|
59
85
|
hget_cmd_res.tap do |lock_data|
|
|
60
86
|
lock_data['lock_key'] = lock_key
|
|
87
|
+
lock_data['rw_mode'] = 'write'
|
|
61
88
|
lock_data['ts'] = Float(lock_data['ts'])
|
|
62
89
|
lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
|
|
63
90
|
lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Float::INFINITY : pttl_cmd_res)
|
|
@@ -73,5 +100,68 @@ module RedisQueuedLocks::Acquirer::LockInfo
|
|
|
73
100
|
end
|
|
74
101
|
end
|
|
75
102
|
# rubocop:enable Metrics/MethodLength
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# Formats live read locks of the lock (expired read locks are ignored).
|
|
107
|
+
#
|
|
108
|
+
# @param redis_client [RedisClient]
|
|
109
|
+
# @param lock_name [String]
|
|
110
|
+
# @param lock_key [String]
|
|
111
|
+
# @param redis_time [Array<String>] Result of the redis `TIME` command.
|
|
112
|
+
# @param read_locks [Array<Array<String,Float>>] Readers registry (acquirer id, expiration).
|
|
113
|
+
# @return [Hash<String,String|Numeric|Array<Hash<String,String|Numeric>>>,NilClass]
|
|
114
|
+
# - `nil` is returned when there are no live read locks;
|
|
115
|
+
# - each reader has the same data as the write lock (acq_id, hst_id, ts, ini_ttl, rem_ttl,
|
|
116
|
+
# meta, reentrant lock data);
|
|
117
|
+
#
|
|
118
|
+
# @api private
|
|
119
|
+
# @since 1.18.0
|
|
120
|
+
# rubocop:disable Metrics/MethodLength
|
|
121
|
+
def read_lock_info(redis_client, lock_name, lock_key, redis_time, read_locks)
|
|
122
|
+
now = RedisQueuedLocks::Resource.redis_time_ms(redis_time)
|
|
123
|
+
live_read_locks = read_locks.select { |(_acquirer_id, expiration)| expiration > now }
|
|
124
|
+
return nil if live_read_locks.empty?
|
|
125
|
+
|
|
126
|
+
# NOTE: read lock data (it can be absent if it is dropped during the read lock life)
|
|
127
|
+
# @type var read_locks_data: Array[Hash[String,String]]
|
|
128
|
+
read_locks_data = redis_client.pipelined do |pipeline|
|
|
129
|
+
live_read_locks.each do |(acquirer_id, _expiration)|
|
|
130
|
+
pipeline.call(
|
|
131
|
+
'HGETALL',
|
|
132
|
+
RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
longest_rem_ttl = 0
|
|
138
|
+
readers = live_read_locks.each_with_index.map do |(acquirer_id, expiration), index|
|
|
139
|
+
rem_ttl = (expiration - now).ceil
|
|
140
|
+
longest_rem_ttl = rem_ttl if rem_ttl > longest_rem_ttl
|
|
141
|
+
|
|
142
|
+
# @type var reader: Hash[String,String|Float|Integer|nil]
|
|
143
|
+
reader = read_locks_data[index] || {}
|
|
144
|
+
reader['acq_id'] = acquirer_id
|
|
145
|
+
reader['hst_id'] ||= RedisQueuedLocks::Resource.host_identifier_from_acquirer(acquirer_id)
|
|
146
|
+
reader['ts'] = Float(reader['ts']) if reader['ts']
|
|
147
|
+
reader['ini_ttl'] = Integer(reader['ini_ttl']) if reader['ini_ttl']
|
|
148
|
+
reader['rem_ttl'] = rem_ttl
|
|
149
|
+
reader['spc_cnt'] = Integer(reader['spc_cnt']) if reader['spc_cnt']
|
|
150
|
+
reader['l_spc_ts'] = Float(reader['l_spc_ts']) if reader['l_spc_ts']
|
|
151
|
+
reader['spc_ext_ttl'] = Integer(reader['spc_ext_ttl']) if reader['spc_ext_ttl']
|
|
152
|
+
reader['l_spc_ext_ini_ttl'] =
|
|
153
|
+
Integer(reader['l_spc_ext_ini_ttl']) if reader['l_spc_ext_ini_ttl']
|
|
154
|
+
reader['l_spc_ext_ts'] = Float(reader['l_spc_ext_ts']) if reader['l_spc_ext_ts']
|
|
155
|
+
reader
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
'lock_key' => lock_key,
|
|
160
|
+
'rw_mode' => 'read',
|
|
161
|
+
'rem_ttl' => longest_rem_ttl,
|
|
162
|
+
'readers' => readers
|
|
163
|
+
}
|
|
164
|
+
end
|
|
165
|
+
# rubocop:enable Metrics/MethodLength
|
|
76
166
|
end
|
|
77
167
|
end
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
7
7
|
class << self
|
|
8
8
|
# NOTE: Lock Series PoC
|
|
9
|
+
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
9
10
|
# @api private
|
|
10
11
|
# @since 1.16.1
|
|
12
|
+
# @version 1.18.0
|
|
11
13
|
def lock_series_obtained( # steep:ignore
|
|
12
14
|
instrumenter,
|
|
13
15
|
instr_sampled,
|
|
14
16
|
lock_keys,
|
|
17
|
+
rw_mode,
|
|
15
18
|
ttl,
|
|
16
19
|
acq_id,
|
|
17
20
|
hst_id,
|
|
@@ -21,17 +24,20 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
|
21
24
|
)
|
|
22
25
|
return unless instr_sampled
|
|
23
26
|
instrumenter.notify('redis_queued_locks.lock_series_obtained', {
|
|
24
|
-
lock_keys:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
27
|
+
lock_keys:, rw_mode:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
25
28
|
}) rescue nil
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
# NOTE: Lock Series PoC
|
|
32
|
+
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
29
33
|
# @api private
|
|
30
34
|
# @since 1.16.1
|
|
35
|
+
# @version 1.18.0
|
|
31
36
|
def lock_series_hold_and_release( # steep:ignore
|
|
32
37
|
instrumenter,
|
|
33
38
|
instr_sampled,
|
|
34
39
|
lock_keys,
|
|
40
|
+
rw_mode,
|
|
35
41
|
ttl,
|
|
36
42
|
acq_id,
|
|
37
43
|
hst_id,
|
|
@@ -42,7 +48,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
|
|
|
42
48
|
)
|
|
43
49
|
return unless instr_sampled
|
|
44
50
|
instrumenter.notify('redis_queued_locks.lock_series_hold_and_release', {
|
|
45
|
-
lock_keys:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
51
|
+
lock_keys:, rw_mode:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
|
|
46
52
|
}) # rescue nil
|
|
47
53
|
end
|
|
48
54
|
end
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
7
7
|
class << self
|
|
8
8
|
# NOTE: Lock Series PoC
|
|
9
|
+
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
9
10
|
# @api private
|
|
10
11
|
# @since 1.16.0
|
|
12
|
+
# @version 1.18.0
|
|
11
13
|
def start_lock_series_obtaining( # steep:ignore
|
|
12
14
|
logger,
|
|
13
15
|
log_sampled,
|
|
@@ -15,23 +17,27 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
15
17
|
queue_ttl,
|
|
16
18
|
acquirer_id,
|
|
17
19
|
host_id,
|
|
18
|
-
access_strategy
|
|
20
|
+
access_strategy,
|
|
21
|
+
rw_mode
|
|
19
22
|
)
|
|
20
23
|
return unless log_sampled
|
|
21
24
|
|
|
22
25
|
logger.debug do
|
|
23
26
|
"[redis_queued_locks.start_lock_series_obtaining] " \
|
|
24
|
-
"lock_keys => '#{lock_keys.inspect}'" \
|
|
27
|
+
"lock_keys => '#{lock_keys.inspect}' " \
|
|
25
28
|
"queue_ttl => #{queue_ttl} " \
|
|
26
29
|
"acq_id => '#{acquirer_id}' " \
|
|
27
30
|
"hst_id => '#{host_id}' " \
|
|
28
|
-
"acs_strat => '#{access_strategy}'"
|
|
31
|
+
"acs_strat => '#{access_strategy}' " \
|
|
32
|
+
"rw_mode => '#{rw_mode}'"
|
|
29
33
|
end # rescue nil
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
# NOTE: Lock Series PoC
|
|
37
|
+
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
33
38
|
# @api private
|
|
34
39
|
# @since 1.16.0
|
|
40
|
+
# @version 1.18.0
|
|
35
41
|
def lock_series_obtained( # steep:ignore
|
|
36
42
|
logger,
|
|
37
43
|
log_sampled,
|
|
@@ -40,7 +46,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
40
46
|
acquirer_id,
|
|
41
47
|
host_id,
|
|
42
48
|
acq_time,
|
|
43
|
-
access_strategy
|
|
49
|
+
access_strategy,
|
|
50
|
+
rw_mode
|
|
44
51
|
)
|
|
45
52
|
return unless log_sampled
|
|
46
53
|
|
|
@@ -51,13 +58,16 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
51
58
|
"acq_id => '#{acquirer_id}' " \
|
|
52
59
|
"hst_id => '#{host_id}' " \
|
|
53
60
|
"acs_strat => '#{access_strategy}' " \
|
|
61
|
+
"rw_mode => '#{rw_mode}' " \
|
|
54
62
|
"acq_time => #{acq_time} (ms)"
|
|
55
63
|
end # rescue nil
|
|
56
64
|
end
|
|
57
65
|
|
|
58
66
|
# NOTE: Lock Series PoC
|
|
67
|
+
# @param rw_mode [Symbol] The mode of the lock series (`:read` or `:write`).
|
|
59
68
|
# @api private
|
|
60
69
|
# @since 1.16.0
|
|
70
|
+
# @version 1.18.0
|
|
61
71
|
def expire_lock_series( # steep:ignore
|
|
62
72
|
logger,
|
|
63
73
|
log_sampled,
|
|
@@ -65,7 +75,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
65
75
|
queue_ttl,
|
|
66
76
|
acquirer_id,
|
|
67
77
|
host_id,
|
|
68
|
-
access_strategy
|
|
78
|
+
access_strategy,
|
|
79
|
+
rw_mode
|
|
69
80
|
)
|
|
70
81
|
return unless log_sampled
|
|
71
82
|
|
|
@@ -75,7 +86,8 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
|
|
|
75
86
|
"queue_ttl => #{queue_ttl} " \
|
|
76
87
|
"acq_id => '#{acquirer_id}' " \
|
|
77
88
|
"hst_id => '#{host_id}' " \
|
|
78
|
-
"acs_strat => '#{access_strategy}'"
|
|
89
|
+
"acs_strat => '#{access_strategy}' " \
|
|
90
|
+
"rw_mode => '#{rw_mode}'"
|
|
79
91
|
end rescue nil
|
|
80
92
|
end
|
|
81
93
|
end
|
|
@@ -14,7 +14,7 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
14
14
|
class << self
|
|
15
15
|
# @api private
|
|
16
16
|
# @since 1.16.0
|
|
17
|
-
# @version 1.
|
|
17
|
+
# @version 1.18.0
|
|
18
18
|
def lock_series_poc( # steep:ignore
|
|
19
19
|
redis,
|
|
20
20
|
lock_names,
|
|
@@ -52,6 +52,9 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
52
52
|
instr_sample_this:,
|
|
53
53
|
&block
|
|
54
54
|
)
|
|
55
|
+
# NOTE: (RW) the lock mode is the same for all locks of the series
|
|
56
|
+
# (see RedisQueuedLocks::Acquirer::AcquireLock for read_write_mode validations);
|
|
57
|
+
|
|
55
58
|
case meta
|
|
56
59
|
when Hash, NilClass then nil
|
|
57
60
|
else
|
|
@@ -72,13 +75,14 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
72
75
|
key == 'spc_cnt' ||
|
|
73
76
|
key == 'l_spc_ext_ini_ttl' ||
|
|
74
77
|
key == 'l_spc_ext_ts' ||
|
|
75
|
-
key == 'l_spc_ts'
|
|
78
|
+
key == 'l_spc_ts' ||
|
|
79
|
+
key == 'rw_mode'
|
|
76
80
|
end)
|
|
77
81
|
raise(
|
|
78
82
|
RedisQueuedLocks::ArgumentError,
|
|
79
83
|
'`:meta` keys can not overlap reserved lock data keys ' \
|
|
80
84
|
'"acq_id", "hst_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
|
|
81
|
-
'"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts"'
|
|
85
|
+
'"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts", "rw_mode"'
|
|
82
86
|
)
|
|
83
87
|
end
|
|
84
88
|
|
|
@@ -128,13 +132,16 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
128
132
|
|
|
129
133
|
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.start_lock_series_obtaining( # steep:ignore
|
|
130
134
|
logger, log_sampled, lock_keys_for_instrumentation,
|
|
131
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
|
|
135
|
+
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
|
|
136
|
+
read_write_mode
|
|
132
137
|
)
|
|
133
138
|
|
|
134
139
|
acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
135
140
|
successfully_acquired_locks = [] # steep:ignore
|
|
136
141
|
failed_locks_and_errors = {} # steep:ignore
|
|
137
142
|
failed_on_lock = nil
|
|
143
|
+
# NOTE: locks of the failed series are released once (exceptional or not)
|
|
144
|
+
is_failed_series_released = false
|
|
138
145
|
|
|
139
146
|
lock_acquirement_operations.map do |lock_operation_options|
|
|
140
147
|
result =
|
|
@@ -179,17 +186,11 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
179
186
|
RedisQueuedLocks::LockAcquirementTimeoutError,
|
|
180
187
|
RedisQueuedLocks::LockAcquirementRetryLimitError,
|
|
181
188
|
RedisQueuedLocks::ConflictLockObtainError => error
|
|
182
|
-
if
|
|
183
|
-
|
|
184
|
-
successfully_acquired_locks
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
conn.multi(watch: [lock_key]) do |transact|
|
|
188
|
-
transact.call('DEL', lock_key)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
189
|
+
# NOTE: release all previously acquired locks if any next lock is already locked
|
|
190
|
+
release_lock_series( # steep:ignore
|
|
191
|
+
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
192
|
+
)
|
|
193
|
+
is_failed_series_released = true
|
|
193
194
|
|
|
194
195
|
raise(error) if raise_errors
|
|
195
196
|
end
|
|
@@ -203,6 +204,13 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
203
204
|
else
|
|
204
205
|
failed_on_lock = lock_operation_options[:lock_name]
|
|
205
206
|
failed_locks_and_errors[lock_operation_options[:lock_name]] = result
|
|
207
|
+
# NOTE: release all previously acquired locks (non-exceptional failure: raise_errors: false)
|
|
208
|
+
unless is_failed_series_released
|
|
209
|
+
release_lock_series( # steep:ignore
|
|
210
|
+
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
211
|
+
)
|
|
212
|
+
is_failed_series_released = true
|
|
213
|
+
end
|
|
206
214
|
break
|
|
207
215
|
end
|
|
208
216
|
end
|
|
@@ -210,16 +218,16 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
210
218
|
if (successfully_acquired_locks.size == lock_names.size && (successfully_acquired_locks.all? { |res| res[:ok] }))
|
|
211
219
|
acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
|
|
212
220
|
acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
|
|
213
|
-
ts = Time.now.
|
|
221
|
+
ts = Time.now.to_f
|
|
214
222
|
|
|
215
223
|
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.lock_series_obtained( # steep:ignore
|
|
216
224
|
logger, log_sampled, lock_keys_for_instrumentation,
|
|
217
225
|
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
|
|
218
|
-
acq_time, access_strategy
|
|
226
|
+
acq_time, access_strategy, read_write_mode
|
|
219
227
|
)
|
|
220
228
|
|
|
221
229
|
RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_obtained( # steep:ignore
|
|
222
|
-
instrumenter, instr_sampled, lock_keys_for_instrumentation,
|
|
230
|
+
instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode,
|
|
223
231
|
ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time, instrument
|
|
224
232
|
)
|
|
225
233
|
|
|
@@ -238,6 +246,11 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
238
246
|
redis,
|
|
239
247
|
logger,
|
|
240
248
|
lock_keys_for_instrumentation.last,
|
|
249
|
+
RedisQueuedLocks::Resource.prepare_lock_readers(lock_names.last),
|
|
250
|
+
RedisQueuedLocks::Resource.prepare_read_lock_key(
|
|
251
|
+
lock_names.last, acquirer_id_for_instrumentation
|
|
252
|
+
),
|
|
253
|
+
read_write_mode,
|
|
241
254
|
acquirer_id_for_instrumentation,
|
|
242
255
|
host_id_for_instrumentation,
|
|
243
256
|
access_strategy,
|
|
@@ -257,14 +270,9 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
257
270
|
|
|
258
271
|
# expire locks manually
|
|
259
272
|
if block_given?
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
lock_keys_for_instrumentation.each do |lock_key|
|
|
264
|
-
transaction.call('EXPIRE', lock_key, '0')
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
end
|
|
273
|
+
release_lock_series( # steep:ignore
|
|
274
|
+
redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
|
|
275
|
+
)
|
|
268
276
|
is_lock_manually_released = true
|
|
269
277
|
end
|
|
270
278
|
|
|
@@ -274,13 +282,15 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
274
282
|
|
|
275
283
|
RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.expire_lock_series( # steep:ignore
|
|
276
284
|
logger, log_sampled, lock_keys_for_instrumentation,
|
|
277
|
-
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
|
|
285
|
+
queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
|
|
286
|
+
read_write_mode
|
|
278
287
|
) if is_lock_manually_released
|
|
279
288
|
|
|
280
289
|
RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_hold_and_release( # steep:ignore
|
|
281
290
|
instrumenter,
|
|
282
291
|
instr_sampled,
|
|
283
292
|
lock_keys_for_instrumentation,
|
|
293
|
+
read_write_mode,
|
|
284
294
|
ttl,
|
|
285
295
|
acquirer_id_for_instrumentation,
|
|
286
296
|
host_id_for_instrumentation,
|
|
@@ -321,6 +331,95 @@ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
|
|
|
321
331
|
}
|
|
322
332
|
end
|
|
323
333
|
end
|
|
334
|
+
|
|
335
|
+
private
|
|
336
|
+
|
|
337
|
+
# Releases locks of the series that are obtained by the series itself:
|
|
338
|
+
# - reentrant locks (already obtained by the outer logic of the current acquirer) are kept;
|
|
339
|
+
# - only locks of the current acquirer are released (owner check);
|
|
340
|
+
#
|
|
341
|
+
# @param redis [RedisClient]
|
|
342
|
+
# @param read_write_mode [Symbol]
|
|
343
|
+
# @param successfully_acquired_locks [Array<Hash<Symbol,Any>>]
|
|
344
|
+
# @param acquirer_id [String]
|
|
345
|
+
# @return [void]
|
|
346
|
+
#
|
|
347
|
+
# @api private
|
|
348
|
+
# @since 1.18.0
|
|
349
|
+
def release_lock_series(redis, read_write_mode, successfully_acquired_locks, acquirer_id) # steep:ignore
|
|
350
|
+
lock_names = obtained_lock_names(successfully_acquired_locks) # steep:ignore
|
|
351
|
+
|
|
352
|
+
if read_write_mode == :read
|
|
353
|
+
release_read_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
354
|
+
else
|
|
355
|
+
release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Releases write locks of the given acquirer (compare-and-delete: the lock can be expired
|
|
360
|
+
# and obtained by another acquirer when the series logic exceeds the lock ttl).
|
|
361
|
+
#
|
|
362
|
+
# @param redis [RedisClient]
|
|
363
|
+
# @param lock_names [Array<String>]
|
|
364
|
+
# @param acquirer_id [String]
|
|
365
|
+
# @return [void]
|
|
366
|
+
#
|
|
367
|
+
# @api private
|
|
368
|
+
# @since 1.18.0
|
|
369
|
+
def release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
370
|
+
return if lock_names.empty?
|
|
371
|
+
|
|
372
|
+
redis.with do |conn|
|
|
373
|
+
lock_names.each do |lock_name|
|
|
374
|
+
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
|
375
|
+
conn.multi(watch: [lock_key]) do |transaction|
|
|
376
|
+
if conn.call('HGET', lock_key, 'acq_id') == acquirer_id
|
|
377
|
+
transaction.call('DEL', lock_key)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Locks of the series that are obtained by the series itself (reentrant locks are obtained
|
|
385
|
+
# by the outer logic of the current acquirer and should be released by it).
|
|
386
|
+
#
|
|
387
|
+
# @param successfully_acquired_locks [Array<Hash<Symbol,Any>>]
|
|
388
|
+
# @return [Array<String>]
|
|
389
|
+
#
|
|
390
|
+
# @api private
|
|
391
|
+
# @since 1.18.0
|
|
392
|
+
def obtained_lock_names(successfully_acquired_locks) # steep:ignore
|
|
393
|
+
successfully_acquired_locks.filter_map do |operation_result|
|
|
394
|
+
operation_result[:lock_name] if operation_result[:result][:process] == :lock_obtaining
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Releases read locks of the given acquirer (other readers are not affected).
|
|
399
|
+
#
|
|
400
|
+
# @param redis [RedisClient]
|
|
401
|
+
# @param lock_names [Array<String>]
|
|
402
|
+
# @param acquirer_id [String]
|
|
403
|
+
# @return [void]
|
|
404
|
+
#
|
|
405
|
+
# @api private
|
|
406
|
+
# @since 1.18.0
|
|
407
|
+
def release_read_lock_series(redis, lock_names, acquirer_id) # steep:ignore
|
|
408
|
+
return if lock_names.empty?
|
|
409
|
+
|
|
410
|
+
redis.with do |conn|
|
|
411
|
+
conn.multi do |transaction|
|
|
412
|
+
lock_names.each do |lock_name|
|
|
413
|
+
transaction.call(
|
|
414
|
+
'ZREM', RedisQueuedLocks::Resource.prepare_lock_readers(lock_name), acquirer_id
|
|
415
|
+
)
|
|
416
|
+
transaction.call(
|
|
417
|
+
'DEL', RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
|
|
418
|
+
)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
324
423
|
end
|
|
325
424
|
end
|
|
326
425
|
# rubocop:enable all
|