redis_queued_locks 1.6.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,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