redis_queued_locks 0.0.2 → 0.0.3
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 +9 -0
- data/README.md +3 -9
- data/lib/redis_queued_locks/acquier/delay.rb +2 -0
- data/lib/redis_queued_locks/acquier/expire.rb +4 -4
- data/lib/redis_queued_locks/acquier/release.rb +14 -20
- data/lib/redis_queued_locks/acquier.rb +48 -35
- data/lib/redis_queued_locks/client.rb +6 -6
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5dbd11cefb9d18a816eb4c63fe1178f87325c30d75efc2bc806d29f41994fc2c
|
4
|
+
data.tar.gz: 9697f4bf452aaaa7cade45320c50d16b510c657074ba97513c5f8de9c51f6c49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b28ef92375c7eef4d6c43fccb5e44105d653fb00e052edcf67316f8da13578ad7b39086b3799782592b38bf046760f8faa1238b92de293b83eb1e4a7c441413e
|
7
|
+
data.tar.gz: dfd3891ae8d0303b88ac95268056addc3b4e03f862a5200166de9553b412eee78b77f42193f133f9dd340793b67928806e2e05451517585c2282fb7e4c871d1f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.3] - 2024-02-26
|
4
|
+
### Changed
|
5
|
+
- Instrumentation events:
|
6
|
+
- `"redis_queued_locks.explicit_all_locks_release"`
|
7
|
+
- re-factored with fully pipelined invocation;
|
8
|
+
- removed `rel_queue_cnt` and `rel_lock_cnt` because of the pipelined invocation
|
9
|
+
misses the concrete results and now we can receive only "released redis keys count";
|
10
|
+
- adde `rel_keys` payload data (released redis keys);
|
11
|
+
|
3
12
|
## [0.0.2] - 2024-02-26
|
4
13
|
### Added
|
5
14
|
- Instrumentation events:
|
data/README.md
CHANGED
@@ -22,7 +22,6 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
|
|
22
22
|
- `lock_key` - `string` - lock name;
|
23
23
|
- `ts` - `integer`/`epoch` - the time when lock was obtained;
|
24
24
|
- `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
25
|
-
- `rel_time` - `float`/`milliseconds` - time spent on lock "holding + releasing";
|
26
25
|
- `"redis_queued_locks.explicit_lock_release"`
|
27
26
|
- an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
|
28
27
|
- payload:
|
@@ -31,13 +30,8 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
|
|
31
30
|
- `lock_key` - `string` - released lock (lock name);
|
32
31
|
- `lock_key_queue` - `string` - released lock queue (lock queue name);
|
33
32
|
- `"redis_queued_locks.explicit_all_locks_release"`
|
34
|
-
- an event signalizes about the explicit all locks release (invoked
|
33
|
+
- an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
|
35
34
|
- payload:
|
36
|
-
- `rel_time` - `float`/`milliseconds` - time
|
35
|
+
- `rel_time` - `float`/`milliseconds` - time spent on the lock release;
|
37
36
|
- `at` - `integer`/`epoch` - the time when the operation has ended;
|
38
|
-
- `
|
39
|
-
- `rel_queue_cnt` - `integer` - released queue count;
|
40
|
-
|
41
|
-
## Todo
|
42
|
-
|
43
|
-
- CI (github actions);
|
37
|
+
- `rel_keys` - `integer` - released redis keys count (`released queu keys` + `released lock keys`);
|
@@ -3,6 +3,8 @@
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
5
|
module RedisQueuedLocks::Acquier::Delay
|
6
|
+
# Sleep with random time-shifting (it is necessary for empty-time-slot lock acquirement).
|
7
|
+
#
|
6
8
|
# @param retry_delay [Integer] In milliseconds
|
7
9
|
# @param retry_jitter [Integer] In milliseconds
|
8
10
|
# @return [void]
|
@@ -3,10 +3,10 @@
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
5
|
module RedisQueuedLocks::Acquier::Expire
|
6
|
-
# @param redis [RedisClient]
|
7
|
-
# @param lock_key [String]
|
8
|
-
# @param block [Block]
|
9
|
-
# @return [
|
6
|
+
# @param redis [RedisClient] Redis connection manager.
|
7
|
+
# @param lock_key [String] Lock key to be expired.
|
8
|
+
# @param block [Block] Custom logic that should be invoked unter the obtained lock.
|
9
|
+
# @return [Any,NilClass] nil is returned no block parametr is provided.
|
10
10
|
#
|
11
11
|
# @api private
|
12
12
|
# @since 0.1.0
|
@@ -30,37 +30,31 @@ module RedisQueuedLocks::Acquier::Release
|
|
30
30
|
# @api private
|
31
31
|
# @since 0.1.0
|
32
32
|
def fully_release_all_locks(redis, batch_size)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
) do |lock_queue|
|
42
|
-
rel_queue_cnt += 1
|
43
|
-
rel_lock_cnt += 1
|
44
|
-
|
45
|
-
redis.pipelined do |pipeline|
|
33
|
+
result = redis.pipelined do |pipeline|
|
34
|
+
# Step A: release all queus and their related locks
|
35
|
+
redis.scan(
|
36
|
+
'MATCH',
|
37
|
+
RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
|
38
|
+
count: batch_size
|
39
|
+
) do |lock_queue|
|
40
|
+
puts "RELEASE (lock_queue): #{lock_queue}"
|
46
41
|
pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
|
47
|
-
pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue))
|
42
|
+
pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), "0")
|
48
43
|
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Step B: release all locks
|
52
|
-
redis.pipelined do |pipeline|
|
53
|
-
rel_lock_cnt += 1
|
54
44
|
|
45
|
+
# Step B: release all locks
|
55
46
|
redis.scan(
|
56
47
|
'MATCH',
|
57
48
|
RedisQueuedLocks::Resource::LOCK_PATTERN,
|
58
49
|
count: batch_size
|
59
50
|
) do |lock_key|
|
51
|
+
puts "RELEASE (lock_key): #{lock_key}"
|
60
52
|
pipeline.call('EXPIRE', lock_key, '0')
|
61
53
|
end
|
62
54
|
end
|
63
55
|
|
64
|
-
|
56
|
+
rel_keys = result.count { |red_res| red_res == 0 }
|
57
|
+
|
58
|
+
{ ok: true, result: { rel_keys: rel_keys } }
|
65
59
|
end
|
66
60
|
end
|
@@ -43,8 +43,8 @@ module RedisQueuedLocks::Acquier
|
|
43
43
|
# Lifetime of the acuier's lock request. In seconds.
|
44
44
|
# @option timeout [Integer]
|
45
45
|
# Time period whe should try to acquire the lock (in seconds).
|
46
|
-
# @option retry_count [Integer]
|
47
|
-
# How many times we should try to acquire a lock.
|
46
|
+
# @option retry_count [Integer,NilClass]
|
47
|
+
# How many times we should try to acquire a lock. Nil means "infinite retries".
|
48
48
|
# @option retry_delay [Integer]
|
49
49
|
# A time-interval between the each retry (in milliseconds).
|
50
50
|
# @option retry_jitter [Integer]
|
@@ -113,7 +113,7 @@ module RedisQueuedLocks::Acquier
|
|
113
113
|
) => { ok:, result: }
|
114
114
|
|
115
115
|
acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
116
|
-
acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil
|
116
|
+
acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil(2)
|
117
117
|
|
118
118
|
# Step X: save the intermediate results to the result observer
|
119
119
|
acq_process[:result] = result
|
@@ -139,20 +139,20 @@ module RedisQueuedLocks::Acquier
|
|
139
139
|
acq_process[:acquired] = true
|
140
140
|
acq_process[:should_try] = false
|
141
141
|
acq_process[:acq_time] = acq_time
|
142
|
+
acq_process[:acq_end_time] = acq_end_time
|
142
143
|
else
|
143
144
|
# Step 2.1.b: failed acquirement => retry
|
144
145
|
acq_process[:tries] += 1
|
145
146
|
|
146
|
-
if acq_process[:tries] >= retry_count
|
147
|
+
if retry_count != nil && acq_process[:tries] >= retry_count
|
147
148
|
# NOTE: reached the retry limit => quit from the loop
|
148
149
|
acq_process[:should_try] = false
|
149
150
|
# NOTE: reached the retry limit => dequeue from the lock queue
|
150
151
|
acq_dequeue.call
|
151
|
-
|
152
|
+
elsif delay_execution(retry_delay, retry_jitter)
|
152
153
|
# NOTE:
|
153
154
|
# delay the exceution in order to prevent chaotic attempts
|
154
155
|
# and to allow other processes and threads to obtain the lock too.
|
155
|
-
delay_execution(retry_delay, retry_jitter)
|
156
156
|
end
|
157
157
|
end
|
158
158
|
end
|
@@ -163,23 +163,24 @@ module RedisQueuedLocks::Acquier
|
|
163
163
|
# Step 3.a: acquired successfully => run logic or return the result of acquirement
|
164
164
|
if block_given?
|
165
165
|
begin
|
166
|
-
yield_with_expire(redis, lock_key,
|
166
|
+
yield_with_expire(redis, lock_key, &block)
|
167
167
|
ensure
|
168
168
|
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
169
|
-
acq_process[:hold_time] = (
|
170
|
-
acq_process[:rel_time] - acq_process[:
|
171
|
-
)
|
169
|
+
acq_process[:hold_time] = (
|
170
|
+
(acq_process[:rel_time] - acq_process[:acq_end_time]) * 1000
|
171
|
+
).ceil(2)
|
172
172
|
|
173
173
|
# Step X (instrumentation): lock_hold_and_release
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
174
|
+
run_non_critical do
|
175
|
+
instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
|
176
|
+
hold_time: acq_process[:hold_time],
|
177
|
+
ttl: acq_process[:lock_info][:ttl],
|
178
|
+
acq_id: acq_process[:lock_info][:acq_id],
|
179
|
+
ts: acq_process[:lock_info][:ts],
|
180
|
+
lock_key: acq_process[:lock_info][:lock_key],
|
181
|
+
acq_time: acq_process[:acq_time]
|
182
|
+
})
|
183
|
+
end
|
183
184
|
end
|
184
185
|
else
|
185
186
|
{ ok: true, result: acq_process[:lock_info] }
|
@@ -211,17 +212,19 @@ module RedisQueuedLocks::Acquier
|
|
211
212
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
212
213
|
|
213
214
|
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
214
|
-
|
215
|
+
fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
|
215
216
|
time_at = Time.now.to_i
|
216
217
|
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
217
|
-
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
|
218
|
+
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
|
218
219
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
220
|
+
run_non_critical do
|
221
|
+
instrumenter.notify('redis_queued_locks.explicit_lock_release', {
|
222
|
+
lock_key: lock_key,
|
223
|
+
lock_key_queue: lock_key_queue,
|
224
|
+
rel_time: rel_time,
|
225
|
+
at: time_at
|
226
|
+
})
|
227
|
+
end
|
225
228
|
|
226
229
|
{ ok: true, result: result }
|
227
230
|
end
|
@@ -239,17 +242,18 @@ module RedisQueuedLocks::Acquier
|
|
239
242
|
# @since 0.1.0
|
240
243
|
def release_all_locks!(redis, batch_size, instrumenter)
|
241
244
|
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
242
|
-
|
245
|
+
fully_release_all_locks(redis, batch_size) => { ok:, result: }
|
243
246
|
time_at = Time.now.to_i
|
244
247
|
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
245
|
-
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
|
248
|
+
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
|
246
249
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
250
|
+
run_non_critical do
|
251
|
+
instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
|
252
|
+
at: time_at,
|
253
|
+
rel_time: rel_time,
|
254
|
+
rel_keys: result[:rel_keys]
|
255
|
+
})
|
256
|
+
end
|
253
257
|
|
254
258
|
{ ok: true, result: result }
|
255
259
|
end
|
@@ -279,6 +283,15 @@ module RedisQueuedLocks::Acquier
|
|
279
283
|
ERROR_MESSAGE
|
280
284
|
end
|
281
285
|
end
|
286
|
+
|
287
|
+
# @param block [Block]
|
288
|
+
# @return [Any]
|
289
|
+
#
|
290
|
+
# @api private
|
291
|
+
# @since 0.1.0
|
292
|
+
def run_non_critical(&block)
|
293
|
+
yield rescue nil
|
294
|
+
end
|
282
295
|
end
|
283
296
|
# rubocop:enable Metrics/ClassLength
|
284
297
|
end
|
@@ -12,15 +12,15 @@ class RedisQueuedLocks::Client
|
|
12
12
|
setting :retry_jitter, 50 # NOTE: milliseconds
|
13
13
|
setting :default_timeout, 10 # NOTE: seconds
|
14
14
|
setting :exp_precision, 1 # NOTE: milliseconds
|
15
|
-
setting :default_lock_ttl,
|
16
|
-
setting :default_queue_ttl,
|
15
|
+
setting :default_lock_ttl, 5_000 # NOTE: milliseconds
|
16
|
+
setting :default_queue_ttl, 30 # NOTE: seconds
|
17
17
|
setting :lock_release_batch_size, 100
|
18
18
|
setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
|
19
19
|
|
20
20
|
# TODO: setting :logger, Logger.new(IO::NULL)
|
21
21
|
# TODO: setting :debug, true/false
|
22
22
|
|
23
|
-
validate('retry_count'
|
23
|
+
validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
|
24
24
|
validate('retry_delay', :integer)
|
25
25
|
validate('retry_jitter', :integer)
|
26
26
|
validate('default_timeout', :integer)
|
@@ -28,7 +28,7 @@ class RedisQueuedLocks::Client
|
|
28
28
|
validate('default_lock_tt', :integer)
|
29
29
|
validate('default_queue_ttl', :integer)
|
30
30
|
validate('lock_release_batch_size', :integer)
|
31
|
-
validate('instrumenter') { |
|
31
|
+
validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
|
32
32
|
end
|
33
33
|
|
34
34
|
# @return [RedisClient]
|
@@ -63,8 +63,8 @@ class RedisQueuedLocks::Client
|
|
63
63
|
# Lifetime of the acuier's lock request. In seconds.
|
64
64
|
# @option timeout [Integer,NilClass]
|
65
65
|
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
|
66
|
-
# @option retry_count [Integer]
|
67
|
-
# How many times we should try to acquire a lock.
|
66
|
+
# @option retry_count [Integer,NilClass]
|
67
|
+
# How many times we should try to acquire a lock. Nil means "infinite retries".
|
68
68
|
# @option retry_delay [Integer]
|
69
69
|
# A time-interval between the each retry (in milliseconds).
|
70
70
|
# @option retry_jitter [Integer]
|