redis_queued_locks 1.6.0 → 1.7.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.
@@ -2,10 +2,10 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
+ # @version 1.7.0
5
6
  # rubocop:disable Metrics/ModuleLength, Metrics/BlockNesting
6
7
  module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
7
- # @since 1.0.0
8
- extend RedisQueuedLocks::Utilities
8
+ require_relative 'try_to_lock/log_visitor'
9
9
 
10
10
  # @return [String]
11
11
  #
@@ -27,6 +27,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
27
27
  # @param queue_ttl [Integer]
28
28
  # @param fail_fast [Boolean]
29
29
  # @param conflict_strategy [Symbol]
30
+ # @param access_strategy [Symbol]
30
31
  # @param meta [NilClass,Hash<String|Symbol,Any>]
31
32
  # @param log_sampled [Boolean]
32
33
  # @param instr_sampled [Boolean]
@@ -34,7 +35,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
34
35
  #
35
36
  # @api private
36
37
  # @since 1.0.0
37
- # @version 1.6.0
38
+ # @version 1.7.0
38
39
  # rubocop:disable Metrics/MethodLength
39
40
  def try_to_lock(
40
41
  redis,
@@ -48,6 +49,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
48
49
  queue_ttl,
49
50
  fail_fast,
50
51
  conflict_strategy,
52
+ access_strategy,
51
53
  meta,
52
54
  log_sampled,
53
55
  instr_sampled
@@ -57,29 +59,17 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
57
59
  timestamp = nil
58
60
  spc_processed_timestamp = nil
59
61
 
60
- if log_sampled && log_lock_try
61
- run_non_critical do
62
- logger.debug do
63
- "[redis_queued_locks.try_lock.start] " \
64
- "lock_key => '#{lock_key}' " \
65
- "queue_ttl => #{queue_ttl} " \
66
- "acq_id => '#{acquier_id}'"
67
- end
68
- end
69
- end
62
+ LogVisitor.start(
63
+ logger, log_sampled, log_lock_try,
64
+ lock_key, queue_ttl, acquier_id, access_strategy
65
+ )
70
66
 
71
67
  # Step X: start to work with lock acquiring
72
68
  result = redis.with do |rconn|
73
- if log_sampled && log_lock_try
74
- run_non_critical do
75
- logger.debug do
76
- "[redis_queued_locks.try_lock.rconn_fetched] " \
77
- "lock_key => '#{lock_key}' " \
78
- "queue_ttl => #{queue_ttl} " \
79
- "acq_id => '#{acquier_id}'"
80
- end
81
- end
82
- end
69
+ LogVisitor.rconn_fetched(
70
+ logger, log_sampled, log_lock_try,
71
+ lock_key, queue_ttl, acquier_id, access_strategy
72
+ )
83
73
 
84
74
  # Step 0:
85
75
  # watch the lock key changes (and discard acquirement if lock is already
@@ -92,16 +82,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
92
82
 
93
83
  # SP-Conflict Step X1: calculate the current deadlock status
94
84
  if current_lock_obtainer != nil && acquier_id == current_lock_obtainer
95
- if log_sampled && log_lock_try
96
- run_non_critical do
97
- logger.debug do
98
- "[redis_queued_locks.try_lock.same_process_conflict_detected] " \
99
- "lock_key => '#{lock_key}' " \
100
- "queue_ttl => #{queue_ttl} " \
101
- "acq_id => '#{acquier_id}'"
102
- end
103
- end
104
- end
85
+ LogVisitor.same_process_conflict_detected(
86
+ logger, log_sampled, log_lock_try,
87
+ lock_key, queue_ttl, acquier_id, access_strategy
88
+ )
105
89
 
106
90
  # SP-Conflict Step X2: self-process dead lock moment started.
107
91
  # SP-Conflict CHECK (Step CHECK): check chosen strategy and flag the current status
@@ -128,17 +112,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
128
112
  end
129
113
  # rubocop:enable Lint/DuplicateBranch
130
114
 
131
- if log_sampled && log_lock_try
132
- run_non_critical do
133
- logger.debug do
134
- "[redis_queued_locks.try_lock.same_process_conflict_analyzed] " \
135
- "lock_key => '#{lock_key}' " \
136
- "queue_ttl => #{queue_ttl} " \
137
- "acq_id => '#{acquier_id}' " \
138
- "spc_status => '#{sp_conflict_status}'"
139
- end
140
- end
141
- end
115
+ LogVisitor.same_process_conflict_analyzed(
116
+ logger, log_sampled, log_lock_try,
117
+ lock_key, queue_ttl, acquier_id, access_strategy, sp_conflict_status
118
+ )
142
119
  end
143
120
 
144
121
  # SP-Conflict-Step X2: switch to conflict-based logic or not
@@ -176,19 +153,11 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
176
153
  )
