redis_queued_locks 1.1.0 → 1.3.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.
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