redis_queued_locks 0.0.21 → 0.0.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/README.md +11 -0
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +77 -71
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +8 -1
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +30 -9
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +2 -1
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +25 -20
- data/lib/redis_queued_locks/acquier/release_lock.rb +9 -4
- data/lib/redis_queued_locks/client.rb +18 -11
- 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/version.rb +2 -2
- data/lib/redis_queued_locks.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05a8fc14cae41bf9870bedf4eae9fa298c3704b57d25df428c2e2c8bd9bf0223
|
4
|
+
data.tar.gz: 14a941048edc7679df1d80805a96d3db4019d45acfc80ac5444d99e12cc3eadc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 292f4ed74242a1a53b96e290e7759db0b1c918070f0875805bedbe78360fac71b6ac81ec09b63836002b2a17ab247ca06f9cf7ff828a4a07f924b2588e24fd3f
|
7
|
+
data.tar.gz: 270749b6a32418e2a57b3995b5ef3b826b6066a97a56efd08f9305f0ed314ed7da13f9f395f891ab619ceea3d4bde53c880278758fbf42fb1e84b98fa5b14690
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.23] - 2024-03-21
|
4
|
+
### Changed
|
5
|
+
- Composed redis commands are invoked on the one conenction
|
6
|
+
(instead of mutiple connection fetchiong from redis connection pool on each redis command);
|
7
|
+
|
8
|
+
## [0.0.22] - 2024-03-21
|
9
|
+
### Added
|
10
|
+
- Logging infrastructure. Initial implementation includes the only debugging features.
|
11
|
+
|
3
12
|
## [0.0.21] - 2024-03-19
|
4
13
|
### Changed
|
5
|
-
- `RedisQueuedLocks::Acquier
|
14
|
+
- Refactored `RedisQueuedLocks::Acquier`;
|
6
15
|
|
7
16
|
## [0.0.20] - 2024-03-14
|
8
17
|
### Added
|
data/README.md
CHANGED
@@ -134,6 +134,16 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
134
134
|
# - it is calculated once per `RedisQueudLocks::Client` instance;
|
135
135
|
# - expects the proc object;
|
136
136
|
config.uniq_identifier = -> { RedisQueuedLocks::Resource.calc_uniq_identity }
|
137
|
+
|
138
|
+
# (default: RedisQueuedLocks::Logging::VoidLogger)
|
139
|
+
# - the logger object;
|
140
|
+
# - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
|
141
|
+
# - at this moment the only debug logs are realised in 3 cases:
|
142
|
+
# - start of lock obtaining: "[redis_queud_locks.start_lock_obtaining] lock_key => 'rql:lock:your_lock'"
|
143
|
+
# - finish of the lock obtaining: "[redis_queued_locks.lock_obtained] lock_key => 'rql:lock:your_lock' acq_time => 123.456 (ms)"
|
144
|
+
# - start of the lock expiration after `yield`: "[redis_queud_locks.expire_lock] lock_key => 'rql:lock:your_lock'"
|
145
|
+
# - by default uses VoidLogger that does nothing;
|
146
|
+
config.logger = RedisQueuedLocks::Logging::VoidLogger
|
137
147
|
end
|
138
148
|
```
|
139
149
|
|
@@ -581,6 +591,7 @@ Detalized event semantics and payload structure:
|
|
581
591
|
- an ability to add custom metadata to the lock and an ability to read this data;
|
582
592
|
- lock prioritization;
|
583
593
|
- support for LIFO strategy;
|
594
|
+
- structured logging;
|
584
595
|
- **Minor**
|
585
596
|
- GitHub Actions CI;
|
586
597
|
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
@@ -31,95 +31,101 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
31
31
|
timestamp = nil
|
32
32
|
|
33
33
|
# Step 0: watch the lock key changes (and discard acquirement if lock is already acquired)
|
34
|
-
result = redis.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
RedisQueuedLocks.debug(
|
44
|
-
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
45
|
-
)
|
46
|
-
|
47
|
-
# Step 2.1: drop expired acquiers from the lock queue
|
48
|
-
res = redis.call(
|
49
|
-
'ZREMRANGEBYSCORE',
|
50
|
-
lock_key_queue,
|
51
|
-
'-inf',
|
52
|
-
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
53
|
-
)
|
54
|
-
|
55
|
-
RedisQueuedLocks.debug(
|
56
|
-
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
57
|
-
)
|
58
|
-
|
59
|
-
# Step 3: get the actual acquier waiting in the queue
|
60
|
-
waiting_acquier = Array(redis.call('ZRANGE', lock_key_queue, '0', '0')).first
|
61
|
-
|
62
|
-
RedisQueuedLocks.debug(
|
63
|
-
"Step №3: какой процесс в очереди сейчас ждет. " \
|
64
|
-
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
65
|
-
)
|
66
|
-
|
67
|
-
# Step 4: check the actual acquier: is it ours? are we aready to lock?
|
68
|
-
unless waiting_acquier == acquier_id
|
69
|
-
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
34
|
+
result = redis.with do |rconn|
|
35
|
+
rconn.multi(watch: [lock_key]) do |transact|
|
36
|
+
# Fast-Step X0: fail-fast check
|
37
|
+
if fail_fast && rconn.call('HGET', lock_key, 'acq_id')
|
38
|
+
# Fast-Step X1: is lock already obtained. fail fast - no try.
|
39
|
+
inter_result = :fail_fast_no_try
|
40
|
+
else
|
41
|
+
# Step 1: add an acquier to the lock acquirement queue
|
42
|
+
res = rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
70
43
|
|
71
44
|
RedisQueuedLocks.debug(
|
72
|
-
"Step
|
73
|
-
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
45
|
+
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
74
46
|
)
|
75
47
|
|
76
|
-
|
77
|
-
|
78
|
-
|
48
|
+
# Step 2.1: drop expired acquiers from the lock queue
|
49
|
+
res = rconn.call(
|
50
|
+
'ZREMRANGEBYSCORE',
|
51
|
+
lock_key_queue,
|
52
|
+
'-inf',
|
53
|
+
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
54
|
+
)
|
55
|
+
|
56
|
+
RedisQueuedLocks.debug(
|
57
|
+
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
58
|
+
)
|
79
59
|
|
80
|
-
# Step
|
81
|
-
|
60
|
+
# Step 3: get the actual acquier waiting in the queue
|
61
|
+
waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
|
82
62
|
|
83
63
|
RedisQueuedLocks.debug(
|
84
|
-
"
|
85
|
-
"[
|
86
|
-
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
64
|
+
"Step №3: какой процесс в очереди сейчас ждет. " \
|
65
|
+
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
87
66
|
)
|
88
67
|
|
89
|
-
|
90
|
-
|
68
|
+
# Step 4: check the actual acquier: is it ours? are we aready to lock?
|
69
|
+
unless waiting_acquier == acquier_id
|
70
|
+
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
91
71
|
|
92
72
|
RedisQueuedLocks.debug(
|
93
|
-
"Step ROLLBACK №
|
94
|
-
"[
|
73
|
+
"Step ROLLBACK №1: не одинаковые ключи. выходим. " \
|
74
|
+
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
95
75
|
)
|
96
76
|
|
97
|
-
inter_result = :
|
77
|
+
inter_result = :acquier_is_not_first_in_queue
|
98
78
|
else
|
99
|
-
# NOTE:
|
79
|
+
# NOTE: our time has come! let's try to acquire the lock!
|
100
80
|
|
101
|
-
# Step
|
102
|
-
|
81
|
+
# Step 5: check if the our lock is already acquired
|
82
|
+
locked_by_acquier = rconn.call('HGET', lock_key, 'acq_id')
|
103
83
|
|
84
|
+
# rubocop:disable Layout/LineLength
|
104
85
|
RedisQueuedLocks.debug(
|
105
|
-
|
86
|
+
"Ste №5: Ищем требуемый лок. " \
|
87
|
+
"[HGET<#{lock_key}>: " \
|
88
|
+
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
106
89
|
)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
90
|
+
# rubocop:enable Layout/LineLength
|
91
|
+
|
92
|
+
if locked_by_acquier
|
93
|
+
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
94
|
+
|
95
|
+
RedisQueuedLocks.debug(
|
96
|
+
"Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
|
97
|
+
"[Занят процессом: #{locked_by_acquier}]"
|
98
|
+
)
|
99
|
+
|
100
|
+
inter_result = :lock_is_still_acquired
|
101
|
+
else
|
102
|
+
# NOTE: required lock is free and ready to be acquired! acquire!
|
103
|
+
|
104
|
+
# Step 6.1: remove our acquier from waiting queue
|
105
|
+
transact.call('ZPOPMIN', lock_key_queue, '1')
|
106
|
+
|
107
|
+
RedisQueuedLocks.debug(
|
108
|
+
'Step №4: Забираем наш текущий процесс из очереди. [ZPOPMIN]'
|
109
|
+
)
|
110
|
+
|
111
|
+
# rubocop:disable Layout/LineLength
|
112
|
+
RedisQueuedLocks.debug(
|
113
|
+
"===> <FINAL> Step №6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
|
114
|
+
)
|
115
|
+
# rubocop:enable Layout/LineLength
|
116
|
+
|
117
|
+
# Step 6.2: acquire a lock and store an info about the acquier
|
118
|
+
transact.call(
|
119
|
+
'HSET',
|
120
|
+
lock_key,
|
121
|
+
'acq_id', acquier_id,
|
122
|
+
'ts', (timestamp = Time.now.to_i),
|
123
|
+
'ini_ttl', ttl
|
124
|
+
)
|
125
|
+
|
126
|
+
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
127
|
+
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
128
|
+
end
|
123
129
|
end
|
124
130
|
end
|
125
131
|
end
|
@@ -3,7 +3,11 @@
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
5
|
module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
6
|
+
# @since 0.1.0
|
7
|
+
extend RedisQueuedLocks::Utilities
|
8
|
+
|
6
9
|
# @param redis [RedisClient] Redis connection manager.
|
10
|
+
# @param logger [#debug] Logger object.
|
7
11
|
# @param lock_key [String] Lock key to be expired.
|
8
12
|
# @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
|
9
13
|
# @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
|
@@ -13,7 +17,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
13
17
|
#
|
14
18
|
# @api private
|
15
19
|
# @since 0.1.0
|
16
|
-
def yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
|
20
|
+
def yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
|
17
21
|
if block_given?
|
18
22
|
if timed && ttl != nil
|
19
23
|
timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
|
@@ -23,6 +27,9 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
23
27
|
end
|
24
28
|
end
|
25
29
|
ensure
|
30
|
+
run_non_critical do
|
31
|
+
logger.debug("[redis_queued_locks.expire_lock] lock_key => '#{lock_key}'")
|
32
|
+
end
|
26
33
|
redis.call('EXPIRE', lock_key, '0')
|
27
34
|
end
|
28
35
|
|
@@ -69,6 +69,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
69
69
|
# already obtained.
|
70
70
|
# @option metadata [NilClass,Any]
|
71
71
|
# - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
|
72
|
+
# @option logger [#debug]
|
73
|
+
# - Logger object used from `configuration` layer (see config[:logger]);
|
74
|
+
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
72
75
|
# @param [Block]
|
73
76
|
# A block of code that should be executed after the successfully acquired lock.
|
74
77
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -96,6 +99,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
96
99
|
identity:,
|
97
100
|
fail_fast:,
|
98
101
|
metadata:,
|
102
|
+
logger:,
|
99
103
|
&block
|
100
104
|
)
|
101
105
|
# Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
|
@@ -127,6 +131,13 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
127
131
|
}
|
128
132
|
acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
|
129
133
|
|
134
|
+
run_non_critical do
|
135
|
+
logger.debug(
|
136
|
+
"[redis_queued_locks.start_lock_obtaining] " \
|
137
|
+
"lock_key => '#{lock_key}'"
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
130
141
|
# Step 2: try to lock with timeout
|
131
142
|
with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
|
132
143
|
acq_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
@@ -153,15 +164,25 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
153
164
|
|
154
165
|
# Step 2.1: analyze an acquirement attempt
|
155
166
|
if ok
|
167
|
+
run_non_critical do
|
168
|
+
logger.debug(
|
169
|
+
"[redis_queued_locks.lock_obtained] " \
|
170
|
+
"lock_key => '#{result[:lock_key]}'" \
|
171
|
+
"acq_time => #{acq_time} (ms)"
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
156
175
|
# Step X (instrumentation): lock obtained
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
176
|
+
run_non_critical do
|
177
|
+
instrumenter.notify('redis_queued_locks.lock_obtained', {
|
178
|
+
lock_key: result[:lock_key],
|
179
|
+
ttl: result[:ttl],
|
180
|
+
acq_id: result[:acq_id],
|
181
|
+
ts: result[:ts],
|
182
|
+
acq_time: acq_time,
|
183
|
+
meta: metadata
|
184
|
+
})
|
185
|
+
end
|
165
186
|
|
166
187
|
# Step 2.1.a: successfully acquired => build the result
|
167
188
|
acq_process[:lock_info] = {
|
@@ -225,7 +246,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
225
246
|
begin
|
226
247
|
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
227
248
|
ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
|
228
|
-
yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
|
249
|
+
yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
|
229
250
|
ensure
|
230
251
|
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
231
252
|
acq_process[:hold_time] = (
|
@@ -7,11 +7,12 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
|
|
7
7
|
# @param redis_client [RedisClient]
|
8
8
|
# @param lock_name [String]
|
9
9
|
# @param milliseconds [Integer]
|
10
|
+
# @param logger [#debug]
|
10
11
|
# @return [?]
|
11
12
|
#
|
12
13
|
# @api private
|
13
14
|
# @since 0.1.0
|
14
|
-
def extend_lock_ttl(redis_client, lock_name, milliseconds)
|
15
|
+
def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
|
15
16
|
# TODO: realize
|
16
17
|
end
|
17
18
|
end
|
@@ -17,12 +17,15 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
17
17
|
# The number of lock keys that should be released in a time.
|
18
18
|
# @param isntrumenter [#notify]
|
19
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;
|
20
23
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
|
21
24
|
# Format: { ok: true/false, result: Hash<Symbol,Numeric> }
|
22
25
|
#
|
23
26
|
# @api private
|
24
27
|
# @since 0.1.0
|
25
|
-
def release_all_locks(redis, batch_size, instrumenter)
|
28
|
+
def release_all_locks(redis, batch_size, instrumenter, logger)
|
26
29
|
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
27
30
|
fully_release_all_locks(redis, batch_size) => { ok:, result: }
|
28
31
|
time_at = Time.now.to_i
|
@@ -54,26 +57,28 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
54
57
|
# @api private
|
55
58
|
# @since 0.1.0
|
56
59
|
def fully_release_all_locks(redis, batch_size)
|
57
|
-
result = redis.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
60
|
+
result = redis.with do |rconn|
|
61
|
+
rconn.pipelined do |pipeline|
|
62
|
+
# Step A: release all queus and their related locks
|
63
|
+
rconn.scan(
|
64
|
+
'MATCH',
|
65
|
+
RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
|
66
|
+
count: batch_size
|
67
|
+
) do |lock_queue|
|
68
|
+
# TODO: reduce unnecessary iterations
|
69
|
+
pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
|
70
|
+
pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
|
71
|
+
end
|
68
72
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
# Step B: release all locks
|
74
|
+
rconn.scan(
|
75
|
+
'MATCH',
|
76
|
+
RedisQueuedLocks::Resource::LOCK_PATTERN,
|
77
|
+
count: batch_size
|
78
|
+
) do |lock_key|
|
79
|
+
# TODO: reduce unnecessary iterations
|
80
|
+
pipeline.call('EXPIRE', lock_key, '0')
|
81
|
+
end
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
@@ -20,12 +20,15 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
20
20
|
# The lock name that should be released.
|
21
21
|
# @param isntrumenter [#notify]
|
22
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;
|
23
26
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
|
24
27
|
# Format: { ok: true/false, result: Hash<Symbil,Numeric|String> }
|
25
28
|
#
|
26
29
|
# @api private
|
27
30
|
# @since 0.1.0
|
28
|
-
def release_lock(redis, lock_name, instrumenter)
|
31
|
+
def release_lock(redis, lock_name, instrumenter, logger)
|
29
32
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
30
33
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
31
34
|
|
@@ -62,9 +65,11 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
62
65
|
# @api private
|
63
66
|
# @since 0.1.0
|
64
67
|
def fully_release_lock(redis, lock_key, lock_key_queue)
|
65
|
-
result = redis.
|
66
|
-
|
67
|
-
|
68
|
+
result = redis.with do |rconn|
|
69
|
+
rconn.multi do |transact|
|
70
|
+
transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
|
71
|
+
transact.call('EXPIRE', lock_key, '0')
|
72
|
+
end
|
68
73
|
end
|
69
74
|
|
70
75
|
RedisQueuedLocks::Data[ok: true, result:]
|
@@ -9,18 +9,16 @@ class RedisQueuedLocks::Client
|
|
9
9
|
|
10
10
|
configuration do
|
11
11
|
setting :retry_count, 3
|
12
|
-
setting :retry_delay, 200 # NOTE: milliseconds
|
13
|
-
setting :retry_jitter, 25 # NOTE: milliseconds
|
14
|
-
setting :try_to_lock_timeout, 10 # NOTE: seconds
|
15
|
-
setting :default_lock_ttl, 5_000 # NOTE: milliseconds
|
16
|
-
setting :default_queue_ttl, 15 # NOTE: seconds
|
12
|
+
setting :retry_delay, 200 # NOTE: in milliseconds
|
13
|
+
setting :retry_jitter, 25 # NOTE: in milliseconds
|
14
|
+
setting :try_to_lock_timeout, 10 # NOTE: in seconds
|
15
|
+
setting :default_lock_ttl, 5_000 # NOTE: in milliseconds
|
16
|
+
setting :default_queue_ttl, 15 # NOTE: in seconds
|
17
17
|
setting :lock_release_batch_size, 100
|
18
18
|
setting :key_extraction_batch_size, 500
|
19
19
|
setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
|
20
20
|
setting :uniq_identifier, -> { RedisQueuedLocks::Resource.calc_uniq_identity }
|
21
|
-
|
22
|
-
# TODO: setting :logger, Logger.new(IO::NULL)
|
23
|
-
# TODO: setting :debug, true/false
|
21
|
+
setting :logger, RedisQueuedLocks::Logging::VoidLogger
|
24
22
|
|
25
23
|
validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
|
26
24
|
validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
|
@@ -30,6 +28,7 @@ class RedisQueuedLocks::Client
|
|
30
28
|
validate('default_queue_ttl', :integer)
|
31
29
|
validate('lock_release_batch_size', :integer)
|
32
30
|
validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
|
31
|
+
validate('logger') { |val| RedisQueuedLocks::Logging.valid_interface?(val) }
|
33
32
|
validate('uniq_identifier', :proc)
|
34
33
|
end
|
35
34
|
|
@@ -136,6 +135,7 @@ class RedisQueuedLocks::Client
|
|
136
135
|
identity:,
|
137
136
|
fail_fast:,
|
138
137
|
metadata:,
|
138
|
+
logger: config[:logger],
|
139
139
|
&block
|
140
140
|
)
|
141
141
|
end
|
@@ -186,7 +186,8 @@ class RedisQueuedLocks::Client
|
|
186
186
|
RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
|
187
187
|
redis_client,
|
188
188
|
lock_name,
|
189
|
-
config[:instrumenter]
|
189
|
+
config[:instrumenter],
|
190
|
+
config[:logger]
|
190
191
|
)
|
191
192
|
end
|
192
193
|
|
@@ -233,7 +234,12 @@ class RedisQueuedLocks::Client
|
|
233
234
|
# @api public
|
234
235
|
# @since 0.1.0
|
235
236
|
def extend_lock_ttl(lock_name, milliseconds)
|
236
|
-
RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
|
237
|
+
RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
|
238
|
+
redis_client,
|
239
|
+
lock_name,
|
240
|
+
milliseconds,
|
241
|
+
config[:logger]
|
242
|
+
)
|
237
243
|
end
|
238
244
|
|
239
245
|
# @option batch_size [Integer]
|
@@ -246,7 +252,8 @@ class RedisQueuedLocks::Client
|
|
246
252
|
RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
|
247
253
|
redis_client,
|
248
254
|
batch_size,
|
249
|
-
config[:instrumenter]
|
255
|
+
config[:instrumenter],
|
256
|
+
config[:logger]
|
250
257
|
)
|
251
258
|
end
|
252
259
|
|
@@ -8,7 +8,7 @@ module RedisQueuedLocks::Instrument::ActiveSupport
|
|
8
8
|
# @param payload [Hash<String|Symbol,Any>]
|
9
9
|
# @return [void]
|
10
10
|
#
|
11
|
-
# @api
|
11
|
+
# @api public
|
12
12
|
# @since 0.1.0
|
13
13
|
def notify(event, payload = {})
|
14
14
|
::ActiveSupport::Notifications.instrument(event, payload)
|
@@ -25,10 +25,10 @@ module RedisQueuedLocks::Instrument
|
|
25
25
|
m_obj = instrumenter.method(:notify)
|
26
26
|
m_sig = m_obj.parameters
|
27
27
|
|
28
|
-
f_prm = m_sig[0][0]
|
29
|
-
s_prm = m_sig[1][0]
|
30
|
-
|
31
28
|
if m_sig.size == 2
|
29
|
+
f_prm = m_sig[0][0]
|
30
|
+
s_prm = m_sig[1][0]
|
31
|
+
|
32
32
|
# rubocop:disable Layout/MultilineOperationIndentation
|
33
33
|
# NOTE: check the signature vairants
|
34
34
|
f_prm == :req && s_prm == :req ||
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api public
|
4
|
+
# @since 0.1.0
|
5
|
+
module RedisQueuedLocks::Logging::VoidLogger
|
6
|
+
class << self
|
7
|
+
# @api public
|
8
|
+
# @since 0.1.0
|
9
|
+
def warn(progname = nil, &block); end
|
10
|
+
|
11
|
+
# @api public
|
12
|
+
# @since 0.1.0
|
13
|
+
def unknown(progname = nil, &block); end
|
14
|
+
|
15
|
+
# @api public
|
16
|
+
# @since 0.1.0
|
17
|
+
def log(progname = nil, &block); end
|
18
|
+
|
19
|
+
# @api public
|
20
|
+
# @since 0.1.0
|
21
|
+
def info(progname = nil, &block); end
|
22
|
+
|
23
|
+
# @api public
|
24
|
+
# @since 0.1.0
|
25
|
+
def error(progname = nil, &block); end
|
26
|
+
|
27
|
+
# @api public
|
28
|
+
# @since 0.1.0
|
29
|
+
def fatal(progname = nil, &block); end
|
30
|
+
|
31
|
+
# @api public
|
32
|
+
# @since 0.1.0
|
33
|
+
def debug(progname = nil, &block); end
|
34
|
+
|
35
|
+
# @api public
|
36
|
+
# @since 0.1.0
|
37
|
+
def add(*, &block); end
|
38
|
+
|
39
|
+
# @api public
|
40
|
+
# @since 0.1.0
|
41
|
+
def <<(message); end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api public
|
4
|
+
# @since 0.1.0
|
5
|
+
module RedisQueuedLocks::Logging
|
6
|
+
require_relative 'logging/void_logger'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# @param logger [#debug]
|
10
|
+
# @return [Boolean]
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
# @since 0.1.0
|
14
|
+
def valid_interface?(logger)
|
15
|
+
return true if logger.is_a?(::Logger)
|
16
|
+
|
17
|
+
# NOTE: should provide `#debug` method.
|
18
|
+
return false unless logger.respond_to?(:debug)
|
19
|
+
|
20
|
+
# NOTE:
|
21
|
+
# `#debug` method should have appropriated signature `(progname, &block)`
|
22
|
+
# Required method signature (progname, &block):
|
23
|
+
# => [[:opt, :progname], [:block, :block]]
|
24
|
+
# => [[:req, :progname], [:block, :block]]
|
25
|
+
# => [[:opt, :progname]]
|
26
|
+
# => [[:req, :progname]]
|
27
|
+
# => [[:rest], [:block, :block]]
|
28
|
+
# => [[:rest]]
|
29
|
+
|
30
|
+
m_obj = logger.method(:debug)
|
31
|
+
m_sig = m_obj.parameters
|
32
|
+
|
33
|
+
if m_sig.size == 2
|
34
|
+
# => [[:opt, :progname], [:block, :block]]
|
35
|
+
# => [[:req, :progname], [:block, :block]]
|
36
|
+
# => [[:rest], [:block, :block]]
|
37
|
+
f_prm = m_sig[0][0]
|
38
|
+
s_prm = m_sig[1][0]
|
39
|
+
|
40
|
+
# rubocop:disable Layout/MultilineOperationIndentation
|
41
|
+
f_prm == :opt && s_prm == :block ||
|
42
|
+
f_prm == :req && s_prm == :block ||
|
43
|
+
f_prm == :rest && s_prm == :block
|
44
|
+
# rubocop:enable Layout/MultilineOperationIndentation
|
45
|
+
elsif m_sig.size == 1
|
46
|
+
# => [[:opt, :progname]]
|
47
|
+
# => [[:req, :progname]]
|
48
|
+
# => [[:rest]]
|
49
|
+
prm = m_sig[0][0]
|
50
|
+
|
51
|
+
prm == :opt || prm == :req || prm == :rest
|
52
|
+
else
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/redis_queued_locks.rb
CHANGED
@@ -4,6 +4,7 @@ require 'redis-client'
|
|
4
4
|
require 'qonfig'
|
5
5
|
require 'timeout'
|
6
6
|
require 'securerandom'
|
7
|
+
require 'logger'
|
7
8
|
|
8
9
|
# @api public
|
9
10
|
# @since 0.1.0
|
@@ -11,6 +12,7 @@ module RedisQueuedLocks
|
|
11
12
|
require_relative 'redis_queued_locks/version'
|
12
13
|
require_relative 'redis_queued_locks/errors'
|
13
14
|
require_relative 'redis_queued_locks/utilities'
|
15
|
+
require_relative 'redis_queued_locks/logging'
|
14
16
|
require_relative 'redis_queued_locks/data'
|
15
17
|
require_relative 'redis_queued_locks/debugger'
|
16
18
|
require_relative 'redis_queued_locks/resource'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_queued_locks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.23
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rustam Ibragimov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -79,6 +79,8 @@ files:
|
|
79
79
|
- lib/redis_queued_locks/instrument.rb
|
80
80
|
- lib/redis_queued_locks/instrument/active_support.rb
|
81
81
|
- lib/redis_queued_locks/instrument/void_notifier.rb
|
82
|
+
- lib/redis_queued_locks/logging.rb
|
83
|
+
- lib/redis_queued_locks/logging/void_logger.rb
|
82
84
|
- lib/redis_queued_locks/resource.rb
|
83
85
|
- lib/redis_queued_locks/utilities.rb
|
84
86
|
- lib/redis_queued_locks/version.rb
|