177
154
  inter_result = :extendable_conflict_work_through
178
155
 
179
- if log_sampled && log_lock_try
180
- run_non_critical do
181
- logger.debug do
182
- "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through] " \
183
- "lock_key => '#{lock_key}' " \
184
- "queue_ttl => #{queue_ttl} " \
185
- "acq_id => '#{acquier_id}'" \
186
- "spc_status => '#{sp_conflict_status} '" \
187
- "last_ext_ttl => '#{ttl}' " \
188
- "last_ext_ts => '#{spc_processed_timestamp}'"
189
- end
190
- end
191
- end
156
+ LogVisitor.reentrant_lock__extend_and_work_through(
157
+ logger, log_sampled, log_lock_try,
158
+ lock_key, queue_ttl, acquier_id, access_strategy,
159
+ sp_conflict_status, ttl, spc_processed_timestamp
160
+ )
192
161
  # SP-Conflict-Step X2: switch to dead lock logic or not
193
162
  elsif sp_conflict_status == :conflict_work_through
194
163
  inter_result = :conflict_work_through
@@ -209,33 +178,21 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
209
178
  'l_spc_ts', (spc_processed_timestamp = Time.now.to_f)
210
179
  )
211
180
 
212
- if log_sampled && log_lock_try
213
- run_non_critical do
214
- logger.debug do
215
- "[redis_queued_locks.try_lock.reentrant_lock__work_through] " \
216
- "lock_key => '#{lock_key}' " \
217
- "queue_ttl => #{queue_ttl} " \
218
- "acq_id => '#{acquier_id}' " \
219
- "spc_status => '#{sp_conflict_status} ' " \
220
- "last_spc_ts => '#{spc_processed_timestamp}'"
221
- end
222
- end
223
- end
181
+ LogVisitor.reentrant_lock__work_through(
182
+ logger, log_sampled, log_lock_try,
183
+ lock_key, queue_ttl, acquier_id, access_strategy,
184
+ sp_conflict_status, spc_processed_timestamp
185
+ )
224
186
  # SP-Conflict-Step X2: switch to dead lock logic or not
225
187
  elsif sp_conflict_status == :conflict_dead_lock
226
188
  inter_result = :conflict_dead_lock
227
189
  spc_processed_timestamp = Time.now.to_f
228
190
 
229
- if log_sampled && log_lock_try
230
- logger.debug do
231
- "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock] " \
232
- "lock_key => '#{lock_key}' " \
233
- "queue_ttl => #{queue_ttl} " \
234
- "acq_id => '#{acquier_id}' " \
235
- "spc_status => '#{sp_conflict_status}' " \
236
- "last_spc_ts => '#{spc_processed_timestamp}'"
237
- end
238
- end
191
+ LogVisitor.single_process_lock_conflict__dead_lock(
192
+ logger, log_sampled, log_lock_try,
193
+ lock_key, queue_ttl, acquier_id, access_strategy,
194
+ sp_conflict_status, spc_processed_timestamp
195
+ )
239
196
  # Reached the SP-Non-Conflict Mode (NOTE):
240
197
  # - in other sp-conflict cases we are in <wait_for_lock> (non-conflict) status and should
241
198
  # continue to work in classic way (next lines of code):
@@ -246,16 +203,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
246
203
  # Step 1: add an acquier to the lock acquirement queue
247
204
  rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
248
205
 
249
- if log_sampled && log_lock_try
250
- run_non_critical do
251
- logger.debug do
252
- "[redis_queued_locks.try_lock.acq_added_to_queue] " \
253
- "lock_key => '#{lock_key}' " \
254
- "queue_ttl => #{queue_ttl} " \
255
- "acq_id => '#{acquier_id}'"
256
- end
257
- end
258
- end
206
+ LogVisitor.acq_added_to_queue(
207
+ logger, log_sampled, log_lock_try,
208
+ lock_key, queue_ttl, acquier_id, access_strategy
209
+ )
259
210
 
