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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3137184f5a89b895fe347d8080957e08a57974e91d5a68189d0eb157afd7c3eb
4
- data.tar.gz: 7b059f5de5cde2af1634eed3aeae540afd7edfc5ba564781789649098e53e0a1
3
+ metadata.gz: '0920957ed856cf515d2867ecd6ed7b1911ba53466822f09d5bd98386a5be4296'
4
+ data.tar.gz: 587fa64039e85a633fc2c65d5c0b012d7bfb29b9ccd25177e0895deb33850c38
5
5
  SHA512:
6
- metadata.gz: 4821b6c0fc142d90fae314aa11c92069be9223cc4a61be077ff6e19278fae97f920d31b590c5688f3614159a2f5c9d9d1792d8a6aee50f51d7b24f83ea1a9298
7
- data.tar.gz: 80a0de485f263459c52d230cc0e604d0c742db235a6a9b57053cc84001e9e146cfc35b9c31aff5e1ef5a2796692559f95fe94db39350720277fb219ef95a2685
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
- "acq_id => '#{acquier_id}'"
46
+ "queue_ttl => #{queue_ttl} " \
47
+ "acq_id => '#{acquier_id}' "
47
48
  )
48
49
  end
49
50
  end
50
51
 
51
- # Step 0: watch the lock key changes (and discard acquirement if lock is already acquired)
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
- unless waiting_acquier == acquier_id
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(redis, logger, lock_key, acquier_id, timed, ttl_shift, ttl, &block)
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(redis, logger, lock_key, acquier_id, timed, ttl_shift, ttl, &block)
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
  #
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.29
9
- VERSION = '0.0.29'
8
+ # @version 0.0.30
9
+ VERSION = '0.0.30'
10
10
  end
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.29
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.5.1
110
+ rubygems_version: 3.3.7
111
111
  signing_key:
112
112
  specification_version: 4
113
113
  summary: Queued distributed locks based on Redis.