redis_queued_locks 0.0.20 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +14 -1
  4. data/lib/redis_queued_locks/acquier/{delay.rb → acquire_lock/delay_execution.rb} +2 -2
  5. data/lib/redis_queued_locks/acquier/{try.rb → acquire_lock/try_to_lock.rb} +1 -1
  6. data/lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb +29 -0
  7. data/lib/redis_queued_locks/acquier/{yield_expire.rb → acquire_lock/yield_with_expire.rb} +9 -2
  8. data/lib/redis_queued_locks/acquier/acquire_lock.rb +291 -0
  9. data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +19 -0
  10. data/lib/redis_queued_locks/acquier/is_locked.rb +18 -0
  11. data/lib/redis_queued_locks/acquier/is_queued.rb +18 -0
  12. data/lib/redis_queued_locks/acquier/keys.rb +26 -0
  13. data/lib/redis_queued_locks/acquier/lock_info.rb +57 -0
  14. data/lib/redis_queued_locks/acquier/locks.rb +26 -0
  15. data/lib/redis_queued_locks/acquier/queue_info.rb +50 -0
  16. data/lib/redis_queued_locks/acquier/queues.rb +26 -0
  17. data/lib/redis_queued_locks/acquier/release_all_locks.rb +88 -0
  18. data/lib/redis_queued_locks/acquier/release_lock.rb +76 -0
  19. data/lib/redis_queued_locks/acquier.rb +11 -557
  20. data/lib/redis_queued_locks/client.rb +34 -19
  21. data/lib/redis_queued_locks/instrument/active_support.rb +1 -1
  22. data/lib/redis_queued_locks/instrument/void_notifier.rb +1 -1
  23. data/lib/redis_queued_locks/instrument.rb +3 -3
  24. data/lib/redis_queued_locks/logging/void_logger.rb +43 -0
  25. data/lib/redis_queued_locks/logging.rb +57 -0
  26. data/lib/redis_queued_locks/utilities.rb +16 -0
  27. data/lib/redis_queued_locks/version.rb +2 -2
  28. data/lib/redis_queued_locks.rb +3 -0
  29. metadata +20 -6
  30. data/lib/redis_queued_locks/acquier/release.rb +0 -60
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module RedisQueuedLocks::Acquier::Queues
6
+ class << self
7
+ # @param redis_client [RedisClient]
8
+ # @param scan_size [Integer]
9
+ # @return [Set<String>]
10
+ #
11
+ # @api private
12
+ # @since 0.1.0
13
+ def queues(redis_client, scan_size:)
14
+ Set.new.tap do |lock_queues|
15
+ redis_client.scan(
16
+ 'MATCH',
17
+ RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
18
+ count: scan_size
19
+ ) do |lock_queue|
20
+ # TODO: reduce unnecessary iterations
21
+ lock_queues.add(lock_queue)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module RedisQueuedLocks::Acquier::ReleaseAllLocks
6
+ # @since 0.1.0
7
+ extend RedisQueuedLocks::Utilities
8
+
9
+ class << self
10
+ # Release all locks:
11
+ # - 1. clear all lock queus: drop them all from Redis database by the lock queue pattern;
12
+ # - 2. delete all locks: drop lock keys from Redis by the lock key pattern;
13
+ #
14
+ # @param redis [RedisClient]
15
+ # Redis connection client.
16
+ # @param batch_size [Integer]
17
+ # The number of lock keys that should be released in a time.
18
+ # @param isntrumenter [#notify]
19
+ # See RedisQueuedLocks::Instrument::ActiveSupport for example.
20
+ # @param logger [#debug]
21
+ # - Logger object used from `configuration` layer (see config[:logger]);
22
+ # - See RedisQueuedLocks::Logging::VoidLogger for example;
23
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
24
+ # Format: { ok: true/false, result: Hash<Symbol,Numeric> }
25
+ #
26
+ # @api private
27
+ # @since 0.1.0
28
+ def release_all_locks(redis, batch_size, instrumenter, logger)
29
+ rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
30
+ fully_release_all_locks(redis, batch_size) => { ok:, result: }
31
+ time_at = Time.now.to_i
32
+ rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
33
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
34
+
35
+ run_non_critical do
36
+ instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
37
+ at: time_at,
38
+ rel_time: rel_time,
39
+ rel_keys: result[:rel_keys]
40
+ })
41
+ end
42
+
43
+ RedisQueuedLocks::Data[
44
+ ok: true,
45
+ result: { rel_key_cnt: result[:rel_keys], rel_time: rel_time }
46
+ ]
47
+ end
48
+
49
+ private
50
+
51
+ # Release all locks: clear all lock queus and expire all locks.
52
+ #
53
+ # @param redis [RedisClient]
54
+ # @param batch_size [Integer]
55
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
56
+ #
57
+ # @api private
58
+ # @since 0.1.0
59
+ def fully_release_all_locks(redis, batch_size)
60
+ result = redis.pipelined do |pipeline|
61
+ # Step A: release all queus and their related locks
62
+ redis.scan(
63
+ 'MATCH',
64
+ RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
65
+ count: batch_size
66
+ ) do |lock_queue|
67
+ # TODO: reduce unnecessary iterations
68
+ pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
69
+ pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
70
+ end
71
+
72
+ # Step B: release all locks
73
+ redis.scan(
74
+ 'MATCH',
75
+ RedisQueuedLocks::Resource::LOCK_PATTERN,
76
+ count: batch_size
77
+ ) do |lock_key|
78
+ # TODO: reduce unnecessary iterations
79
+ pipeline.call('EXPIRE', lock_key, '0')
80
+ end
81
+ end
82
+
83
+ rel_keys = result.count { |red_res| red_res == 0 }
84
+
85
+ RedisQueuedLocks::Data[ok: true, result: { rel_keys: rel_keys }]
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module RedisQueuedLocks::Acquier::ReleaseLock
6
+ # @since 0.1.0
7
+ extend RedisQueuedLocks::Utilities
8
+
9
+ class << self
10
+ # Release the concrete lock:
11
+ # - 1. clear lock queue: al; related processes released
12
+ # from the lock aquierment and should retry;
13
+ # - 2. delete the lock: drop lock key from Redis;
14
+ # It is safe because the lock obtain logic is transactional and
15
+ # watches the original lock for changes.
16
+ #
17
+ # @param redis [RedisClient]
18
+ # Redis connection client.
19
+ # @param lock_name [String]
20
+ # The lock name that should be released.
21
+ # @param isntrumenter [#notify]
22
+ # See RedisQueuedLocks::Instrument::ActiveSupport for example.
23
+ # @param logger [#debug]
24
+ # - Logger object used from `configuration` layer (see config[:logger]);
25
+ # - See RedisQueuedLocks::Logging::VoidLogger for example;
26
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
27
+ # Format: { ok: true/false, result: Hash<Symbil,Numeric|String> }
28
+ #
29
+ # @api private
30
+ # @since 0.1.0
31
+ def release_lock(redis, lock_name, instrumenter, logger)
32
+ lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
33
+ lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
34
+
35
+ rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
36
+ fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
37
+ time_at = Time.now.to_i
38
+ rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
39
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
40
+
41
+ run_non_critical do
42
+ instrumenter.notify('redis_queued_locks.explicit_lock_release', {
43
+ lock_key: lock_key,
44
+ lock_key_queue: lock_key_queue,
45
+ rel_time: rel_time,
46
+ at: time_at
47
+ })
48
+ end
49
+
50
+ RedisQueuedLocks::Data[
51
+ ok: true,
52
+ result: { rel_time: rel_time, rel_key: lock_key, rel_queue: lock_key_queue }
53
+ ]
54
+ end
55
+
56
+ private
57
+
58
+ # Realease the lock: clear the lock queue and expire the lock.
59
+ #
60
+ # @param redis [RedisClient]
61
+ # @param lock_key [String]
62
+ # @param lock_key_queue [String]
63
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
64
+ #
65
+ # @api private
66
+ # @since 0.1.0
67
+ def fully_release_lock(redis, lock_key, lock_key_queue)
68
+ result = redis.multi do |transact|
69
+ transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
70
+ transact.call('EXPIRE', lock_key, '0')
71
+ end
72
+
73
+ RedisQueuedLocks::Data[ok: true, result:]
74
+ end
75
+ end
76
+ end