260
211
  # Step 2.1: drop expired acquiers from the lock queue
261
212
  rconn.call(
@@ -265,66 +216,41 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
265
216
  RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
266
217
  )
267
218
 
268
- if log_sampled && log_lock_try
269
- run_non_critical do
270
- logger.debug do
271
- "[redis_queued_locks.try_lock.remove_expired_acqs] " \
272
- "lock_key => '#{lock_key}' " \
273
- "queue_ttl => #{queue_ttl} " \
274
- "acq_id => '#{acquier_id}'"
275
- end
276
- end
277
- end
219
+ LogVisitor.remove_expired_acqs(
220
+ logger, log_sampled, log_lock_try,
221
+ lock_key, queue_ttl, acquier_id, access_strategy
222
+ )
278
223
 
279
224
  # Step 3: get the actual acquier waiting in the queue
280
225
  waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
281
226
 
282
- if log_sampled && log_lock_try
283
- run_non_critical do
284
- logger.debug do
285
- "[redis_queued_locks.try_lock.get_first_from_queue] " \
286
- "lock_key => '#{lock_key}' " \
287
- "queue_ttl => #{queue_ttl} " \
288
- "acq_id => '#{acquier_id}' " \
289
- "first_acq_id_in_queue => '#{waiting_acquier}'"
290
- end
291
- end
292
- end
227
+ LogVisitor.get_first_from_queue(
228
+ logger, log_sampled, log_lock_try,
229
+ lock_key, queue_ttl, acquier_id, access_strategy, waiting_acquier
230
+ )
293
231
 
294
232
  # Step PRE-4.x: check if the request time limit is reached
295
233
  # (when the current try self-removes itself from queue (queue ttl has come))
296
234
  if waiting_acquier == nil
297
- if log_sampled && log_lock_try
298
- run_non_critical do
299
- logger.debug do
300
- "[redis_queued_locks.try_lock.exit__queue_ttl_reached] " \
301
- "lock_key => '#{lock_key}' " \
302
- "queue_ttl => #{queue_ttl} " \
303
- "acq_id => '#{acquier_id}'"
304
- end
305
- end
306
- end
235
+ LogVisitor.exit__queue_ttl_reached(
236
+ logger, log_sampled, log_lock_try,
237
+ lock_key, queue_ttl, acquier_id, access_strategy
238
+ )
307
239
 
308
240
  inter_result = :dead_score_reached
309
- # Step 4: check the actual acquier: is it ours? are we aready to lock?
310
- elsif waiting_acquier != acquier_id
241
+ # Step STRATEGY: check the stragegy and corresponding preventing factor
242
+ # Step STRATEGY (queued): check the actual acquier: is it ours? are we aready to lock?
243
+ elsif access_strategy == :queued && waiting_acquier != acquier_id
311
244
  # Step ROLLBACK 1.1: our time hasn't come yet. retry!
312
-
313
- if log_sampled && log_lock_try
314
- run_non_critical do
315
- logger.debug do
316
- "[redis_queued_locks.try_lock.exit__no_first] " \
317
- "lock_key => '#{lock_key}' " \
318
- "queue_ttl => #{queue_ttl} " \
319
- "acq_id => '#{acquier_id}' " \
320
- "first_acq_id_in_queue => '#{waiting_acquier}' " \
321
- "<current_lock_data> => <<#{rconn.call('HGETALL', lock_key).to_h}>>"
322
- end
323
- end
324
- end
325
-
245
+ LogVisitor.exit__no_first(
246
+ logger, log_sampled, log_lock_try,
247
+ lock_key, queue_ttl, acquier_id, access_strategy, waiting_acquier,
248
+ rconn.call('HGETALL', lock_key).to_h
249
+ )
326
250
  inter_result = :acquier_is_not_first_in_queue
327
- else
251
+ # Step STRAGEY: successfull (:queued OR :random)
252
+ elsif (access_strategy == :queued && waiting_acquier == acquier_id) ||
253
+ (access_strategy == :random)
328
254
  # NOTE: our time has come! let's try to acquire the lock!
329
255
 
