redis_queued_locks 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -3
  3. data/README.md +262 -60
  4. data/lib/redis_queued_locks/acquier/acquire_lock/delay_execution.rb +2 -2
  5. data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +239 -12
  6. data/lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb +2 -2
  7. data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +115 -0
  8. data/lib/redis_queued_locks/acquier/acquire_lock.rb +200 -89
  9. data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +3 -3
  10. data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +3 -3
  11. data/lib/redis_queued_locks/acquier/is_locked.rb +2 -2
  12. data/lib/redis_queued_locks/acquier/is_queued.rb +2 -2
  13. data/lib/redis_queued_locks/acquier/keys.rb +2 -2
  14. data/lib/redis_queued_locks/acquier/lock_info.rb +19 -3
  15. data/lib/redis_queued_locks/acquier/locks.rb +13 -4
  16. data/lib/redis_queued_locks/acquier/queue_info.rb +2 -2
  17. data/lib/redis_queued_locks/acquier/queues.rb +4 -4
  18. data/lib/redis_queued_locks/acquier/release_all_locks.rb +4 -4
  19. data/lib/redis_queued_locks/acquier/release_lock.rb +4 -4
  20. data/lib/redis_queued_locks/acquier.rb +1 -1
  21. data/lib/redis_queued_locks/client.rb +50 -22
  22. data/lib/redis_queued_locks/debugger/interface.rb +4 -4
  23. data/lib/redis_queued_locks/debugger.rb +8 -8
  24. data/lib/redis_queued_locks/errors.rb +10 -6
  25. data/lib/redis_queued_locks/instrument/active_support.rb +2 -2
  26. data/lib/redis_queued_locks/instrument/void_notifier.rb +2 -2
  27. data/lib/redis_queued_locks/instrument.rb +2 -2
  28. data/lib/redis_queued_locks/logging/void_logger.rb +10 -10
  29. data/lib/redis_queued_locks/logging.rb +10 -3
  30. data/lib/redis_queued_locks/resource.rb +22 -16
  31. data/lib/redis_queued_locks/utilities.rb +2 -2
  32. data/lib/redis_queued_locks/version.rb +2 -2
  33. data/lib/redis_queued_locks.rb +2 -2
  34. metadata +4 -4
  35. data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +0 -72
@@ -1,34 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  # rubocop:disable Metrics/ModuleLength
6
6
  # rubocop:disable Metrics/MethodLength
7
7
  # rubocop:disable Metrics/ClassLength
8
8
  # rubocop:disable Metrics/BlockNesting
9
+ # rubocop:disable Style/IfInsideElse
9
10
  module RedisQueuedLocks::Acquier::AcquireLock
10
11
  require_relative 'acquire_lock/delay_execution'
11
12
  require_relative 'acquire_lock/with_acq_timeout'
12
- require_relative 'acquire_lock/yield_with_expire'
13
+ require_relative 'acquire_lock/yield_expire'
13
14
  require_relative 'acquire_lock/try_to_lock'
14
15
 
15
- # @since 0.1.0
16
+ # @since 1.0.0
16
17
  extend TryToLock
17
- # @since 0.1.0
18
+ # @since 1.0.0
18
19
  extend DelayExecution
19
- # @since 0.1.0
20
- extend YieldWithExpire
21
- # @since 0.1.0
20
+ # @since 1.3.0
21
+ extend YieldExpire
22
+ # @since 1.0.0
22
23
  extend WithAcqTimeout
23
- # @since 0.1.0
24
+ # @since 1.0.0
24
25
  extend RedisQueuedLocks::Utilities
25
26
 
26
- # @return [Integer] Redis time error (in milliseconds).
27
- #
28
- # @api private
29
- # @since 0.1.0
30
- REDIS_TIMESHIFT_ERROR = 2
31
-
32
27
  class << self
33
28
  # @param redis [RedisClient]
34
29
  # Redis connection client.
@@ -72,14 +67,25 @@ module RedisQueuedLocks::Acquier::AcquireLock
72
67
  # - Metadata can not contain reserved lock data keys;
73
68
  # @option logger [::Logger,#debug]
74
69
  # - Logger object used from the configuration layer (see config[:logger]);
