redis_queued_locks 0.0.29 → 0.0.30
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +34 -3
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +13 -1
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +38 -2
- data/lib/redis_queued_locks/resource.rb +14 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0920957ed856cf515d2867ecd6ed7b1911ba53466822f09d5bd98386a5be4296'
|
4
|
+
data.tar.gz: 587fa64039e85a633fc2c65d5c0b012d7bfb29b9ccd25177e0895deb33850c38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 388e0d43c66464672bbe019dc0f26e3bac44be0cc3e5dcdb502ccd9b2d0b82931de2608c6b7219b2c190b9024197a0cbf1263a7cc7d4f8ed6568480ce6120930
|
7
|
+
data.tar.gz: 56158aca2424c21fc3fcbeebbd33625559aceca82143c2e18700334c6485e919d4005f4391ba3a07b0839e9122f57b9c5d223ebfd4bc38bdd4f61b58bc94f34f
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,19 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.30] - 2024-03-23
|
4
|
+
### Fixed
|
5
|
+
- Re-enqueue problem: fixed a problem when the expired lock requests were infinitly re-added to the lock queue
|
6
|
+
and immediately removed from the lock queue rather than being re-positioned. It happens when the lock request
|
7
|
+
ttl reached the queue ttl, and the new request now had the dead score forever (fix: it's score now will be correctly
|
8
|
+
recalculated from the current time at the dead score time moment);
|
9
|
+
### Added
|
10
|
+
- Logging: more detailed logs to the `RedisQueuedLocks::Acquier::AcquierLock` logic and it's sub-modules:
|
11
|
+
- added new logs;
|
12
|
+
- added `queue_ttl` to each log;
|
13
|
+
|
3
14
|
## [0.0.29] - 2024-03-23
|
4
15
|
### Added
|
5
|
-
- Logging: more detailed logs to `RedisQueuedLocks::Acquier::AcquireLock::TryToLock`;
|
16
|
+
- Logging: added more detailed logs to `RedisQueuedLocks::Acquier::AcquireLock::TryToLock`;
|
6
17
|
|
7
18
|
## [0.0.28] - 2024-03-21
|
8
19
|
### Added
|
@@ -43,23 +43,26 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
43
43
|
logger.debug(
|
44
44
|
"[redis_queued_locks.try_lock.start] " \
|
45
45
|
"lock_key => '#{lock_key}' " \
|
46
|
-
"
|
46
|
+
"queue_ttl => #{queue_ttl} " \
|
47
|
+
"acq_id => '#{acquier_id}' "
|
47
48
|
)
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
51
|
-
# Step
|
52
|
+
# Step X: start to work with lock acquiring
|
52
53
|
result = redis.with do |rconn|
|
53
54
|
if log_lock_try
|
54
55
|
run_non_critical do
|
55
56
|
logger.debug(
|
56
57
|
"[redis_queued_locks.try_lock.rconn_fetched] " \
|
57
58
|
"lock_key => '#{lock_key}' " \
|
59
|
+
"queue_ttl => #{queue_ttl} " \
|
58
60
|
"acq_id => '#{acquier_id}'"
|
59
61
|
)
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
65
|
+
# Step 0: watch the lock key changes (and discard acquirement if lock is already acquired)
|
63
66
|
rconn.multi(watch: [lock_key]) do |transact|
|
64
67
|
# Fast-Step X0: fail-fast check
|
65
68
|
if fail_fast && rconn.call('HGET', lock_key, 'acq_id')
|
@@ -74,6 +77,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
74
77
|
logger.debug(
|
75
78
|
"[redis_queued_locks.try_lock.acq_added_to_queue] " \
|
76
79
|
"lock_key => '#{lock_key}' " \
|
80
|
+
"queue_ttl => #{queue_ttl} " \
|
77
81
|
"acq_id => '#{acquier_id}'"
|
78
82
|
)
|
79
83
|
end
|
@@ -96,6 +100,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
96
100
|
logger.debug(
|
97
101
|
"[redis_queued_locks.try_lock.remove_expired_acqs] " \
|
98
102
|
"lock_key => '#{lock_key}' " \
|
103
|
+
"queue_ttl => #{queue_ttl} " \
|
99
104
|
"acq_id => '#{acquier_id}'"
|
100
105
|
)
|
101
106
|
end
|
@@ -113,6 +118,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
113
118
|
logger.debug(
|
114
119
|
"[redis_queued_locks.try_lock.get_first_from_queue] " \
|
115
120
|
"lock_key => '#{lock_key}' " \
|
121
|
+
"queue_ttl => #{queue_ttl} " \
|
116
122
|
"acq_id => '#{acquier_id}' " \
|
117
123
|
"first_acq_id_in_queue => '#{waiting_acquier}'"
|
118
124
|
)
|
@@ -124,8 +130,28 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
124
130
|
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
125
131
|
)
|
126
132
|
|
133
|
+
# Step PRE-4.x: check if the request time limit is reached
|
134
|
+
# (when the current try self-removes itself from queue (queue ttl has come))
|
135
|
+
if waiting_acquier == nil
|
136
|
+
if log_lock_try
|
137
|
+
run_non_critical do
|
138
|
+
logger.debug(
|
139
|
+
"[redis_queued_locks.try_lock.exit__queue_ttl_reached] " \
|
140
|
+
"lock_key => '#{lock_key}' " \
|
141
|
+
"queue_ttl => #{queue_ttl} " \
|
142
|
+
"acq_id => '#{acquier_id}'"
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
RedisQueuedLocks.debug(
|
148
|
+
"Step PRE-ROLLBACK №0: достигли лимита времени эквайра лока (queue ttl). выходим. " \
|
149
|
+
"[Наша позиция: #{acquier_id}. queue_ttl: #{queue_ttl}]"
|
150
|
+
)
|
151
|
+
|
152
|
+
inter_result = :dead_score_reached
|
127
153
|
# Step 4: check the actual acquier: is it ours? are we aready to lock?
|
128
|
-
|
154
|
+
elsif waiting_acquier != acquier_id
|
129
155
|
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
130
156
|
|
131
157
|
if log_lock_try
|
@@ -133,6 +159,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
133
159
|
logger.debug(
|
134
160
|
"[redis_queued_locks.try_lock.exit__no_first] " \
|
135
161
|
"lock_key => '#{lock_key}' " \
|
162
|
+
"queue_ttl => #{queue_ttl} " \
|
136
163
|
"acq_id => '#{acquier_id}' " \
|
137
164
|
"first_acq_id_in_queue => '#{waiting_acquier}'"
|
138
165
|
)
|
@@ -167,6 +194,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
167
194
|
logger.debug(
|
168
195
|
"[redis_queued_locks.try_lock.exit__still_obtained] " \
|
169
196
|
"lock_key => '#{lock_key}' " \
|
197
|
+
"queue_ttl => #{queue_ttl} " \
|
170
198
|
"acq_id => '#{acquier_id}' " \
|
171
199
|
"first_acq_id_in_queue => '#{waiting_acquier}' " \
|
172
200
|
"locked_by_acq_id => '#{locked_by_acquier}'"
|
@@ -213,6 +241,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
213
241
|
logger.debug(
|
214
242
|
"[redis_queued_locks.try_lock.run__free_to_acquire] " \
|
215
243
|
"lock_key => '#{lock_key}' " \
|
244
|
+
"queue_ttl => #{queue_ttl} " \
|
216
245
|
"acq_id => '#{acquier_id}'"
|
217
246
|
)
|
218
247
|
end
|
@@ -229,6 +258,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
229
258
|
when fail_fast && inter_result == :fail_fast_no_try
|
230
259
|
# Step 7.a: lock is still acquired and we should exit from the logic as soon as possible
|
231
260
|
RedisQueuedLocks::Data[ok: false, result: inter_result]
|
261
|
+
when inter_result == :dead_score_reached
|
262
|
+
RedisQueuedLocks::Data[ok: false, result: inter_result]
|
232
263
|
when inter_result == :lock_is_still_acquired || inter_result == :acquier_is_not_first_in_queue
|
233
264
|
# Step 7.b: lock is still acquired by another process => failed to acquire
|
234
265
|
RedisQueuedLocks::Data[ok: false, result: inter_result]
|
@@ -13,12 +13,23 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
13
13
|
# @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
|
14
14
|
# @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
|
15
15
|
# @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
|
16
|
+
# @param queue_ttl [Integer] Lock request lifetime.
|
16
17
|
# @param block [Block] Custom logic that should be invoked unter the obtained lock.
|
17
18
|
# @return [Any,NilClass] nil is returned no block parametr is provided.
|
18
19
|
#
|
19
20
|
# @api private
|
20
21
|
# @since 0.1.0
|
21
|
-
def yield_with_expire(
|
22
|
+
def yield_with_expire(
|
23
|
+
redis,
|
24
|
+
logger,
|
25
|
+
lock_key,
|
26
|
+
acquier_id,
|
27
|
+
timed,
|
28
|
+
ttl_shift,
|
29
|
+
ttl,
|
30
|
+
queue_ttl,
|
31
|
+
&block
|
32
|
+
)
|
22
33
|
if block_given?
|
23
34
|
if timed && ttl != nil
|
24
35
|
timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
|
@@ -32,6 +43,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
32
43
|
logger.debug(
|
33
44
|
"[redis_queued_locks.expire_lock] " \
|
34
45
|
"lock_key => '#{lock_key}' " \
|
46
|
+
"queue_ttl => #{queue_ttl} " \
|
35
47
|
"acq_id => '#{acquier_id}'"
|
36
48
|
)
|
37
49
|
end
|
@@ -140,6 +140,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
140
140
|
logger.debug(
|
141
141
|
"[redis_queued_locks.start_lock_obtaining] " \
|
142
142
|
"lock_key => '#{lock_key}' " \
|
143
|
+
"queue_ttl => #{queue_ttl} " \
|
143
144
|
"acq_id => '#{acquier_id}'"
|
144
145
|
)
|
145
146
|
end
|
@@ -150,6 +151,30 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
150
151
|
|
151
152
|
# Step 2.1: caclically try to obtain the lock
|
152
153
|
while acq_process[:should_try]
|
154
|
+
run_non_critical do
|
155
|
+
logger.debug(
|
156
|
+
"[redis_queued_locks.start_try_to_lock_cycle] " \
|
157
|
+
"lock_key => '#{lock_key}' " \
|
158
|
+
"queue_ttl => #{queue_ttl} " \
|
159
|
+
"acq_id => '{#{acquier_id}'"
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Step 2.X: check the actual score: is it in queue ttl limit or not?
|
164
|
+
if RedisQueuedLocks::Resource.dead_score_reached?(acquier_position, queue_ttl)
|
165
|
+
# Step 2.X.X: dead score reached => re-queue the lock request with the new score;
|
166
|
+
acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
|
167
|
+
|
168
|
+
run_non_critical do
|
169
|
+
logger.debug(
|
170
|
+
"[redis_queued_locks.dead_score_reached__reset_acquier_position] " \
|
171
|
+
"lock_key => '#{lock_key} " \
|
172
|
+
"queue_ttl => #{queue_ttl} " \
|
173
|
+
"acq_id => '#{acquier_id}'"
|
174
|
+
)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
153
178
|
try_to_lock(
|
154
179
|
redis,
|
155
180
|
logger,
|
@@ -176,6 +201,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
176
201
|
logger.debug(
|
177
202
|
"[redis_queued_locks.lock_obtained] " \
|
178
203
|
"lock_key => '#{result[:lock_key]}' " \
|
204
|
+
"queue_ttl => #{queue_ttl} " \
|
179
205
|
"acq_id => '#{acquier_id}' " \
|
180
206
|
"acq_time => #{acq_time} (ms)"
|
181
207
|
)
|
@@ -240,7 +266,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
240
266
|
end
|
241
267
|
else
|
242
268
|
# NOTE:
|
243
|
-
# delay the exceution in order to prevent chaotic attempts
|
269
|
+
# delay the exceution in order to prevent chaotic lock-acquire attempts
|
244
270
|
# and to allow other processes and threads to obtain the lock too.
|
245
271
|
delay_execution(retry_delay, retry_jitter)
|
246
272
|
end
|
@@ -255,7 +281,17 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
255
281
|
begin
|
256
282
|
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
257
283
|
ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
|
258
|
-
yield_with_expire(
|
284
|
+
yield_with_expire(
|
285
|
+
redis,
|
286
|
+
logger,
|
287
|
+
lock_key,
|
288
|
+
acquier_id,
|
289
|
+
timed,
|
290
|
+
ttl_shift,
|
291
|
+
ttl,
|
292
|
+
queue_ttl,
|
293
|
+
&block
|
294
|
+
)
|
259
295
|
ensure
|
260
296
|
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
261
297
|
acq_process[:hold_time] = (
|
@@ -82,6 +82,20 @@ module RedisQueuedLocks::Resource
|
|
82
82
|
Time.now.to_f - queue_ttl
|
83
83
|
end
|
84
84
|
|
85
|
+
# @param acquier_position [Float]
|
86
|
+
# A time (epoch, seconds.microseconds) that represents
|
87
|
+
# the acquier position in lock request queue.
|
88
|
+
# @parma queue_ttl [Integer]
|
89
|
+
# In second.
|
90
|
+
# @return [Boolean]
|
91
|
+
# Is the lock request time limit has reached or not.
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
# @since 0.1.0
|
95
|
+
def dead_score_reached?(acquier_position, queue_ttl)
|
96
|
+
(acquier_position + queue_ttl) < Time.now.to_f
|
97
|
+
end
|
98
|
+
|
85
99
|
# @param lock_queue [String]
|
86
100
|
# @return [String]
|
87
101
|
#
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_queued_locks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.30
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rustam Ibragimov
|
@@ -107,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
107
|
- !ruby/object:Gem::Version
|
108
108
|
version: '0'
|
109
109
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
110
|
+
rubygems_version: 3.3.7
|
111
111
|
signing_key:
|
112
112
|
specification_version: 4
|
113
113
|
summary: Queued distributed locks based on Redis.
|