redis_queued_locks 0.0.21 → 0.0.23
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.
- 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
|