75
- # - See RedisQueuedLocks::Logging::VoidLogger for example;
70
+ # - See `RedisQueuedLocks::Logging::VoidLogger` for example;
71
+ # - Supports `SemanticLogger::Logger` (see "semantic_logger" gem)
76
72
  # @option log_lock_try [Boolean]
77
73
  # - should be logged the each try of lock acquiring (a lot of logs can be generated depending
78
74
  # on your retry configurations);
79
75
  # - see `config[:log_lock_try]`;
80
76
  # @option instrument [NilClass,Any]
81
- # - Custom instrumentation data wich will be passed to the instrumenter's payload
82
- # with :instrument key;
77
+ # - Custom instrumentation data wich will be passed to the instrumenter's payload
78
+ # with :instrument key;
79
+ # @option conflict_strategy [Symbol]
80
+ # - The conflict strategy mode for cases when the process that obtained the lock
81
+ # want to acquire this lock again;
82
+ # - By default uses `:wait_for_lock` strategy;
83
+ # - pre-confured in `config[:default_conflict_strategy]`;
84
+ # - Supports:
85
+ # - `:work_through`;
86
+ # - `:extendable_work_through`;
87
+ # - `:wait_for_lock`;
88
+ # - `:dead_locking`;
83
89
  # @param [Block]
84
90
  # A block of code that should be executed after the successfully acquired lock.
85
91
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
@@ -87,7 +93,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
87
93
  # - If block is given the result of block's yeld will be returned.
88
94
  #
89
95
  # @api private
90
- # @since 0.1.0
96
+ # @since 1.0.0
97
+ # @version 1.3.0
91
98
  def acquire_lock(
92
99
  redis,
93
100
  lock_name,
@@ -110,6 +117,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
110
117
  instrument:,
111
118
  logger:,
112
119
  log_lock_try:,
120
+ conflict_strategy:,
113
121
  &block
114
122
  )
115
123
  # Step 0: Prevent argument type incompatabilities
@@ -129,12 +137,18 @@ module RedisQueuedLocks::Acquier::AcquireLock
129
137
  key == 'ts' ||
130
138
  key == 'ini_ttl' ||
131
139
  key == 'lock_key' ||
132
- key == 'rem_ttl'
140
+ key == 'rem_ttl' ||
141
+ key == 'spc_ext_ttl' ||
142
+ key == 'spc_cnt' ||
143
+ key == 'l_spc_ext_ini_ttl' ||
144
+ key == 'l_spc_ext_ts' ||
145
+ key == 'l_spc_ts'
133
146
  end)
134
147
  raise(
135
148
  RedisQueuedLocks::ArgumentError,
136
- '`:meta` keys can not overlap reserved lock data keys' \
137
- '"acq_id", "ts", "ini_ttl", "lock_key", "rem_ttl"'
149
+ '`:meta` keys can not overlap reserved lock data keys ' \
150
+ '"acq_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
151
+ '"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts"'
138
152
  )
139
153
  end
140
154
 
@@ -223,6 +237,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
223
237
  lock_ttl,
224
238
  queue_ttl,
225
239
  fail_fast,
240
+ conflict_strategy,
226
241
  meta
227
242
  ) => { ok:, result: }
228
243
 
@@ -235,26 +250,75 @@ module RedisQueuedLocks::Acquier::AcquireLock
235
250
 
236
251
  # Step 2.1: analyze an acquirement attempt
237
252
  if ok
238
- run_non_critical do
239
- logger.debug do
240
- "[redis_queued_locks.lock_obtained] " \
241
- "lock_key => '#{result[:lock_key]}' " \
242
- "queue_ttl => #{queue_ttl} " \
243
- "acq_id => '#{acquier_id}' " \
244
- "acq_time => #{acq_time} (ms)"
253
+ # Step X: (instrumentation)
254
+ if acq_process[:result][:process] == :extendable_conflict_work_through
255
+ # instrumetnation: (reentrant lock with ttl extension)
256
+ run_non_critical do
257
+ logger.debug do
258
+ "[redis_queued_locks.extendable_reentrant_lock_obtained] " \
259
+ "lock_key => '#{result[:lock_key]}' " \
260
+ "queue_ttl => #{queue_ttl} " \
261
+ "acq_id => '#{acquier_id}' " \
262
+ "acq_time => #{acq_time} (ms)"
263
+ end
245
264
  end