330
256
  # Step 5: find the lock -> check if the our lock is already acquired
@@ -333,20 +259,12 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
333
259
  if locked_by_acquier
334
260
  # Step ROLLBACK 2: required lock is stil acquired. retry!
335
261
 
336
- if log_sampled && log_lock_try
337
- run_non_critical do
338
- logger.debug do
339
- "[redis_queued_locks.try_lock.exit__lock_still_obtained] " \
340
- "lock_key => '#{lock_key}' " \
341
- "queue_ttl => #{queue_ttl} " \
342
- "acq_id => '#{acquier_id}' " \
343
- "first_acq_id_in_queue => '#{waiting_acquier}' " \
344
- "locked_by_acq_id => '#{locked_by_acquier}' " \
345
- "<current_lock_data> => <<#{rconn.call('HGETALL', lock_key).to_h}>>"
346
- end
347
- end
348
- end
349
-
262
+ LogVisitor.exit__lock_still_obtained(
263
+ logger, log_sampled, log_lock_try,
264
+ lock_key, queue_ttl, acquier_id, access_strategy,
265
+ waiting_acquier, locked_by_acquier,
266
+ rconn.call('HGETALL', lock_key).to_h
267
+ )
350
268
  inter_result = :lock_is_still_acquired
351
269
  else
352
270
  # NOTE: required lock is free and ready to be acquired! acquire!
@@ -367,16 +285,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
367
285
  # Step 6.3: set the lock expiration time in order to prevent "infinite locks"
368
286
  transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
369
287
 
370
- if log_sampled && log_lock_try
371
- run_non_critical do
372
- logger.debug do
373
- "[redis_queued_locks.try_lock.obtain__free_to_acquire] " \
374
- "lock_key => '#{lock_key}' " \
375
- "queue_ttl => #{queue_ttl} " \
376
- "acq_id => '#{acquier_id}'"
377
- end
378
- end
379
- end
288
+ LogVisitor.obtain__free_to_acquire(
289
+ logger, log_sampled, log_lock_try,
290
+ lock_key, queue_ttl, acquier_id, access_strategy
291
+ )
380
292
  end
381
293
  end
382
294
  end
@@ -479,42 +391,5 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
479
391
  # rubocop:enable Lint/DuplicateBranch
480
392
  end
481
393
  # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
482
-
483
- # @param redis [RedisClient]
484
- # @param logger [::Logger,#debug]
485
- # @param lock_key [String]
486
- # @param lock_key_queue [String]
487
- # @param queue_ttl [Integer]
488
- # @param acquier_id [String]
489
- # @param log_sampled [Boolean]
490
- # @param instr_sampled [Boolean]
491
- # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
492
- #
493
- # @api private
494
- # @since 1.0.0
495
- # @version 1.6.0
496
- def dequeue_from_lock_queue(
497
- redis,
498
- logger,
499
- lock_key,
500
- lock_key_queue,
501
- queue_ttl,
502
- acquier_id,
503
- log_sampled,
504
- instr_sampled
505
- )
506
- result = redis.call('ZREM', lock_key_queue, acquier_id)
507
-
508
- run_non_critical do
509
- logger.debug do
510
- "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue] " \
511
- "lock_key => '#{lock_key}' " \
512
- "queue_ttl => '#{queue_ttl}' " \
513
- "acq_id => '#{acquier_id}'"
514
- end
515
- end if log_sampled
516
-
517
- RedisQueuedLocks::Data[ok: true, result: result]
518
- end
519
394
  end
