redis_queued_locks 1.16.2 → 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 +281 -0
- data/.claude/rules/acquirer.md +81 -0
- data/.claude/rules/arguments.md +60 -0
- data/.claude/rules/git.md +46 -0
- data/.claude/rules/logic.md +63 -0
- data/.claude/rules/swarm.md +120 -0
- data/.claude/rules/tests.md +48 -0
- data/.claude/rules/type-checking.md +50 -0
- data/.claude/rules/visitors.md +69 -0
- data/.rubocop.rbs.yml +31 -0
- data/.rubocop.yml +3 -3
- data/.ruby-version +1 -1
- data/CHANGELOG.md +80 -0
- data/CLAUDE.md +36 -0
- data/README.md +367 -82
- data/Rakefile +31 -2
- data/github_ci/.keep +0 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/delay_execution.rb +2 -2
- 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 -132
- 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 +33 -9
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +73 -24
- 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 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -7
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +126 -29
- 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 +274 -158
- data/lib/redis_queued_locks/config/dsl.rb +4 -10
- data/lib/redis_queued_locks/resource.rb +117 -9
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +46 -6
- data/lib/redis_queued_locks/swarm/supervisor.rb +1 -1
- data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +166 -44
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +45 -47
- data/lib/redis_queued_locks/swarm/zombie_info.rb +42 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- data/rbs_collection.lock.yaml +10 -2
- data/rbs_collection.yaml +0 -2
- data/redis_queued_locks.gemspec +1 -1
- 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/flush_zombies.rbs +1 -1
- data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +13 -5
- data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +6 -6
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +5 -0
- metadata +17 -5
- data/github_ci/ruby3.3.gemfile +0 -17
- data/github_ci/ruby3.3.gemfile.lock +0 -216
|
@@ -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,68 +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
|
-
# @type var sp_conflict_status: Symbol
|
|
165
259
|
# @type var spc_processed_timestamp: Float
|
|
166
260
|
LogVisitor.reentrant_lock__extend_and_work_through(
|
|
167
261
|
logger, log_sampled, log_lock_try, lock_key,
|
|
168
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
262
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
169
263
|
sp_conflict_status, ttl, spc_processed_timestamp
|
|
170
264
|
)
|
|
171
265
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
172
266
|
elsif sp_conflict_status == :conflict_work_through
|
|
173
267
|
inter_result = :conflict_work_through
|
|
174
268
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
190
304
|
|
|
191
|
-
# @type var sp_conflict_status: Symbol
|
|
192
305
|
# @type var spc_processed_timestamp: Float
|
|
193
306
|
LogVisitor.reentrant_lock__work_through(
|
|
194
307
|
logger, log_sampled, log_lock_try, lock_key,
|
|
195
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
308
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
196
309
|
sp_conflict_status, spc_processed_timestamp
|
|
197
310
|
)
|
|
198
311
|
# SP-Conflict-Step X2: switch to dead lock logic or not
|
|
@@ -200,20 +313,39 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
200
313
|
inter_result = :conflict_dead_lock
|
|
201
314
|
spc_processed_timestamp = Time.now.to_f
|
|
202
315
|
|
|
203
|
-
# @type var sp_conflict_status: Symbol
|
|
204
316
|
# @type var spc_processed_timestamp: Float
|
|
205
317
|
LogVisitor.single_process_lock_conflict__dead_lock(
|
|
206
318
|
logger, log_sampled, log_lock_try, lock_key,
|
|
207
|
-
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,
|
|
208
331
|
sp_conflict_status, spc_processed_timestamp
|
|
209
332
|
)
|
|
210
333
|
# Reached the SP-Non-Conflict Mode (NOTE):
|
|
211
334
|
# - in other sp-conflict cases we are in <wait_for_lock> (non-conflict) status and should
|
|
212
335
|
# continue to work in classic way (next lines of code):
|
|
213
|
-
|
|
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
|
+
)
|
|
214
342
|
# Fast-Step X1: lock is already obtained. fail fast leads to "no try".
|
|
215
343
|
inter_result = :fail_fast_no_try
|
|
216
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
|
+
|
|
217
349
|
# Step 1: add an acquirer to the lock acquirement queue
|
|
218
350
|
# NOTE:
|
|
219
351
|
# 'NX' means "Only add new elements. Don't update already existing elements."
|
|
@@ -221,97 +353,185 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
221
353
|
# 1. (enqueue) <<if you are already in the queue - do nothing and wait your time>>
|
|
222
354
|
# 2. (requeue) or <<add to the right pre-calculated position if you are
|
|
223
355
|
# not in the queue now>>;
|
|
224
|
-
rconn.call('ZADD',
|
|
356
|
+
rconn.call('ZADD', request_queue, 'NX', acquirer_position, acquirer_id)
|
|
225
357
|
|
|
226
358
|
LogVisitor.acq_added_to_queue(
|
|
227
359
|
logger, log_sampled, log_lock_try, lock_key,
|
|
228
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
360
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
229
361
|
)
|
|
230
362
|
|
|
231
|
-
# Step 2.1: drop expired acquirers from the lock
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
238
371
|
|
|
239
372
|
LogVisitor.remove_expired_acqs(
|
|
240
373
|
logger, log_sampled, log_lock_try, lock_key,
|
|
241
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
374
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
242
375
|
)
|
|
243
376
|
|
|
244
377
|
# Step 3: get the actual acquirer waiting in the queue
|
|
245
|
-
|
|
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
|
|
246
386
|
|
|
247
387
|
LogVisitor.get_first_from_queue(
|
|
248
388
|
logger, log_sampled, log_lock_try, lock_key,
|
|
249
|
-
queue_ttl, acquirer_id, host_id, access_strategy, waiting_acquirer
|
|
389
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode, waiting_acquirer
|
|
250
390
|
)
|
|
251
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
|
+
|
|
252
404
|
# Step PRE-4.x: check if the request time limit is reached
|
|
253
405
|
# (when the current try self-removes itself from queue (queue ttl has come))
|
|
254
406
|
if waiting_acquirer == nil
|
|
255
407
|
LogVisitor.exit__queue_ttl_reached(
|
|
256
408
|
logger, log_sampled, log_lock_try, lock_key,
|
|
257
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
409
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
|
|
258
410
|
)
|
|
259
411
|
|
|
260
412
|
inter_result = :dead_score_reached
|
|
261
413
|
# Step STRATEGY: check the stragegy and corresponding preventing factor
|
|
262
414
|
# Step STRATEGY (queued): check the actual acquirer: is it ours? are we aready to lock?
|
|
263
|
-
|
|
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
|
|
264
418
|
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
|
265
419
|
|
|
266
420
|
LogVisitor.exit__no_first(
|
|
267
421
|
logger, log_sampled, log_lock_try, lock_key,
|
|
268
|
-
queue_ttl, acquirer_id, host_id, access_strategy, waiting_acquirer,
|
|
269
|
-
|
|
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 : {})
|
|
270
425
|
)
|
|
271
426
|
inter_result = :acquirer_is_not_first_in_queue
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
# Step 5: find the lock -> check if the our lock is already acquired
|
|
278
|
-
locked_by_acquirer = rconn.call('HGET', lock_key, 'acq_id')
|
|
279
|
-
|
|
280
|
-
if locked_by_acquirer
|
|
281
|
-
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
|
282
|
-
|
|
283
|
-
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(
|
|
284
431
|
logger, log_sampled, log_lock_try, lock_key,
|
|
285
|
-
queue_ttl, acquirer_id, host_id, access_strategy,
|
|
286
|
-
|
|
287
|
-
rconn.call('HGETALL', lock_key).to_h
|
|
432
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
433
|
+
opposite_request_ahead
|
|
288
434
|
)
|
|
289
|
-
inter_result = :
|
|
435
|
+
inter_result = :write_request_is_ahead
|
|
290
436
|
else
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
# Step 6.1: remove our acquirer from waiting queue
|
|
294
|
-
transact.call('ZREM', lock_key_queue, acquirer_id)
|
|
295
|
-
|
|
296
|
-
# Step 6.2: acquire a lock and store an info about the acquirer and host
|
|
297
|
-
transact.call(
|
|
298
|
-
'HSET',
|
|
299
|
-
lock_key,
|
|
300
|
-
'acq_id', acquirer_id,
|
|
301
|
-
'hst_id', host_id,
|
|
302
|
-
'ts', timestamp = Time.now.to_f,
|
|
303
|
-
'ini_ttl', ttl,
|
|
304
|
-
*(meta.to_a if meta != nil) # steep:ignore
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
|
308
|
-
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
|
309
|
-
|
|
310
|
-
LogVisitor.obtain__free_to_acquire(
|
|
437
|
+
LogVisitor.exit__read_request_ahead(
|
|
311
438
|
logger, log_sampled, log_lock_try, lock_key,
|
|
312
|
-
queue_ttl, acquirer_id, host_id, access_strategy
|
|
439
|
+
queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode,
|
|
440
|
+
opposite_request_ahead
|
|
313
441
|
)
|
|
442
|
+
inter_result = :read_request_is_ahead
|
|
314
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
|
+
)
|
|
315
535
|
end
|
|
316
536
|
end
|
|
317
537
|
end
|
|
@@ -327,7 +547,29 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
327
547
|
# - if <result == nil> => the lock was changed during an extention:
|
|
328
548
|
# it is the fail case => go retry.
|
|
329
549
|
# - else: let's go! :))
|
|
330
|
-
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
|
|
331
573
|
# TODO:
|
|
332
574
|
# => (!) analyze the command result and do actions with the depending on it
|
|
333
575
|
# 1. EVAL (extend lock pttl) (OK for != nil)
|
|
@@ -343,7 +585,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
343
585
|
acq_id: acquirer_id,
|
|
344
586
|
hst_id: host_id,
|
|
345
587
|
ts: spc_processed_timestamp,
|
|
346
|
-
ttl: ttl
|
|
588
|
+
ttl: ttl,
|
|
589
|
+
rw_mode: held_rw_mode
|
|
347
590
|
}
|
|
348
591
|
}
|
|
349
592
|
elsif result[0] != nil
|
|
@@ -357,7 +600,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
357
600
|
acq_id: acquirer_id,
|
|
358
601
|
hst_id: host_id,
|
|
359
602
|
ts: spc_processed_timestamp,
|
|
360
|
-
ttl: ttl
|
|
603
|
+
ttl: ttl,
|
|
604
|
+
rw_mode: held_rw_mode
|
|
361
605
|
}
|
|
362
606
|
}
|
|
363
607
|
else
|
|
@@ -382,13 +626,18 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
382
626
|
acq_id: acquirer_id,
|
|
383
627
|
hst_id: host_id,
|
|
384
628
|
ts: spc_processed_timestamp,
|
|
385
|
-
ttl: ttl
|
|
629
|
+
ttl: ttl,
|
|
630
|
+
rw_mode: held_rw_mode
|
|
386
631
|
}
|
|
387
632
|
}
|
|
388
633
|
when inter_result == :conflict_dead_lock
|
|
389
634
|
# Step 7.same_process_conflict.C:
|
|
390
635
|
# - deadlock. should fail in acquirement logic;
|
|
391
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 }
|
|
392
641
|
when fail_fast && inter_result == :fail_fast_no_try
|
|
393
642
|
# Step 7.a: lock is still acquired and we should exit from the logic as soon as possible
|
|
394
643
|
{ ok: false, result: :fail_fast_no_try }
|
|
@@ -397,23 +646,39 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
397
646
|
when inter_result == :lock_is_still_acquired
|
|
398
647
|
# Step 7.b: lock is still acquired by another process => failed to acquire
|
|
399
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 }
|
|
400
652
|
when inter_result == :acquirer_is_not_first_in_queue
|
|
401
653
|
# Step 7.c: lock is still acquired by another process => failed to acquire
|
|
402
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 }
|
|
403
661
|
when result == nil || (result.is_a?(::Array) && result.empty?)
|
|
404
662
|
# Step 7.d: lock is already acquired durign the acquire race => failed to acquire
|
|
405
663
|
{ ok: false, result: :lock_is_acquired_during_acquire_race }
|
|
406
|
-
when result.is_a?(::Array)
|
|
664
|
+
when inter_result == :lock_obtaining && result.is_a?(::Array)
|
|
407
665
|
# TODO:
|
|
408
666
|
# => (!) analyze the command result and do actions with the depending on it;
|
|
409
667
|
# => (*) at this moment we accept that all comamnds are completed successfully;
|
|
410
668
|
# => (!) need to analyze:
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
#
|
|
414
|
-
#
|
|
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)
|
|
415
680
|
|
|
416
|
-
# Step 7.
|
|
681
|
+
# Step 7.e: locked! :) let's go! => successfully acquired
|
|
417
682
|
{
|
|
418
683
|
ok: true,
|
|
419
684
|
result: {
|
|
@@ -422,7 +687,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
422
687
|
acq_id: acquirer_id,
|
|
423
688
|
hst_id: host_id,
|
|
424
689
|
ts: timestamp,
|
|
425
|
-
ttl: ttl
|
|
690
|
+
ttl: ttl,
|
|
691
|
+
rw_mode: held_rw_mode
|
|
426
692
|
}
|
|
427
693
|
}
|
|
428
694
|
else
|
|
@@ -431,6 +697,6 @@ module RedisQueuedLocks::Acquirer::AcquireLock::TryToLock
|
|
|
431
697
|
end
|
|
432
698
|
# rubocop:enable Lint/DuplicateBranch
|
|
433
699
|
end
|
|
434
|
-
# rubocop:enable Metrics/MethodLength
|
|
700
|
+
# rubocop:enable Metrics/MethodLength
|
|
435
701
|
end
|
|
436
702
|
# rubocop:enable Metrics/ModuleLength
|