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
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Read/Write locks (`read_write_mode`):
|
|
4
|
+
# - `:write` lock is exclusive: it is obtained when there is no write lock and no live read lock;
|
|
5
|
+
# - `:read` lock is shared: it is obtained when there is no write lock;
|
|
6
|
+
# - read locks are stored in the readers registry: a sorted set where the member is an acquirer
|
|
7
|
+
# id and the score is the read lock expiration time (redis server time, in milliseconds);
|
|
8
|
+
# - each read lock has its own data hash (the same structure as the write lock: acquirer, host,
|
|
9
|
+
# timestamp, ttl, meta, reentrant lock data) with the read lock ttl; the readers registry is
|
|
10
|
+
# the only source of truth for the read lock existence (the data hash is a data carrier only);
|
|
11
|
+
# - mutual exclusion relies on the optimistic transactions only (WATCH/MULTI):
|
|
12
|
+
# - readers WATCH the write lock key: a new write lock invalidates the reader's transaction;
|
|
13
|
+
# - writers WATCH the write lock key and the readers registry: a new read lock invalidates
|
|
14
|
+
# the writer's transaction;
|
|
15
|
+
# - readers do not WATCH the readers registry so they never invalidate each other;
|
|
16
|
+
# - lock queues are responsible for the order only (`:queued` access strategy,
|
|
17
|
+
# FIFO between modes):
|
|
18
|
+
# - write lock request should be the first in the write lock queue (the classic lock queue)
|
|
19
|
+
# and should wait for all earlier read lock requests;
|
|
20
|
+
# - read lock request should wait for all earlier write lock requests;
|
|
21
|
+
# - `:random` access strategy ignores both queues (no ordering between modes, so
|
|
22
|
+
# a continuous flow of readers can starve writers);
|
|
23
|
+
#
|
|
3
24
|
# @api private
|
|
4
25
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
26
|
+
# @version 1.18.0
|
|
6
27
|
# rubocop:disable Metrics/ModuleLength
|
|
7
28
|
module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
8
29
|
require_relative 'try_to_lock/log_visitor'
|
|
@@ -20,10 +41,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
20
41
|
# @param logger [::Logger,#debug]
|
|
21
42
|
# @param log_lock_try [Boolean]
|
|
22
43
|
# @param lock_key [String]
|
|
23
|
-
# @param read_write_mode [Symbol]
|
|
24
|
-
# @param lock_key_queue [String]
|
|
25
|
-
# @param read_lock_key_queue [String]
|
|
26
|
-
# @param
|
|
44
|
+
# @param read_write_mode [Symbol] `:read` or `:write`
|
|
45
|
+
# @param lock_key_queue [String] Queue of write lock requests.
|
|
46
|
+
# @param read_lock_key_queue [String] Queue of read lock requests.
|
|
47
|
+
# @param lock_readers_key [String] Registry of read lock holders.
|
|
48
|
+
# @param read_lock_key [String] Read lock data of the current acquirer.
|
|
27
49
|
# @param acquirer_id [String]
|
|
28
50
|
# @param host_id [String]
|
|
29
51
|
# @param acquirer_position [Numeric]
|
|
@@ -39,7 +61,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
39
61
|
#
|
|
40
62
|
# @api private
|
|
41
63
|
# @since 1.0.0
|
|
42
|
-
# @version 1.
|
|
64
|
+
# @version 1.18.0
|
|
43
65
|
# rubocop:disable Metrics/MethodLength
|
|
44
66
|
def try_to_lock(
|
|
45
67
|
redis,
|
|
@@ -49,7 +71,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
49
71
|
read_write_mode,
|
|
50
72
|
lock_key_queue,
|
|
51
73
|
read_lock_key_queue,
|
|
52
|
-
|
|
74
|
+
lock_readers_key,
|
|
75
|
+
read_lock_key,
|
|
53
76
|
acquirer_id,
|
|
54
77
|
host_id,
|
|
55
78
|
acquirer_position,
|
|
@@ -69,44 +92,89 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
69
92
|
timestamp = nil
|
|
70
93
|
# @type var spc_processed_timestamp: Float?
|
|
71
94
|
spc_processed_timestamp = nil
|
|
95
|
+
# Step RW: the mode of the lock that is really held by the acquirer after the attempt
|
|
96
|
+
# (the requested mode or the mode of the already obtained lock for same-process conflicts);
|
|
97
|
+
# @type var held_rw_mode: Symbol
|
|
98
|
+
held_rw_mode = read_write_mode
|
|
99
|
+
# Step RW: read lock request flag
|
|
100
|
+
read_mode = (read_write_mode == :read)
|
|
72
101
|
|
|
73
102
|
LogVisitor.start(
|
|
74
103
|
logger, log_sampled, log_lock_try, lock_key,
|
|
75
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
104
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
76
105
|
)
|
|
77
106
|
|
|
78
107
|
# Step X: start to work with lock acquiring
|
|
79
108
|
result = redis.with do |rconn|
|
|
80
109
|
LogVisitor.rconn_fetched(
|
|
81
110
|
logger, log_sampled, log_lock_try, lock_key,
|
|
82
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
111
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
83
112
|
)
|
|
84
113
|
|
|
85
114
|
# Step 0:
|
|
86
115
|
# watch the lock key changes (and discard acquirement if lock is already
|
|
87
116
|
# obtained by another acquirer during the current lock acquiremntt)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
# Step 0 (RW):
|
|
118
|
+
# - write lock request watches the readers registry too (and discards acquirement if
|
|
119
|
+
# a read lock is obtained during the current lock acquirement);
|
|
120
|
+
# - read lock request does not watch the readers registry (readers should not invalidate
|
|
121
|
+
# transactions of each other);
|
|
122
|
+
rconn.multi(watch: (read_mode ? [lock_key] : [lock_key, lock_readers_key])) do |transact|
|
|
123
|
+
# Step 0.1: fetch the current lock state in one round trip:
|
|
124
|
+
# - current write lock obtainer;
|
|
125
|
+
# - redis server time (read lock expiration is calculated in redis time);
|
|
126
|
+
# - read lock expiration of the current acquirer;
|
|
127
|
+
# - the longest read lock (the last one in the readers registry);
|
|
128
|
+
# @type var lock_state: [String?, Array[String], Float?, Array[[String, Float]]]
|
|
129
|
+
lock_state = rconn.pipelined do |pipeline|
|
|
130
|
+
pipeline.call('HGET', lock_key, 'acq_id')
|
|
131
|
+
pipeline.call('TIME')
|
|
132
|
+
pipeline.call('ZSCORE', lock_readers_key, acquirer_id)
|
|
133
|
+
pipeline.call('ZRANGE', lock_readers_key, '-1', '-1', 'WITHSCORES')
|
|
134
|
+
end
|
|
135
|
+
current_lock_obtainer = lock_state[0]
|
|
136
|
+
redis_time = RedisQueuedLocks::Resource.redis_time_ms(lock_state[1])
|
|
137
|
+
own_read_lock_expiration = lock_state[2]
|
|
138
|
+
longest_read_lock = lock_state[3].first
|
|
139
|
+
# NOTE:
|
|
140
|
+
# - expired read locks are ignored (they can live in the registry till the cleanup);
|
|
141
|
+
# - nil expiration (no read lock) is converted to 0.0 (always expired);
|
|
142
|
+
read_locked_by_acquirer = own_read_lock_expiration.to_f > redis_time
|
|
143
|
+
read_locked = longest_read_lock != nil && longest_read_lock[1] > redis_time
|
|
144
|
+
|
|
145
|
+
# SP-Conflict status PREPARING: the mode of the lock (already obtained by the current
|
|
146
|
+
# acquirer) that the current lock request conflicts with;
|
|
147
|
+
sp_conflict_rw_mode =
|
|
148
|
+
if current_lock_obtainer != nil && acquirer_id == current_lock_obtainer
|
|
149
|
+
:write
|
|
150
|
+
elsif read_locked_by_acquirer
|
|
151
|
+
:read
|
|
152
|
+
end
|
|
91
153
|
# SP-Conflict status PREPARING: status flag variable
|
|
92
154
|
sp_conflict_status = nil
|
|
93
155
|
|
|
94
156
|
# SP-Conflict Step X1: calculate the current deadlock status
|
|
95
|
-
if
|
|
157
|
+
if sp_conflict_rw_mode != nil
|
|
96
158
|
LogVisitor.same_process_conflict_detected(
|
|
97
159
|
logger, log_sampled, log_lock_try, lock_key,
|
|
98
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
160
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
99
161
|
)
|
|
100
162
|
|
|
163
|
+
# NOTE:
|
|
164
|
+
# read-to-write lock upgrade can not "work through": the read lock is not exclusive
|
|
165
|
+
# so the "work through" logic will work without an exclusive lock;
|
|
166
|
+
lock_upgrade = !read_mode && sp_conflict_rw_mode == :read
|
|
167
|
+
|
|
101
168
|
# SP-Conflict Step X2: self-process dead lock moment started.
|
|
102
169
|
# SP-Conflict CHECK (Step CHECK): check chosen strategy and flag the current status
|
|
103
170
|
case conflict_strategy
|
|
104
171
|
when :work_through
|
|
105
172
|
# <SP-Conflict Moment>: work through => exit
|
|
106
|
-
sp_conflict_status = :conflict_work_through
|
|
173
|
+
sp_conflict_status = lock_upgrade ? :conflict_lock_upgrade : :conflict_work_through
|
|
107
174
|
when :extendable_work_through
|
|
108
175
|
# <SP-Conflict Moment>: extendable_work_through => extend the lock pttl and exit
|
|
109
|
-
sp_conflict_status =
|
|
176
|
+
sp_conflict_status =
|
|
177
|
+
lock_upgrade ? :conflict_lock_upgrade : :extendable_conflict_work_through
|
|
110
178
|
when :wait_for_lock
|
|
111
179
|
# <SP-Conflict Moment>: wait_for_lock => obtain a lock in classic way
|
|
112
180
|
sp_conflict_status = :conflict_wait_for_lock
|
|
@@ -122,7 +190,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
122
190
|
end
|
|
123
191
|
LogVisitor.same_process_conflict_analyzed(
|
|
124
192
|
logger, log_sampled, log_lock_try, lock_key,
|
|
125
|
-
queue_ttl, acquirer_id, host_id, access_strategy, sp_conflict_status
|
|
193
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, sp_conflict_status
|
|
126
194
|
)
|
|
127
195
|
end
|
|
128
196
|
|
|
@@ -131,66 +199,113 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
131
199
|
# SP-Conflict-Step FINAL (SPCF): extend the lock and work through
|
|
132
200
|
# - extend the lock ttl;
|
|
133
201
|
# - store extensions in lock metadata;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
202
|
+
if sp_conflict_rw_mode == :write
|
|
203
|
+
held_rw_mode = :write
|
|
204
|
+
|
|
205
|
+
# SPCF Step 1: extend the lock pttl
|
|
206
|
+
# - [REDIS RESULT]: in normal cases should return the last script command value
|
|
207
|
+
# - for the current script should return:
|
|
208
|
+
# <1> => timeout was set;
|
|
209
|
+
# <0> => timeount was not set;
|
|
210
|
+
transact.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, ttl)
|
|
211
|
+
# SPCF Step <Meta>: store conflict-state additionals in lock metadata:
|
|
212
|
+
# SPCF Step 2: (lock meta-data)
|
|
213
|
+
# - add the added ttl to reflect the real lock TTL in info;
|
|
214
|
+
# - [REDIS RESULT]: in normal cases should return the value of <ttl> key
|
|
215
|
+
# - for non-existent key value starts from <0> (zero)
|
|
216
|
+
transact.call('HINCRBY', lock_key, 'spc_ext_ttl', ttl)
|
|
217
|
+
# SPCF Step 3: (lock meta-data)
|
|
218
|
+
# - increment the conflcit counter in order to remember
|
|
219
|
+
# how many times dead lock happened;
|
|
220
|
+
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
221
|
+
# - for non-existent key starts from 0
|
|
222
|
+
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
223
|
+
# SPCF Step 4: (lock meta-data)
|
|
224
|
+
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
225
|
+
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
226
|
+
# were added/changed;
|
|
227
|
+
transact.call(
|
|
228
|
+
'HSET',
|
|
229
|
+
lock_key,
|
|
230
|
+
'l_spc_ext_ts', spc_processed_timestamp = Time.now.to_f,
|
|
231
|
+
'l_spc_ext_ini_ttl', ttl
|
|
232
|
+
)
|
|
233
|
+
else
|
|
234
|
+
held_rw_mode = :read
|
|
235
|
+
# SPCF Step 1 (read lock): extend the read lock of the current acquirer
|
|
236
|
+
# - [REDIS RESULT]: the new read lock expiration time
|
|
237
|
+
# (or nil if the read lock has been removed from the registry);
|
|
238
|
+
transact.call('ZADD', lock_readers_key, 'XX', 'INCR', ttl, acquirer_id)
|
|
239
|
+
# SPCF Step 2 (read lock): the registry should live as long as the longest read lock
|
|
240
|
+
# - NX + GT => sets the registry TTL to max(current TTL, extended read lock TTL);
|
|
241
|
+
extended_read_lock_ttl = (own_read_lock_expiration.to_f + ttl.to_i - redis_time).ceil
|
|
242
|
+
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'NX')
|
|
243
|
+
transact.call('PEXPIRE', lock_readers_key, extended_read_lock_ttl, 'GT')
|
|
244
|
+
# SPCF Step 3 (read lock data): the same reentrant lock data as for the write lock
|
|
245
|
+
transact.call('HINCRBY', read_lock_key, 'spc_ext_ttl', ttl)
|
|
246
|
+
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
247
|
+
transact.call(
|
|
248
|
+
'HSET',
|
|
249
|
+
read_lock_key,
|
|
250
|
+
'l_spc_ext_ts', spc_processed_timestamp = Time.now.to_f,
|
|
251
|
+
'l_spc_ext_ini_ttl', ttl
|
|
252
|
+
)
|
|
253
|
+
# SPCF Step 4 (read lock data): the read lock data lives as long as the read lock
|
|
254
|
+
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'NX')
|
|
255
|
+
transact.call('PEXPIRE', read_lock_key, extended_read_lock_ttl, 'GT')
|
|
256
|
+
end
|
|
162
257
|
inter_result = :extendable_conflict_work_through
|
|
163
258
|
|
|
164
259
|
# @type var spc_processed_timestamp: Float
|
|
165
260
|
LogVisitor.reentrant_lock__extend_and_work_through(
|
|
166
261
|
logger, log_sampled, log_lock_try, lock_key,
|
|
167
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
262
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
168
263
|
sp_conflict_status, ttl, spc_processed_timestamp
|
|
169
264
|
)
|
|
170
265
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
171
266
|
elsif sp_conflict_status == :conflict_work_through
|
|
172
267
|
inter_result = :conflict_work_through
|
|
173
268
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
269
|
+
if sp_conflict_rw_mode == :write
|
|
270
|
+
held_rw_mode = :write
|
|
271
|
+
|
|
272
|
+
# SPCF Step X: (lock meta-data)
|
|
273
|
+
# - increment the conflcit counter in order to remember
|
|
274
|
+
# how many times dead lock happened;
|
|
275
|
+
# - [REDIS RESULT]: in normal cases should return the value of <spc_cnt> key
|
|
276
|
+
# - for non-existent key starts from 0
|
|
277
|
+
transact.call('HINCRBY', lock_key, 'spc_cnt', 1)
|
|
278
|
+
# SPCF Step 4: (lock meta-data)
|
|
279
|
+
# - remember the last ext-timestamp and the last ext-initial ttl;
|
|
280
|
+
# - [REDIS RESULT]: for normal cases should return the number of fields
|
|
281
|
+
# were added/changed;
|
|
282
|
+
transact.call(
|
|
283
|
+
'HSET',
|
|
284
|
+
lock_key,
|
|
285
|
+
'l_spc_ts', spc_processed_timestamp = Time.now.to_f
|
|
286
|
+
)
|
|
287
|
+
else
|
|
288
|
+
held_rw_mode = :read
|
|
289
|
+
|
|
290
|
+
# SPCF Step X (read lock data): the same reentrant lock data as for the write lock
|
|
291
|
+
transact.call('HINCRBY', read_lock_key, 'spc_cnt', 1)
|
|
292
|
+
transact.call(
|
|
293
|
+
'HSET', read_lock_key, 'l_spc_ts', spc_processed_timestamp = Time.now.to_f
|
|
294
|
+
)
|
|
295
|
+
# SPCF Step X (read lock data): (NX) the read lock data should not outlive the read lock
|
|
296
|
+
# (it is the case when the read lock data is dropped during the read lock life);
|
|
297
|
+
transact.call(
|
|
298
|
+
'PEXPIRE',
|
|
299
|
+
read_lock_key,
|
|
300
|
+
(own_read_lock_expiration.to_f - redis_time).ceil,
|
|
301
|
+
'NX'
|
|
302
|
+
)
|
|
303
|
+
end
|
|
189
304
|
|
|
190
305
|
# @type var spc_processed_timestamp: Float
|
|
191
306
|
LogVisitor.reentrant_lock__work_through(
|
|
192
307
|
logger, log_sampled, log_lock_try, lock_key,
|
|
193
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
308
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
194
309
|
sp_conflict_status, spc_processed_timestamp
|
|
195
310
|
)
|
|
196
311
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
@@ -201,16 +316,36 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
201
316
|
# @type var spc_processed_timestamp: Float
|
|
202
317
|
LogVisitor.single_process_lock_conflict__dead_lock(
|
|
203
318
|
logger, log_sampled, log_lock_try, lock_key,
|
|
204
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
319
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
320
|
+
sp_conflict_status, spc_processed_timestamp
|
|
321
|
+
)
|
|
322
|
+
# SP-Conflict-Step X2 (RW): read-to-write lock upgrade is not supported => exit and fail
|
|
323
|
+
elsif sp_conflict_status == :conflict_lock_upgrade
|
|
324
|
+
inter_result = :conflict_lock_upgrade
|
|
325
|
+
spc_processed_timestamp = Time.now.to_f
|
|
326
|
+
|
|
327
|
+
# @type var spc_processed_timestamp: Float
|
|
328
|
+
LogVisitor.single_process_lock_conflict__lock_upgrade(
|
|
329
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
330
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
205
331
|
sp_conflict_status, spc_processed_timestamp
|
|
206
332
|
)
|
|
207
333
|
# Reached the SP-Non-Conflict Mode (NOTE):
|
|
208
334
|
# - in other sp-conflict cases we are in <wait_for_lock> (non-conflict) status and should
|
|
209
335
|
# continue to work in classic way (next lines of code):
|
|
210
|
-
|
|
336
|
+
# Fast-Step X0: fail-fast check
|
|
337
|
+
# - write lock request: the lock is obtained by a writer or by readers;
|
|
338
|
+
# - read lock request: the lock is obtained by a writer (or by the current acquirer);
|
|
339
|
+
elsif fail_fast && (
|
|
340
|
+
current_lock_obtainer != nil || (read_mode ? read_locked_by_acquirer : read_locked)
|
|
341
|
+
)
|
|
211
342
|
# Fast-Step X1: lock is already obtained. fail fast leads to "no try".
|
|
212
343
|
inter_result = :fail_fast_no_try
|
|
213
344
|
else
|
|
345
|
+
# Step RW: request queue of the current mode and the queue of the opposite mode
|
|
346
|
+
request_queue = read_mode ? read_lock_key_queue : lock_key_queue
|
|
347
|
+
opposite_request_queue = read_mode ? lock_key_queue : read_lock_key_queue
|
|
348
|
+
|
|
214
349
|
# Step 1: add an acquirer to the lock acquirement queue
|
|
215
350
|
# NOTE:
|
|
216
351
|
# 'NX' means "Only add new elements. Don't update already existing elements."
|
|
@@ -218,97 +353,185 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
218
353
|
# 1. (enqueue) <<if you are already in the queue - do nothing and wait your time>>
|
|
219
354
|
# 2. (requeue) or <<add to the right pre-calculated position if you are
|
|
220
355
|
# not in the queue now>>;
|
|
221
|
-
rconn.call('ZADD',
|
|
356
|
+
rconn.call('ZADD', request_queue, 'NX', acquirer_position, acquirer_id)
|
|
222
357
|
|
|
223
358
|
LogVisitor.acq_added_to_queue(
|
|
224
359
|
logger, log_sampled, log_lock_try, lock_key,
|
|
225
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
360
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
226
361
|
)
|
|
227
362
|
|
|
228
|
-
# Step 2.1: drop expired acquirers from the lock
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
363
|
+
# Step 2.1: drop expired acquirers from the lock queues
|
|
364
|
+
# - (RW) both queues are cleared: an expired request of the opposite mode should not
|
|
365
|
+
# block the current request forever;
|
|
366
|
+
acquirer_dead_score = RedisQueuedLocks::Resource.acquirer_dead_score(queue_ttl)
|
|
367
|
+
rconn.pipelined do |pipeline|
|
|
368
|
+
pipeline.call('ZREMRANGEBYSCORE', request_queue, '-inf', acquirer_dead_score)
|
|
369
|
+
pipeline.call('ZREMRANGEBYSCORE', opposite_request_queue, '-inf', acquirer_dead_score)
|
|
370
|
+
end
|
|
235
371
|
|
|
236
372
|
LogVisitor.remove_expired_acqs(
|
|
237
373
|
logger, log_sampled, log_lock_try, lock_key,
|
|
238
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
374
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
239
375
|
)
|
|
240
376
|
|
|
241
377
|
# Step 3: get the actual acquirer waiting in the queue
|
|
242
|
-
|
|
378
|
+
# - (RW) and the first request of the opposite mode;
|
|
379
|
+
# @type var queue_heads: [Array[String], Array[[String, Float]]]
|
|
380
|
+
queue_heads = rconn.pipelined do |pipeline|
|
|
381
|
+
pipeline.call('ZRANGE', request_queue, '0', '0')
|
|
382
|
+
pipeline.call('ZRANGE', opposite_request_queue, '0', '0', 'WITHSCORES')
|
|
383
|
+
end
|
|
384
|
+
waiting_acquirer = queue_heads[0].first
|
|
385
|
+
opposite_waiting_request = queue_heads[1].first
|
|
243
386
|
|
|
244
387
|
LogVisitor.get_first_from_queue(
|
|
245
388
|
logger, log_sampled, log_lock_try, lock_key,
|
|
246
|
-
queue_ttl, acquirer_id, host_id, access_strategy, waiting_acquirer
|
|
389
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer
|
|
247
390
|
)
|
|
248
391
|
|
|
392
|
+
# Step 3.1 (RW): the opposite mode request that should obtain the lock earlier
|
|
393
|
+
# - FIFO between modes (`:queued` access strategy only);
|
|
394
|
+
# - equal positions are ordered by acquirer ids;
|
|
395
|
+
# @type var opposite_request_ahead: String?
|
|
396
|
+
opposite_request_ahead =
|
|
397
|
+
if access_strategy == :queued && opposite_waiting_request != nil &&
|
|
398
|
+
(opposite_waiting_request[1] < acquirer_position ||
|
|
399
|
+
(opposite_waiting_request[1] == acquirer_position &&
|
|
400
|
+
opposite_waiting_request[0] < acquirer_id))
|
|
401
|
+
opposite_waiting_request[0]
|
|
402
|
+
end
|
|
403
|
+
|
|
249
404
|
# Step PRE-4.x: check if the request time limit is reached
|
|
250
405
|
# (when the current try self-removes itself from queue (queue ttl has come))
|
|
251
406
|
if waiting_acquirer == nil
|
|
252
407
|
LogVisitor.exit__queue_ttl_reached(
|
|
253
408
|
logger, log_sampled, log_lock_try, lock_key,
|
|
254
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
409
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
255
410
|
)
|
|
256
411
|
|
|
257
412
|
inter_result = :dead_score_reached
|
|
258
413
|
# Step STRATEGY: check the stragegy and corresponding preventing factor
|
|
259
414
|
# Step STRATEGY (queued): check the actual acquirer: is it ours? are we aready to lock?
|
|
260
|
-
|
|
415
|
+
# - (RW) write lock requests are processed one by one;
|
|
416
|
+
# - (RW) read lock requests are processed in parallel;
|
|
417
|
+
elsif !read_mode && access_strategy == :queued && waiting_acquirer != acquirer_id
|
|
261
418
|
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
|
262
419
|
|
|
263
420
|
LogVisitor.exit__no_first(
|
|
264
421
|
logger, log_sampled, log_lock_try, lock_key,
|
|
265
|
-
queue_ttl, acquirer_id, host_id, access_strategy, waiting_acquirer,
|
|
266
|
-
|
|
422
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer,
|
|
423
|
+
# NOTE: lock data is extracted for logs only
|
|
424
|
+
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : {})
|
|
267
425
|
)
|
|
268
426
|
inter_result = :acquirer_is_not_first_in_queue
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
# Step 5: find the lock -> check if the our lock is already acquired
|
|
275
|
-
locked_by_acquirer = rconn.call('HGET', lock_key, 'acq_id')
|
|
276
|
-
|
|
277
|
-
if locked_by_acquirer
|
|
278
|
-
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
|
279
|
-
|
|
280
|
-
LogVisitor.exit__lock_still_obtained(
|
|
427
|
+
elsif opposite_request_ahead
|
|
428
|
+
# Step ROLLBACK 1.2 (RW): an earlier request of the opposite mode goes first. retry!
|
|
429
|
+
if read_mode
|
|
430
|
+
LogVisitor.exit__write_request_ahead(
|
|
281
431
|
logger, log_sampled, log_lock_try, lock_key,
|
|
282
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
283
|
-
|
|
284
|
-
rconn.call('HGETALL', lock_key).to_h
|
|
432
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
433
|
+
opposite_request_ahead
|
|
285
434
|
)
|
|
286
|
-
inter_result = :
|
|
435
|
+
inter_result = :write_request_is_ahead
|
|
287
436
|
else
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
# Step 6.1: remove our acquirer from waiting queue
|
|
291
|
-
transact.call('ZREM', lock_key_queue, acquirer_id)
|
|
292
|
-
|
|
293
|
-
# Step 6.2: acquire a lock and store an info about the acquirer and host
|
|
294
|
-
transact.call(
|
|
295
|
-
'HSET',
|
|
296
|
-
lock_key,
|
|
297
|
-
'acq_id', acquirer_id,
|
|
298
|
-
'hst_id', host_id,
|
|
299
|
-
'ts', timestamp = Time.now.to_f,
|
|
300
|
-
'ini_ttl', ttl,
|
|
301
|
-
*(meta.to_a if meta != nil) # steep:ignore
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
|
305
|
-
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
|
306
|
-
|
|
307
|
-
LogVisitor.obtain__free_to_acquire(
|
|
437
|
+
LogVisitor.exit__read_request_ahead(
|
|
308
438
|
logger, log_sampled, log_lock_try, lock_key,
|
|
309
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
439
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
440
|
+
opposite_request_ahead
|
|
310
441
|
)
|
|
442
|
+
inter_result = :read_request_is_ahead
|
|
311
443
|
end
|
|
444
|
+
# NOTE: our time has come! let's try to acquire the lock!
|
|
445
|
+
# Step 5: find the lock -> check if the our lock is already acquired
|
|
446
|
+
# - NOTE: the lock state is fetched after WATCH (step 0.1) so any lock state change
|
|
447
|
+
# invalidates the transaction;
|
|
448
|
+
elsif current_lock_obtainer
|
|
449
|
+
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
|
450
|
+
|
|
451
|
+
LogVisitor.exit__lock_still_obtained(
|
|
452
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
453
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
454
|
+
waiting_acquirer, current_lock_obtainer,
|
|
455
|
+
# NOTE: lock data is extracted for logs only
|
|
456
|
+
((log_sampled && log_lock_try) ? rconn.call('HGETALL', lock_key).to_h : {})
|
|
457
|
+
)
|
|
458
|
+
inter_result = :lock_is_still_acquired
|
|
459
|
+
elsif read_mode ? read_locked_by_acquirer : read_locked
|
|
460
|
+
# Step ROLLBACK 3 (RW): the lock is still obtained by readers. retry!
|
|
461
|
+
# - write lock request waits for all readers;
|
|
462
|
+
# - read lock request waits for its own read lock
|
|
463
|
+
# (`:wait_for_lock` conflict strategy);
|
|
464
|
+
|
|
465
|
+
LogVisitor.exit__read_lock_still_obtained(
|
|
466
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
467
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer
|
|
468
|
+
)
|
|
469
|
+
inter_result = :read_lock_is_still_acquired
|
|
470
|
+
elsif read_mode
|
|
471
|
+
# NOTE: required lock is free and ready to be acquired! acquire! (read lock)
|
|
472
|
+
|
|
473
|
+
# Step 6.1: remove our acquirer from waiting queue
|
|
474
|
+
transact.call('ZREM', request_queue, acquirer_id)
|
|
475
|
+
|
|
476
|
+
# Step 6.2 (RW): drop expired read locks from the readers registry
|
|
477
|
+
transact.call('ZREMRANGEBYSCORE', lock_readers_key, '-inf', "(#{redis_time}")
|
|
478
|
+
|
|
479
|
+
# Step 6.3 (RW): register the read lock (expiration time in redis time)
|
|
480
|
+
transact.call('ZADD', lock_readers_key, redis_time + ttl.to_i, acquirer_id)
|
|
481
|
+
|
|
482
|
+
# Step 6.4 (RW): the registry should live as long as the longest read lock
|
|
483
|
+
# - NX + GT => sets the registry TTL to max(current TTL, read lock TTL);
|
|
484
|
+
# - in order to prevent "infinite registries" after process crashes;
|
|
485
|
+
transact.call('PEXPIRE', lock_readers_key, ttl, 'NX')
|
|
486
|
+
transact.call('PEXPIRE', lock_readers_key, ttl, 'GT')
|
|
487
|
+
|
|
488
|
+
# Step 6.5 (RW): store the read lock data (the same structure as the write lock data)
|
|
489
|
+
# - drop the data of the expired read lock of the current acquirer (if any);
|
|
490
|
+
# - the read lock data lives as long as the read lock;
|
|
491
|
+
transact.call('DEL', read_lock_key)
|
|
492
|
+
transact.call(
|
|
493
|
+
'HSET',
|
|
494
|
+
read_lock_key,
|
|
495
|
+
'acq_id', acquirer_id,
|
|
496
|
+
'hst_id', host_id,
|
|
497
|
+
'ts', timestamp = Time.now.to_f,
|
|
498
|
+
'ini_ttl', ttl,
|
|
499
|
+
*(meta.to_a if meta != nil) # steep:ignore
|
|
500
|
+
)
|
|
501
|
+
transact.call('PEXPIRE', read_lock_key, ttl)
|
|
502
|
+
|
|
503
|
+
inter_result = :lock_obtaining
|
|
504
|
+
|
|
505
|
+
LogVisitor.obtain__free_to_acquire(
|
|
506
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
507
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
508
|
+
)
|
|
509
|
+
else
|
|
510
|
+
# NOTE: required lock is free and ready to be acquired! acquire! (write lock)
|
|
511
|
+
|
|
512
|
+
# Step 6.1: remove our acquirer from waiting queue
|
|
513
|
+
transact.call('ZREM', request_queue, acquirer_id)
|
|
514
|
+
|
|
515
|
+
# Step 6.2: acquire a lock and store an info about the acquirer and host
|
|
516
|
+
transact.call(
|
|
517
|
+
'HSET',
|
|
518
|
+
lock_key,
|
|
519
|
+
'acq_id', acquirer_id,
|
|
520
|
+
'hst_id', host_id,
|
|
521
|
+
'ts', timestamp = Time.now.to_f,
|
|
522
|
+
'ini_ttl', ttl,
|
|
523
|
+
*(meta.to_a if meta != nil) # steep:ignore
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
|
527
|
+
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
|
528
|
+
|
|
529
|
+
inter_result = :lock_obtaining
|
|
530
|
+
|
|
531
|
+
LogVisitor.obtain__free_to_acquire(
|
|
532
|
+
logger, log_sampled, log_lock_try, lock_key,
|
|
533
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
534
|
+
)
|
|
312
535
|
end
|
|
313
536
|
end
|
|
314
537
|
end
|
|
@@ -324,7 +547,29 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
324
547
|
# - if <result == nil> => the lock was changed during an extention:
|
|
325
548
|
# it is the fail case => go retry.
|
|
326
549
|
# - else: let's go! :))
|
|
327
|
-
if result.is_a?(::Array) && result.
|
|
550
|
+
if result.is_a?(::Array) && !result.empty? && held_rw_mode == :read
|
|
551
|
+
# NOTE: read lock extension commands:
|
|
552
|
+
# 1. ZADD XX INCR (new read lock expiration) (OK for != nil)
|
|
553
|
+
# 2. PEXPIRE NX/GT (registry TTL)
|
|
554
|
+
# 3. HINCRBY/HSET/PEXPIRE (read lock data)
|
|
555
|
+
if result[0] != nil
|
|
556
|
+
{
|
|
557
|
+
ok: true,
|
|
558
|
+
result: {
|
|
559
|
+
process: :extendable_conflict_work_through,
|
|
560
|
+
lock_key: lock_key,
|
|
561
|
+
acq_id: acquirer_id,
|
|
562
|
+
hst_id: host_id,
|
|
563
|
+
ts: spc_processed_timestamp,
|
|
564
|
+
ttl: ttl,
|
|
565
|
+
rw_mode: held_rw_mode
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else
|
|
569
|
+
# NOTE: the read lock is expired and removed from the registry during the extension
|
|
570
|
+
{ ok: false, result: :read_lock_is_expired_during_extension }
|
|
571
|
+
end
|
|
572
|
+
elsif result.is_a?(::Array) && result.size == 4 # NOTE: four commands should be processed
|
|
328
573
|
# TODO:
|
|
329
574
|
# => (!) analyze the command result and do actions with the depending on it
|
|
330
575
|
# 1. EVAL (extend lock pttl) (OK for != nil)
|
|
@@ -340,7 +585,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
340
585
|
acq_id: acquirer_id,
|
|
341
586
|
hst_id: host_id,
|
|
342
587
|
ts: spc_processed_timestamp,
|
|
343
|
-
ttl: ttl
|
|
588
|
+
ttl: ttl,
|
|
589
|
+
rw_mode: held_rw_mode
|
|
344
590
|
}
|
|
345
591
|
}
|
|
346
592
|
elsif result[0] != nil
|
|
@@ -354,7 +600,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
354
600
|
acq_id: acquirer_id,
|
|
355
601
|
hst_id: host_id,
|
|
356
602
|
ts: spc_processed_timestamp,
|
|
357
|
-
ttl: ttl
|
|
603
|
+
ttl: ttl,
|
|
604
|
+
rw_mode: held_rw_mode
|
|
358
605
|
}
|
|
359
606
|
}
|
|
360
607
|
else
|
|
@@ -379,13 +626,18 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
379
626
|
acq_id: acquirer_id,
|
|
380
627
|
hst_id: host_id,
|
|
381
628
|
ts: spc_processed_timestamp,
|
|
382
|
-
ttl: ttl
|
|
629
|
+
ttl: ttl,
|
|
630
|
+
rw_mode: held_rw_mode
|
|
383
631
|
}
|
|
384
632
|
}
|
|
385
633
|
when inter_result == :conflict_dead_lock
|
|
386
634
|
# Step 7.same_process_conflict.C:
|
|
387
635
|
# - deadlock. should fail in acquirement logic;
|
|
388
636
|
{ ok: false, result: :conflict_dead_lock }
|
|
637
|
+
when inter_result == :conflict_lock_upgrade
|
|
638
|
+
# Step 7.same_process_conflict.D (RW):
|
|
639
|
+
# - read-to-write lock upgrade. should fail in acquirement logic;
|
|
640
|
+
{ ok: false, result: :conflict_lock_upgrade }
|
|
389
641
|
when fail_fast && inter_result == :fail_fast_no_try
|
|
390
642
|
# Step 7.a: lock is still acquired and we should exit from the logic as soon as possible
|
|
391
643
|
{ ok: false, result: :fail_fast_no_try }
|
|
@@ -394,23 +646,39 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
394
646
|
when inter_result == :lock_is_still_acquired
|
|
395
647
|
# Step 7.b: lock is still acquired by another process => failed to acquire
|
|
396
648
|
{ ok: false, result: :lock_is_still_acquired }
|
|
649
|
+
when inter_result == :read_lock_is_still_acquired
|
|
650
|
+
# Step 7.b (RW): lock is still acquired by readers => failed to acquire
|
|
651
|
+
{ ok: false, result: :read_lock_is_still_acquired }
|
|
397
652
|
when inter_result == :acquirer_is_not_first_in_queue
|
|
398
653
|
# Step 7.c: lock is still acquired by another process => failed to acquire
|
|
399
654
|
{ ok: false, result: :acquirer_is_not_first_in_queue }
|
|
655
|
+
when inter_result == :write_request_is_ahead
|
|
656
|
+
# Step 7.c (RW): an earlier write lock request should obtain the lock first
|
|
657
|
+
{ ok: false, result: :write_request_is_ahead }
|
|
658
|
+
when inter_result == :read_request_is_ahead
|
|
659
|
+
# Step 7.c (RW): an earlier read lock request should obtain the lock first
|
|
660
|
+
{ ok: false, result: :read_request_is_ahead }
|
|
400
661
|
when result == nil || (result.is_a?(::Array) && result.empty?)
|
|
401
662
|
# Step 7.d: lock is already acquired durign the acquire race => failed to acquire
|
|
402
663
|
{ ok: false, result: :lock_is_acquired_during_acquire_race }
|
|
403
|
-
when result.is_a?(::Array)
|
|
664
|
+
when inter_result == :lock_obtaining && result.is_a?(::Array)
|
|
404
665
|
# TODO:
|
|
405
666
|
# => (!) analyze the command result and do actions with the depending on it;
|
|
406
667
|
# => (*) at this moment we accept that all comamnds are completed successfully;
|
|
407
668
|
# => (!) need to analyze:
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
669
|
+
# - (write lock):
|
|
670
|
+
# 1. zrem shoud return ? (?)
|
|
671
|
+
# 2. hset should return 3 as minimum
|
|
672
|
+
# (lock key is added to the redis as a hashmap with 3 fields as minimum)
|
|
673
|
+
# 3. pexpire should return 1 (expiration time is successfully applied)
|
|
674
|
+
# - (read lock):
|
|
675
|
+
# 1. zrem shoud return ? (?)
|
|
676
|
+
# 2. zremrangebyscore should return the number of dropped expired read locks
|
|
677
|
+
# 3. zadd should return 1 (new read lock) or 0 (expired read lock is re-obtained)
|
|
678
|
+
# 4. pexpire (NX + GT) should return 1 for one of them as minimum
|
|
679
|
+
# 5. del/hset/pexpire (read lock data)
|
|
412
680
|
|
|
413
|
-
# Step 7.
|
|
681
|
+
# Step 7.e: locked! :) let's go! => successfully acquired
|
|
414
682
|
{
|
|
415
683
|
ok: true,
|
|
416
684
|
result: {
|
|
@@ -419,7 +687,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
419
687
|
acq_id: acquirer_id,
|
|
420
688
|
hst_id: host_id,
|
|
421
689
|
ts: timestamp,
|
|
422
|
-
ttl: ttl
|
|
690
|
+
ttl: ttl,
|
|
691
|
+
rw_mode: held_rw_mode
|
|
423
692
|
}
|
|
424
693
|
}
|
|
425
694
|
else
|
|
@@ -428,6 +697,6 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
428
697
|
end
|
|
429
698
|
# rubocop:enable Lint/DuplicateBranch
|
|
430
699
|
end
|
|
431
|
-
# rubocop:enable Metrics/MethodLength
|
|
700
|
+
# rubocop:enable Metrics/MethodLength
|
|
432
701
|
end
|
|
433
702
|
# rubocop:enable Metrics/ModuleLength
|