246
- end
247
265
 
248
- # Step X (instrumentation): lock obtained
249
- run_non_critical do
250
- instrumenter.notify('redis_queued_locks.lock_obtained', {
251
- lock_key: result[:lock_key],
252
- ttl: result[:ttl],
253
- acq_id: result[:acq_id],
254
- ts: result[:ts],
255
- acq_time: acq_time,
256
- instrument:
257
- })
266
+ run_non_critical do
267
+ instrumenter.notify('redis_queued_locks.extendable_reentrant_lock_obtained', {
268
+ lock_key: result[:lock_key],
269
+ ttl: result[:ttl],
270
+ acq_id: result[:acq_id],
271
+ ts: result[:ts],
272
+ acq_time: acq_time,
273
+ instrument:
274
+ })
275
+ end
276
+ elsif acq_process[:result][:process] == :conflict_work_through
277
+ # instrumetnation: (reentrant lock without ttl extension)
278
+ run_non_critical do
279
+ logger.debug do
280
+ "[redis_queued_locks.reentrant_lock_obtained] " \
281
+ "lock_key => '#{result[:lock_key]}' " \
282
+ "queue_ttl => #{queue_ttl} " \
283
+ "acq_id => '#{acquier_id}' " \
284
+ "acq_time => #{acq_time} (ms)"
285
+ end
286
+ end
287
+
288
+ run_non_critical do
289
+ instrumenter.notify('redis_queued_locks.reentrant_lock_obtained', {
290
+ lock_key: result[:lock_key],
291
+ ttl: result[:ttl],
292
+ acq_id: result[:acq_id],
293
+ ts: result[:ts],
294
+ acq_time: acq_time,
295
+ instrument:
296
+ })
297
+ end
298
+ else
299
+ # instrumentation: (classic lock obtain)
300
+ # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
301
+ run_non_critical do
302
+ logger.debug do
303
+ "[redis_queued_locks.lock_obtained] " \
304
+ "lock_key => '#{result[:lock_key]}' " \
305
+ "queue_ttl => #{queue_ttl} " \
306
+ "acq_id => '#{acquier_id}' " \
307
+ "acq_time => #{acq_time} (ms)"
308
+ end
309
+ end
310
+
311
+ # Step X (instrumentation): lock obtained
312
+ run_non_critical do
313
+ instrumenter.notify('redis_queued_locks.lock_obtained', {
314
+ lock_key: result[:lock_key],
315
+ ttl: result[:ttl],
316
+ acq_id: result[:acq_id],
317
+ ts: result[:ts],
318
+ acq_time: acq_time,
319
+ instrument:
320
+ })
321
+ end
258
322
  end
259
323
 
260
324
  # Step 2.1.a: successfully acquired => build the result
@@ -262,54 +326,73 @@ module RedisQueuedLocks::Acquier::AcquireLock
262
326
  lock_key: result[:lock_key],
263
327
  acq_id: result[:acq_id],
264
328
  ts: result[:ts],
265
- ttl: result[:ttl]
329
+ ttl: result[:ttl],
330
+ process: result[:process]
266
331
  }
267
332
  acq_process[:acquired] = true
268
333
  acq_process[:should_try] = false
269
334
  acq_process[:acq_time] = acq_time
270
335
  acq_process[:acq_end_time] = acq_end_time
271
- elsif fail_fast && acq_process[:result] == :fail_fast_no_try
272
- acq_process[:should_try] = false
273
- if raise_errors
274
- raise(
275
- RedisQueuedLocks::LockAlreadyObtainedError,
276
- "Lock \"#{lock_key}\" is already obtained."
277
- )
278
- end
279
336
  else
280
- # Step 2.1.b: failed acquirement => retry
281
- acq_process[:tries] += 1
282
-
283
- if (retry_count != nil && acq_process[:tries] >= retry_count) || fail_fast
284
- # NOTE:
285
- # - reached the retry limit => quit from the loop
286
- # - should fail fast => quit from the loop
337
+ # Step 2.2: failed to acquire. anylize each case and act in accordance
338
+ if acq_process[:result] == :fail_fast_no_try # Step 2.2.a: fail without try
287
339
  acq_process[:should_try] = false
