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