520
395
  # rubocop:enable Metrics/ModuleLength, Metrics/BlockNesting
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.7.0
5
+ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire::LogVisitor
6
+ extend self
7
+
8
+ # @param logger [::Logger,#debug]
9
+ # @param log_sampled [Boolean]
10
+ # @param lock_key [String]
11
+ # @param queue_ttl [Integer]
12
+ # @param acquier_id [String]
13
+ # @param access_strategy [Symbol]
14
+ # @return [void]
15
+ #
16
+ # @api private
17
+ # @since 1.7.0
18
+ def expire_lock(
19
+ logger,
20
+ log_sampled,
21
+ lock_key,
22
+ queue_ttl,
23
+ acquier_id,
24
+ access_strategy
25
+ )
26
+ return unless log_sampled
27
+
28
+ logger.debug do
29
+ "[redis_queued_locks.expire_lock] " \
30
+ "lock_key => '#{lock_key}' " \
31
+ "queue_ttl => #{queue_ttl} " \
32
+ "acq_id => '#{acquier_id}' " \
33
+ "acs_strat => '#{access_strategy}'"
34
+ end rescue nil
35
+ end
36
+
37
+ # @param logger [::Logger,#debug]
38
+ # @param log_sampled [Boolean]
39
+ # @param lock_key [String]
40
+ # @param decreased_ttl [Integer]
41
+ # @param queue_ttl [Integer]
42
+ # @param acquier_id [String]
43
+ # @param access_strategy [Symbol]
44
+ # @return [void]
45
+ #
46
+ # @api private
47
+ # @since 1.7.0
48
+ def decrease_lock(
49
+ logger,
50
+ log_sampled,
51
+ lock_key,
52
+ decreased_ttl,
53
+ queue_ttl,
54
+ acquier_id,
55
+ access_strategy
56
+ )
57
+ return unless log_sampled
58
+
59
+ logger.debug do
60
+ "[redis_queued_locks.decrease_lock] " \
61
+ "lock_key => '#{lock_key}' " \
62
+ "decreased_ttl => #{decreased_ttl} " \
63
+ "queue_ttl => #{queue_ttl} " \
64
+ "acq_id => '#{acquier_id}' " \
65
+ "acs_strat => '#{access_strategy}'"
66
+ end rescue nil
67
+ end
68
+ end
@@ -2,9 +2,9 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.3.0
5
+ # @version 1.7.0
5
6
  module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
6
- # @since 1.3.0
7
- extend RedisQueuedLocks::Utilities
7
+ require_relative 'yield_expire/log_visitor'
8
8
 
9
9
  # @return [String]
10
10
  #
@@ -19,6 +19,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
19
19
  # @param logger [::Logger,#debug] Logger object.
20
20
  # @param lock_key [String] Obtained lock key that should be expired.
21
21
  # @param acquier_id [String] Acquier identifier.
22
+ # @param access_strategy [Symbol] Lock obtaining strategy.
22
23
  # @param timed [Boolean] Should the lock be wrapped by Timeout with with lock's ttl
23
24
  # @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
24
25
  # @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
@@ -34,13 +35,14 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
34
35
  #
35
36
  # @api private
36
37
  # @since 1.3.0
37
- # @version 1.6.0
38
+ # @version 1.7.0
38
39
  # rubocop:disable Metrics/MethodLength
39
40
  def yield_expire(
40
41
  redis,
41
42
  logger,
42
43
  lock_key,
43
44
  acquier_id,
45
+ access_strategy,
44
46
  timed,
45
47
  ttl_shift,
46
48
  ttl,
@@ -67,15 +69,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
67
69
  end
68
70
  ensure
69
71
  if should_expire
70
- run_non_critical do
71
- logger.debug do
72
- "[redis_queued_locks.expire_lock] " \
73
- "lock_key => '#{lock_key}' " \
74
- "queue_ttl => #{queue_ttl} " \
75
- "acq_id => '#{acquier_id}'"
76
- end
77
- end if log_sampled
78
-
72
+ LogVisitor.expire_lock(
73
+ logger, log_sampled,
74
+ lock_key, queue_ttl, acquier_id, access_strategy
75
+ )
79
76
  redis.call('EXPIRE', lock_key, '0')
80
77
  elsif should_decrease
81
78
  finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
@@ -83,16 +80,10 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
83
80
  decreased_ttl = ttl - spent_time - RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
84
81
 
85
82
  if decreased_ttl > 0
86
- run_non_critical do
87
- logger.debug do
88
- "[redis_queued_locks.decrease_lock] " \
89
- "lock_key => '#{lock_key}' " \
90
- "decreased_ttl => '#{decreased_ttl} " \
91
- "queue_ttl => #{queue_ttl} " \
92
- "acq_id => '#{acquier_id}' " \
93
- end
94
- end if log_sampled
95
-
83
+ LogVisitor.decrease_lock(
84
+ logger, log_sampled,
85
+ lock_key, decreased_ttl, queue_ttl, acquier_id, access_strategy
86
+ )
96
87
  # NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
97
88
  redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
98
89
  # TODO: upload scripts to the redis