288
- acq_process[:result] = fail_fast ? :fail_fast_after_try : :retry_limit_reached
289
340
 
290
- # NOTE:
291
- # - reached the retry limit => dequeue from the lock queue
292
- # - should fail fast => dequeue from the lock queue
293
- acq_dequeue.call
294
-
295
- # NOTE: check and raise an error
296
- if fail_fast && raise_errors
341
+ if raise_errors
297
342
  raise(
298
343
  RedisQueuedLocks::LockAlreadyObtainedError,
299
344
  "Lock \"#{lock_key}\" is already obtained."
300
345
  )
301
- elsif raise_errors
346
+ end
347
+ elsif acq_process[:result] == :conflict_dead_lock # Step 2.2.b: fail after dead lock
348
+ acq_process[:tries] += 1
349
+ acq_process[:should_try] = false
350
+ acq_process[:result] = :conflict_dead_lock
351
+ acq_dequeue.call
352
+
353
+ if raise_errors
302
354
  raise(
303
- RedisQueuedLocks::LockAcquiermentRetryLimitError,
304
- "Failed to acquire the lock \"#{lock_key}\" " \
305
- "for the given retry_count limit (#{retry_count} times)."
355
+ RedisQueuedLock::ConflictLockObtainError,
356
+ "Lock Conflict: trying to acquire the lock \"#{lock_key}\" " \
357
+ "that is already acquired by the current acquier (acq_id: \"#{acquired_id}\")."
306
358
  )
307
359
  end
308
360
  else
309
- # NOTE:
310
- # delay the exceution in order to prevent chaotic lock-acquire attempts
311
- # and to allow other processes and threads to obtain the lock too.
312
- delay_execution(retry_delay, retry_jitter)
361
+ acq_process[:tries] += 1 # Step RETRY: possible retry case
362
+
363
+ if fail_fast # Step RETRY.A: fail after try
364
+ acq_process[:should_try] = false
365
+ acq_process[:result] = :fail_fast_after_try
366
+ acq_dequeue.call
367
+
368
+ if raise_errors
369
+ raise(
370
+ RedisQueuedLocks::LockAlreadyObtainedError,
371
+ "Lock \"#{lock_key}\" is already obtained."
372
+ )
373
+ end
374
+ else
375
+ # Step RETRY.B: fail cuz the retry count is reached
376
+ if retry_count != nil && acq_process[:tries] >= retry_count
377
+ acq_process[:should_try] = false
378
+ acq_process[:result] = :retry_limit_reached
379
+ acq_dequeue.call
380
+
381
+ if raise_errors
382
+ raise(
383
+ RedisQueuedLocks::LockAcquiermentRetryLimitError,
384
+ "Failed to acquire the lock \"#{lock_key}\" " \
385
+ "for the given retry_count limit (#{retry_count} times)."
386
+ )
387
+ end
388
+ else
389
+ # Step RETRY.X: no significant failures => retry easily :)
390
+ # NOTE:
391
+ # delay the exceution in order to prevent chaotic lock-acquire attempts
392
+ # and to allow other processes and threads to obtain the lock too.
393
+ delay_execution(retry_delay, retry_jitter)
394
+ end
395
+ end
313
396
  end
314
397
  end
315
398
  end
@@ -323,10 +406,18 @@ module RedisQueuedLocks::Acquier::AcquireLock
323
406
  yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
324
407
 
325
408
  ttl_shift = (
326
- (yield_time - acq_process[:acq_end_time]) * 1000 - REDIS_TIMESHIFT_ERROR
409
+ (yield_time - acq_process[:acq_end_time]) * 1000 -
410
+ RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
327
411
  ).ceil(2)
328
412
 
