redis_queued_locks 0.0.29 → 0.0.30

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 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.