redis_queued_locks 0.0.19 → 0.0.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
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
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
21
+ # Format: { ok: true/false, result: Hash<Symbol,Numeric> }
22
+ #
23
+ # @api private
24
+ # @since 0.1.0
25
+ def release_all_locks(redis, batch_size, instrumenter)
26
+ rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
27
+ fully_release_all_locks(redis, batch_size) => { ok:, result: }
28
+ time_at = Time.now.to_i
29
+ rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
30
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
31
+
32
+ run_non_critical do
33
+ instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
34
+ at: time_at,
35
+ rel_time: rel_time,
36
+ rel_keys: result[:rel_keys]
37
+ })
38
+ end
39
+
40
+ RedisQueuedLocks::Data[
41
+ ok: true,
42
+ result: { rel_key_cnt: result[:rel_keys], rel_time: rel_time }
43
+ ]
44
+ end
45
+
46
+ private
47
+
48
+ # Release all locks: clear all lock queus and expire all locks.
49
+ #
50
+ # @param redis [RedisClient]
51
+ # @param batch_size [Integer]
52
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
53
+ #
54
+ # @api private
55
+ # @since 0.1.0
56
+ def fully_release_all_locks(redis, batch_size)
57
+ result = redis.pipelined do |pipeline|
58
+ # Step A: release all queus and their related locks
59
+ redis.scan(
60
+ 'MATCH',
61
+ RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
62
+ count: batch_size
63
+ ) do |lock_queue|
64
+ # TODO: reduce unnecessary iterations
65
+ pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
66
+ pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
67
+ end
68
+
69
+ # Step B: release all locks
70
+ redis.scan(
71
+ 'MATCH',
72
+ RedisQueuedLocks::Resource::LOCK_PATTERN,
73
+ count: batch_size
74
+ ) do |lock_key|
75
+ # TODO: reduce unnecessary iterations
76
+ pipeline.call('EXPIRE', lock_key, '0')
77
+ end
78
+ end
79
+
80
+ rel_keys = result.count { |red_res| red_res == 0 }
81
+
82
+ RedisQueuedLocks::Data[ok: true, result: { rel_keys: rel_keys }]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,73 @@
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
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
24
+ # Format: { ok: true/false, result: Hash<Symbil,Numeric|String> }
25
+ #
26
+ # @api private
27
+ # @since 0.1.0
28
+ def release_lock(redis, lock_name, instrumenter)
29
+ lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
30
+ lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
31
+
32
+ rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
33
+ fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
34
+ time_at = Time.now.to_i
35
+ rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
36
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
37
+
38
+ run_non_critical do
39
+ instrumenter.notify('redis_queued_locks.explicit_lock_release', {
40
+ lock_key: lock_key,
41
+ lock_key_queue: lock_key_queue,
42
+ rel_time: rel_time,
43
+ at: time_at
44
+ })
45
+ end
46
+
47
+ RedisQueuedLocks::Data[
48
+ ok: true,
49
+ result: { rel_time: rel_time, rel_key: lock_key, rel_queue: lock_key_queue }
50
+ ]
51
+ end
52
+
53
+ private
54
+
55
+ # Realease the lock: clear the lock queue and expire the lock.
56
+ #
57
+ # @param redis [RedisClient]
58
+ # @param lock_key [String]
59
+ # @param lock_key_queue [String]
60
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
61
+ #
62
+ # @api private
63
+ # @since 0.1.0
64
+ def fully_release_lock(redis, lock_key, lock_key_queue)
65
+ result = redis.multi do |transact|
66
+ transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
67
+ transact.call('EXPIRE', lock_key, '0')
68
+ end
69
+
70
+ RedisQueuedLocks::Data[ok: true, result:]
71
+ end
72
+ end
73
+ end