329
- yield_with_expire(
413
+ should_expire =
414
+ acq_process[:result][:process] != :extendable_conflict_work_through &&
415
+ acq_process[:result][:process] != :conflict_work_through
416
+
417
+ should_decrease =
418
+ acq_process[:result][:process] == :extendable_conflict_work_through
419
+
420
+ yield_expire(
330
421
  redis,
331
422
  logger,
332
423
  lock_key,
@@ -335,6 +426,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
335
426
  ttl_shift,
336
427
  ttl,
337
428
  queue_ttl,
429
+ should_expire, # NOTE: should expire the lock after the block execution
430
+ should_decrease, # NOTE: should decrease the lock ttl in reentrant locks?
338
431
  &block
339
432
  )
340
433
  ensure
@@ -343,17 +436,33 @@ module RedisQueuedLocks::Acquier::AcquireLock
343
436
  (acq_process[:rel_time] - acq_process[:acq_end_time]) * 1000
344
437
  ).ceil(2)
345
438
 
346
- # Step X (instrumentation): lock_hold_and_release
347
- run_non_critical do
348
- instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
349
- hold_time: acq_process[:hold_time],
350
- ttl: acq_process[:lock_info][:ttl],
351
- acq_id: acq_process[:lock_info][:acq_id],
352
- ts: acq_process[:lock_info][:ts],
353
- lock_key: acq_process[:lock_info][:lock_key],
354
- acq_time: acq_process[:acq_time],
355
- instrument:
356
- })
439
+ if acq_process[:result][:process] == :extendable_conflict_work_through ||
440
+ acq_process[:result][:process] == :conflict_work_through
441
+ # Step X (instrumentation): reentrant_lock_hold_completes
442
+ run_non_critical do
443
+ instrumenter.notify('redis_queued_locks.reentrant_lock_hold_completes', {
444
+ hold_time: acq_process[:hold_time],
445
+ ttl: acq_process[:lock_info][:ttl],
446
+ acq_id: acq_process[:lock_info][:acq_id],
447
+ ts: acq_process[:lock_info][:ts],
448
+ lock_key: acq_process[:lock_info][:lock_key],
449
+ acq_time: acq_process[:acq_time],
450
+ instrument:
451
+ })
452
+ end
453
+ else
454
+ # Step X (instrumentation): lock_hold_and_release
455
+ run_non_critical do
456
+ instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
457
+ hold_time: acq_process[:hold_time],
458
+ ttl: acq_process[:lock_info][:ttl],
459
+ acq_id: acq_process[:lock_info][:acq_id],
460
+ ts: acq_process[:lock_info][:ts],
461
+ lock_key: acq_process[:lock_info][:lock_key],
462
+ acq_time: acq_process[:acq_time],
463
+ instrument:
464
+ })
465
+ end
357
466
  end
358
467
  end
359
468
  else
@@ -362,11 +471,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
362
471
  else
363
472
  if acq_process[:result] != :retry_limit_reached &&
364
473
  acq_process[:result] != :fail_fast_no_try &&
365
- acq_process[:result] != :fail_fast_after_try
366
- # NOTE: we have only two situations if lock is not acquired withou fast-fail flag:
367
- # - time limit is reached
368
- # - retry count limit is reached
369
- # In other cases the lock obtaining time and tries count are infinite.
474
+ acq_process[:result] != :fail_fast_after_try &&
475
+ acq_process[:result] != :conflict_dead_lock
476
+ # NOTE: we have only two situations if lock is not acquired without explicit failures:
477
+ # - time limit is reached;
478
+ # - retry count limit is reached;
479
+ # - **(notice: in other cases the lock obtaining time and tries count are infinite)
370
480
  acq_process[:result] = :timeout_reached
371
481
  end
372
482
  # Step 3.b: lock is not acquired (acquier is dequeued by timeout callback)
@@ -379,3 +489,4 @@ end
379
489
  # rubocop:enable Metrics/MethodLength
380
490
  # rubocop:enable Metrics/ClassLength
381
491
  # rubocop:enable Metrics/BlockNesting
492
+ # rubocop:enable Style/IfInsideElse
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::ClearDeadRequests
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
@@ -13,7 +13,7 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
13
13
  # @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
14
14
  #
15
15
  # @api private
16
- # @since 0.1.0
16
+ # @since 1.0.0
17
17
  def clear_dead_requests(redis_client, scan_size, dead_ttl, logger, instrumenter, instrument)
