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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +14 -1
- data/lib/redis_queued_locks/acquier/{delay.rb → acquire_lock/delay_execution.rb} +2 -2
- data/lib/redis_queued_locks/acquier/{try.rb → acquire_lock/try_to_lock.rb} +1 -1
- data/lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb +29 -0
- data/lib/redis_queued_locks/acquier/{yield_expire.rb → acquire_lock/yield_with_expire.rb} +9 -2
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +291 -0
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +19 -0
- data/lib/redis_queued_locks/acquier/is_locked.rb +18 -0
- data/lib/redis_queued_locks/acquier/is_queued.rb +18 -0
- data/lib/redis_queued_locks/acquier/keys.rb +26 -0
- data/lib/redis_queued_locks/acquier/lock_info.rb +57 -0
- data/lib/redis_queued_locks/acquier/locks.rb +26 -0
- data/lib/redis_queued_locks/acquier/queue_info.rb +50 -0
- data/lib/redis_queued_locks/acquier/queues.rb +26 -0
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +88 -0
- data/lib/redis_queued_locks/acquier/release_lock.rb +76 -0
- data/lib/redis_queued_locks/acquier.rb +11 -557
- data/lib/redis_queued_locks/client.rb +34 -19
- data/lib/redis_queued_locks/instrument/active_support.rb +1 -1
- data/lib/redis_queued_locks/instrument/void_notifier.rb +1 -1
- data/lib/redis_queued_locks/instrument.rb +3 -3
- data/lib/redis_queued_locks/logging/void_logger.rb +43 -0
- data/lib/redis_queued_locks/logging.rb +57 -0
- data/lib/redis_queued_locks/utilities.rb +16 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- data/lib/redis_queued_locks.rb +3 -0
- metadata +20 -6
- 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
|