redis_queued_locks 1.5.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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