18
18
  dead_score = RedisQueuedLocks::Resource.acquier_dead_score(dead_ttl / 1000.0)
19
19
 
@@ -39,7 +39,7 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
39
39
  # @return [Enumerator]
40
40
  #
41
41
  # @api private
42
- # @since 0.1.0
42
+ # @since 1.0.0
43
43
  def each_lock_queue(redis_client, scan_size, &block)
44
44
  redis_client.scan(
45
45
  'MATCH',
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::ExtendLockTTL
6
6
  # @return [String]
7
7
  #
8
8
  # @api private
9
- # @since 0.1.0
9
+ # @since 1.0.0
10
10
  EXTEND_LOCK_PTTL = <<~LUA_SCRIPT.strip.tr("\n", '').freeze
11
11
  local new_lock_pttl = redis.call("PTTL", KEYS[1]) + ARGV[1];
12
12
  return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
@@ -20,7 +20,7 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
20
20
  # @return [Hash<Symbol,Boolean|Symbol>]
21
21
  #
22
22
  # @api private
23
- # @since 0.1.0
23
+ # @since 1.0.0
24
24
  def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
25
25
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
26
26
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::IsLocked
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
@@ -9,7 +9,7 @@ module RedisQueuedLocks::Acquier::IsLocked
9
9
  # @return [Boolean]
10
10
  #
11
11
  # @api private
12
- # @since 0.1.0
12
+ # @since 1.0.0
13
13
  def locked?(redis_client, lock_name)
14
14
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
15
15
  redis_client.call('EXISTS', lock_key) == 1
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::IsQueued
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
@@ -9,7 +9,7 @@ module RedisQueuedLocks::Acquier::IsQueued
9
9
  # @return [Boolean]
10
10
  #
11
11
  # @api private
12
- # @since 0.1.0
12
+ # @since 1.0.0
13
13
  def queued?(redis_client, lock_name)
14
14
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
15
15
  redis_client.call('EXISTS', lock_key_queue) == 1
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::Keys
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
@@ -9,7 +9,7 @@ module RedisQueuedLocks::Acquier::Keys
9
9
  # @return [Array<String>]
10
10
  #
11
11
  # @api private
12
- # @since 0.1.0
12
+ # @since 1.0.0
13
13
  def keys(redis_client, scan_size:)
14
14
  Set.new.tap do |keys|
15
15
  redis_client.scan(
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::LockInfo
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
@@ -12,12 +12,20 @@ module RedisQueuedLocks::Acquier::LockInfo
12
12
  # 'lock_key' => "rql:lock:your_lockname", # acquired lock key
13
13
  # 'acq_id' => "rql:acq:process_id/thread_id", # lock acquier identifier
14
14
  # 'ts' => 123456789.2649841, # <locked at> time stamp (epoch, seconds.microseconds)
15
- # 'ini_ttl' => 123456789, # initial lock key ttl (milliseconds),
15
+ # 'ini_ttl' => 123456789, # initial lock key ttl (milliseconds)
16
16
  # 'rem_ttl' => 123456789, # remaining lock key ttl (milliseconds)
17
+ # <additional keys for reentrant locks>:
18
+ # 'spc_cnt' => 2, # lock reentreing count (if lock was used as reentrant lock)
19
+ # 'l_spc_ts' => 123456.1234 # (epoch) <non-extendable reentrant lock obtained at> timestamp
20
+ # 'spc_ext_ttl' => 14500, # (milliseconds) the sum of all ttl extensions
21
+ # 'l_spc_ext_ini_ttl' => 5000, # (milliseconds) the last ttl of reentrant lock
22
+ # 'l_spc_ext_ts' => 123456.789 # (epoch) <extendable reentrant lock obtained at> timestamp
17
23
  # }
18
24
  #
19
25
  # @api private
20
- # @since 0.1.0
26
+ # @since 1.0.0
27
+ # @version 1.3.0
28
+ # rubocop:disable Metrics/MethodLength
21
29
  def lock_info(redis_client, lock_name)
22
30
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
23
31
 
@@ -48,9 +56,17 @@ module RedisQueuedLocks::Acquier::LockInfo
48
56
  lock_data['ts'] = Float(lock_data['ts'])
49
57
  lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
50
58
  lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
59
+ lock_data['spc_cnt'] = Integer(lock_data['spc_cnt']) if lock_data['spc_cnt']
60
+ lock_data['l_spc_ts'] = Float(lock_data['l_spc_ts']) if lock_data['l_spc_ts']
61
+ lock_data['spc_ext_ttl'] = Integer(lock_data['spc_ext_ttl']) if lock_data['spc_ext_ttl']
62
+ lock_data['l_spc_ext_ini_ttl'] =
63
+ Integer(lock_data['l_spc_ext_ini_ttl']) if lock_data.key?('l_spc_ext_ini_ttl')
64
+ lock_data['l_spc_ext_ts'] =
65
+ Float(lock_data['l_spc_ext_ts']) if lock_data['l_spc_ext_ts']
51
66
  end
52
67
  end
53
68
  end
54
69
  end
70
+ # rubocop:enable Metrics/MethodLength
55
71
  end
56
72
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::Locks
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
@@ -10,7 +10,7 @@ module RedisQueuedLocks::Acquier::Locks
10
10
  # @return [Set<String>,Set<Hash<Symbol,Any>>]
11
11
  #
12
12
  # @api private
13
- # @since 0.1.0
13
+ # @since 1.0.0
14
14
  def locks(redis_client, scan_size:, with_info:)
15
15
  redis_client.with do |rconn|
16
16
  lock_keys = scan_locks(rconn, scan_size)
@@ -25,7 +25,7 @@ module RedisQueuedLocks::Acquier::Locks
25
25
  # @return [Set<String>]
26
26
  #
27
27
  # @api private
28
- # @since 0.1.0
28
+ # @since 1.0.0
29
29
  def scan_locks(redis_client, scan_size)
30
30
  Set.new.tap do |lock_keys|
31
31
  redis_client.scan(
@@ -44,7 +44,8 @@ module RedisQueuedLocks::Acquier::Locks
44
44
  # @return [Set<Hash<Symbol,Any>>]
45
45
  #
46
46
  # @api private
47
- # @since 0.1.0
47
+ # @since 1.0.0
48
+ # @version 1.3.0
48
49
  # rubocop:disable Metrics/MethodLength
49
50
  def extract_locks_info(redis_client, lock_keys)
50
51
  # TODO: refactor with RedisQueuedLocks::Acquier::LockInfo
@@ -72,6 +73,14 @@ module RedisQueuedLocks::Acquier::Locks
72
73
  lock_data['ts'] = Float(lock_data['ts'])
73
74
  lock_data['ini_ttl'] = Integer(lock_data['ini_ttl'])
74
75
  lock_data['rem_ttl'] = ((pttl_cmd_res == -1) ? Infinity : pttl_cmd_res)
76
+ lock_data['spc_cnt'] = Integer(lock_data['spc_cnt']) if lock_data['spc_cnt']
77
+ lock_data['l_spc_ts'] = Float(lock_data['l_spc_ts']) if lock_data['l_spc_ts']
78
+ lock_data['spc_ext_ttl'] =
79
+ Integer(lock_data['spc_ext_ttl']) if lock_data['spc_ext_ttl']
80
+ lock_data['l_spc_ext_ini_ttl'] =
81
+ Integer(lock_data['l_spc_ext_ini_ttl']) if lock_data.key?('l_spc_ext_ini_ttl')
82
+ lock_data['l_spc_ext_ts'] =
83
+ Float(lock_data['l_spc_ext_ts']) if lock_data['l_spc_ext_ts']
75
84
  end
76
85
  end
77
86
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 0.1.0
4
+ # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquier::QueueInfo
6
6
  class << self
7
7
  # Returns an information about the required lock queue by the lock name. The result
@@ -23,7 +23,7 @@ module RedisQueuedLocks::Acquier::QueueInfo
23
23
  # }
24
24
  #
25
25
  # @api private
26
- # @since 0.1.0
26
+ # @since 1.0.0
27
27
  def queue_info(redis_client, lock_name)
28
28
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
29
29