redis_queued_locks 0.0.1 → 0.0.2
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 +8 -0
- data/README.md +27 -2
- data/lib/redis_queued_locks/acquier/release.rb +15 -3
- data/lib/redis_queued_locks/acquier.rb +54 -10
- data/lib/redis_queued_locks/client.rb +14 -5
- 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: e984603ea8f509f8e5f7f685c356d1f838b16bc950e1de83ff8f66e6afa83fcf
|
4
|
+
data.tar.gz: 1b2eee0f32c911caecf08214b2b404d620ffb10501ae72d7721235b64fc02362
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7e2d574eeeff4a9a235f39ee25bde152880dcd12e7066196a8304e54098480ef05672edd89a6931ece225711b96602f23ce76304c555dbd623d30099e35ca0a
|
7
|
+
data.tar.gz: d7f5075bbf136e672ebb1632da5c6a78b2b72886bc1d5e8b0d9425cf38b14f5eef5a097fdc835c78f5f2d26885512279926a33bdbdb559151b875453d5312c9e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.2] - 2024-02-26
|
4
|
+
### Added
|
5
|
+
- Instrumentation events:
|
6
|
+
- `"redis_queued_locks.lock_obtained"`;
|
7
|
+
- `"redis_queued_locks.lock_hold_and_release"`;
|
8
|
+
- `"redis_queued_locks.explicit_lock_release"`;
|
9
|
+
- `"redis_queued_locks.explicit_all_locks_release"`;
|
10
|
+
|
3
11
|
## [0.0.1] - 2024-02-26
|
4
12
|
|
5
13
|
- Still the initial release version;
|
data/README.md
CHANGED
@@ -5,13 +5,38 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
|
|
5
5
|
## Instrumentation events
|
6
6
|
|
7
7
|
- `"redis_queued_locks.lock_obtained"`
|
8
|
-
-
|
8
|
+
- a moment when the lock was obtained;
|
9
9
|
- payload:
|
10
10
|
- `ttl` - `integer`/`milliseconds` - lock ttl;
|
11
11
|
- `acq_id` - `string` - lock acquier identifier;
|
12
|
-
- `lock_key` - `string` - lock name
|
12
|
+
- `lock_key` - `string` - lock name;
|
13
13
|
- `ts` - `integer`/`epoch` - the time when the lock was obtaiend;
|
14
14
|
- `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
15
|
+
- `"redis_queued_locks.lock_hold_and_release"`
|
16
|
+
- an event signalizes about the "hold+and+release" process
|
17
|
+
when the lock obtained and hold by the block of logic;
|
18
|
+
- payload:
|
19
|
+
- `hold_time` - `float`/`milliseconds` - lock hold time;
|
20
|
+
- `ttl` - `integer`/`milliseconds` - lock ttl;
|
21
|
+
- `acq_id` - `string` - lock acquier identifier;
|
22
|
+
- `lock_key` - `string` - lock name;
|
23
|
+
- `ts` - `integer`/`epoch` - the time when lock was obtained;
|
24
|
+
- `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
25
|
+
- `rel_time` - `float`/`milliseconds` - time spent on lock "holding + releasing";
|
26
|
+
- `"redis_queued_locks.explicit_lock_release"`
|
27
|
+
- an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
|
28
|
+
- payload:
|
29
|
+
- `at` - `integer`/`epoch` - the time when the lock was released;
|
30
|
+
- `rel_time` - `float`/`milliseconds` - time spent on lock releasing;
|
31
|
+
- `lock_key` - `string` - released lock (lock name);
|
32
|
+
- `lock_key_queue` - `string` - released lock queue (lock queue name);
|
33
|
+
- `"redis_queued_locks.explicit_all_locks_release"`
|
34
|
+
- an event signalizes about the explicit all locks release (invoked viaa `RedisQueuedLock#clear_locks`);
|
35
|
+
- payload:
|
36
|
+
- `rel_time` - `float`/`milliseconds` - time when the locks was released
|
37
|
+
- `at` - `integer`/`epoch` - the time when the operation has ended;
|
38
|
+
- `rel_lock_cnt` - `integer` - released lock count;
|
39
|
+
- `rel_queue_cnt` - `integer` - released queue count;
|
15
40
|
|
16
41
|
## Todo
|
17
42
|
|
@@ -8,32 +8,40 @@ module RedisQueuedLocks::Acquier::Release
|
|
8
8
|
# @param redis [RedisClient]
|
9
9
|
# @param lock_key [String]
|
10
10
|
# @param lock_key_queue [String]
|
11
|
-
# @return [
|
11
|
+
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
12
12
|
#
|
13
13
|
# @api private
|
14
14
|
# @since 0.1.0
|
15
15
|
def fully_release_lock(redis, lock_key, lock_key_queue)
|
16
|
-
redis.multi do |transact|
|
16
|
+
result = redis.multi do |transact|
|
17
17
|
transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
|
18
18
|
transact.call('EXPIRE', lock_key, '0')
|
19
19
|
end
|
20
|
+
|
21
|
+
{ ok: true, result: }
|
20
22
|
end
|
21
23
|
|
22
24
|
# Release all locks: clear all lock queus and expire all locks.
|
23
25
|
#
|
24
26
|
# @param redis [RedisClient]
|
25
27
|
# @param batch_size [Integer]
|
26
|
-
# @return [
|
28
|
+
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
27
29
|
#
|
28
30
|
# @api private
|
29
31
|
# @since 0.1.0
|
30
32
|
def fully_release_all_locks(redis, batch_size)
|
33
|
+
rel_queue_cnt = 0
|
34
|
+
rel_lock_cnt = 0
|
35
|
+
|
31
36
|
# Step A: release all queus and their related locks
|
32
37
|
redis.scan(
|
33
38
|
'MATCH',
|
34
39
|
RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
|
35
40
|
count: batch_size
|
36
41
|
) do |lock_queue|
|
42
|
+
rel_queue_cnt += 1
|
43
|
+
rel_lock_cnt += 1
|
44
|
+
|
37
45
|
redis.pipelined do |pipeline|
|
38
46
|
pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
|
39
47
|
pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue))
|
@@ -42,6 +50,8 @@ module RedisQueuedLocks::Acquier::Release
|
|
42
50
|
|
43
51
|
# Step B: release all locks
|
44
52
|
redis.pipelined do |pipeline|
|
53
|
+
rel_lock_cnt += 1
|
54
|
+
|
45
55
|
redis.scan(
|
46
56
|
'MATCH',
|
47
57
|
RedisQueuedLocks::Resource::LOCK_PATTERN,
|
@@ -50,5 +60,7 @@ module RedisQueuedLocks::Acquier::Release
|
|
50
60
|
pipeline.call('EXPIRE', lock_key, '0')
|
51
61
|
end
|
52
62
|
end
|
63
|
+
|
64
|
+
{ ok: true, result: { rel_queue_cnt:, rel_lock_cnt: } }
|
53
65
|
end
|
54
66
|
end
|
@@ -37,10 +37,10 @@ module RedisQueuedLocks::Acquier
|
|
37
37
|
# The process that want to acquire the lock.
|
38
38
|
# @option thread_id [Integer,String]
|
39
39
|
# The process's thread that want to acquire the lock.
|
40
|
-
# @option ttl [Integer]
|
41
|
-
# Lock's time to live (in milliseconds).
|
40
|
+
# @option ttl [Integer,NilClass]
|
41
|
+
# Lock's time to live (in milliseconds). Nil means "without timeout".
|
42
42
|
# @option queue_ttl [Integer]
|
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
46
|
# @option retry_count [Integer]
|
@@ -90,7 +90,9 @@ module RedisQueuedLocks::Acquier
|
|
90
90
|
tries: 0,
|
91
91
|
acquired: false,
|
92
92
|
result: nil,
|
93
|
-
acq_time: nil # NOTE: in milliseconds
|
93
|
+
acq_time: nil, # NOTE: in milliseconds
|
94
|
+
hold_time: nil, # NOTE: in milliseconds
|
95
|
+
rel_time: nil # NOTE: in milliseconds
|
94
96
|
}
|
95
97
|
acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
|
96
98
|
|
@@ -118,7 +120,7 @@ module RedisQueuedLocks::Acquier
|
|
118
120
|
|
119
121
|
# Step 2.1: analyze an acquirement attempt
|
120
122
|
if ok
|
121
|
-
#
|
123
|
+
# Step X (instrumentation): lock obtained
|
122
124
|
instrumenter.notify('redis_queued_locks.lock_obtained', {
|
123
125
|
lock_key: result[:lock_key],
|
124
126
|
ttl: result[:ttl],
|
@@ -160,7 +162,25 @@ module RedisQueuedLocks::Acquier
|
|
160
162
|
if acq_process[:acquired]
|
161
163
|
# Step 3.a: acquired successfully => run logic or return the result of acquirement
|
162
164
|
if block_given?
|
163
|
-
|
165
|
+
begin
|
166
|
+
yield_with_expire(redis, lock_key, instrumenter, &block)
|
167
|
+
ensure
|
168
|
+
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
169
|
+
acq_process[:hold_time] = ((
|
170
|
+
acq_process[:rel_time] - acq_process[:acq_time]
|
171
|
+
) * 1000).ceil
|
172
|
+
|
173
|
+
# Step X (instrumentation): lock_hold_and_release
|
174
|
+
instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
|
175
|
+
hold_time: acq_process[:hold_time],
|
176
|
+
rel_time: acq_process[:rel_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
|
164
184
|
else
|
165
185
|
{ ok: true, result: acq_process[:lock_info] }
|
166
186
|
end
|
@@ -181,16 +201,28 @@ module RedisQueuedLocks::Acquier
|
|
181
201
|
#
|
182
202
|
# @param redis [RedisClient] Redis connection client.
|
183
203
|
# @param lock_name [String] The lock name that should be released.
|
204
|
+
# @param isntrumenter [#notify] See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
184
205
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
185
206
|
#
|
186
207
|
# @api private
|
187
208
|
# @since 0.1.0
|
188
|
-
def release_lock!(redis, lock_name)
|
209
|
+
def release_lock!(redis, lock_name, instrumenter)
|
189
210
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
190
211
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
191
212
|
|
192
|
-
|
213
|
+
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
193
214
|
result = fully_release_lock(redis, lock_key, lock_key_queue)
|
215
|
+
time_at = Time.now.to_i
|
216
|
+
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
217
|
+
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
|
218
|
+
|
219
|
+
instrumenter.notify('redis_queued_locks.explicit_lock_release', {
|
220
|
+
lock_key: lock_key,
|
221
|
+
lock_key_queue: lock_key_queue,
|
222
|
+
rel_time: rel_time,
|
223
|
+
at: time_at
|
224
|
+
})
|
225
|
+
|
194
226
|
{ ok: true, result: result }
|
195
227
|
end
|
196
228
|
|
@@ -200,13 +232,25 @@ module RedisQueuedLocks::Acquier
|
|
200
232
|
#
|
201
233
|
# @param redis [RedisClient] Redis connection client.
|
202
234
|
# @param batch_size [Integer] The number of lock keys that should be released in a time.
|
235
|
+
# @param isntrumenter [#notify] See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
203
236
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
204
237
|
#
|
205
238
|
# @api private
|
206
239
|
# @since 0.1.0
|
207
|
-
def release_all_locks!(redis, batch_size)
|
208
|
-
|
240
|
+
def release_all_locks!(redis, batch_size, instrumenter)
|
241
|
+
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
209
242
|
result = fully_release_all_locks(redis, batch_size)
|
243
|
+
time_at = Time.now.to_i
|
244
|
+
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
245
|
+
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
|
246
|
+
|
247
|
+
instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
|
248
|
+
at: time_at,
|
249
|
+
rel_time: rel_time,
|
250
|
+
rel_lock_cnt: result[:rel_lock_cnt],
|
251
|
+
rel_queue_cnt: result[:rel_queue_cnt]
|
252
|
+
})
|
253
|
+
|
210
254
|
{ ok: true, result: result }
|
211
255
|
end
|
212
256
|
|
@@ -60,9 +60,9 @@ class RedisQueuedLocks::Client
|
|
60
60
|
# @option ttl [Integer]
|
61
61
|
# Lock's time to live (in milliseconds).
|
62
62
|
# @option queue_ttl [Integer]
|
63
|
-
#
|
64
|
-
# @option timeout [Integer]
|
65
|
-
# Time period whe should try to acquire the lock (in seconds).
|
63
|
+
# Lifetime of the acuier's lock request. In seconds.
|
64
|
+
# @option timeout [Integer,NilClass]
|
65
|
+
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
|
66
66
|
# @option retry_count [Integer]
|
67
67
|
# How many times we should try to acquire a lock.
|
68
68
|
# @option retry_delay [Integer]
|
@@ -147,14 +147,23 @@ class RedisQueuedLocks::Client
|
|
147
147
|
# @api public
|
148
148
|
# @since 0.1.0
|
149
149
|
def unlock(lock_name)
|
150
|
-
RedisQueuedLocks::Acquier.release_lock!(
|
150
|
+
RedisQueuedLocks::Acquier.release_lock!(
|
151
|
+
redis_client,
|
152
|
+
lock_name,
|
153
|
+
config[:instrumenter]
|
154
|
+
)
|
151
155
|
end
|
152
156
|
|
157
|
+
# @option batch_size [Integer]
|
153
158
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol/Hash }.
|
154
159
|
#
|
155
160
|
# @api public
|
156
161
|
# @since 0.1.0
|
157
162
|
def clear_locks(batch_size: config[:lock_release_batch_size])
|
158
|
-
RedisQueuedLocks::Acquier.release_all_locks!(
|
163
|
+
RedisQueuedLocks::Acquier.release_all_locks!(
|
164
|
+
redis_client,
|
165
|
+
batch_size,
|
166
|
+
config[:instrumenter]
|
167
|
+
)
|
159
168
|
end
|
160
169
|
end
|