redis_queued_locks 0.0.18 → 0.0.20
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 -0
- data/README.md +13 -1
- data/lib/redis_queued_locks/acquier/yield_expire.rb +46 -0
- data/lib/redis_queued_locks/acquier.rb +18 -7
- data/lib/redis_queued_locks/client.rb +12 -0
- data/lib/redis_queued_locks/errors.rb +4 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +3 -3
- data/lib/redis_queued_locks/acquier/expire.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 281c476c0b1318a3f061e0d22031de92983f4ebd2277bc7e024b2baf998ae559
|
4
|
+
data.tar.gz: 5547ace054290da08fb1eb26e965e2d0ff68dfc9b5c3df0adb8a77069543b8ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a971c732be6ef918ae6e9fc70e4456a076b36a50622cda4e0f2b4d5d3f0cb9479e768852e391b02a6420d3de682ccca7de374cf1410b25be37a5d8cf43a4865c
|
7
|
+
data.tar.gz: ef66b70b7ef28be2b9b2422a94dc89e2fff350ad5d8ce58806f28c7742fa4a3caa5032f64d4817abd52dc4bc2ae77327dcfee79b28f462241c63fb2ddaf992eb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.20] - 2024-03-14
|
4
|
+
### Added
|
5
|
+
- An ability to provide custom metadata to `lock` and `lock!` methods that will be passed
|
6
|
+
to the instrumentation level inside the `payload` parameter with `:meta` key;
|
7
|
+
|
8
|
+
## [0.0.19] - 2024-03-12
|
9
|
+
### Added
|
10
|
+
- An ability to set the invocation time period to the block of code invoked under
|
11
|
+
the obtained lock;
|
12
|
+
|
3
13
|
## [0.0.18] - 2024-03-04
|
4
14
|
### Changed
|
5
15
|
- Semantic results for methods returning `{ ok: true/false, result: Any }` hash objects.
|
data/README.md
CHANGED
@@ -36,7 +36,9 @@ Each lock request is put into the request queue and processed in order of their
|
|
36
36
|
|
37
37
|
### Algorithm
|
38
38
|
|
39
|
-
|
39
|
+
> Each lock request is put into the request queue and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
|
40
|
+
|
41
|
+
**Soon**: detailed explanation.
|
40
42
|
|
41
43
|
---
|
42
44
|
|
@@ -167,12 +169,14 @@ def lock(
|
|
167
169
|
ttl: config[:default_lock_ttl],
|
168
170
|
queue_ttl: config[:default_queue_ttl],
|
169
171
|
timeout: config[:try_to_lock_timeout],
|
172
|
+
timed: false,
|
170
173
|
retry_count: config[:retry_count],
|
171
174
|
retry_delay: config[:retry_delay],
|
172
175
|
retry_jitter: config[:retry_jitter],
|
173
176
|
raise_errors: false,
|
174
177
|
fail_fast: false,
|
175
178
|
identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
|
179
|
+
metadata: nil,
|
176
180
|
&block
|
177
181
|
)
|
178
182
|
```
|
@@ -185,6 +189,8 @@ def lock(
|
|
185
189
|
- Lifetime of the acuier's lock request. In seconds.
|
186
190
|
- `timeout` - `[Integer,NilClass]`
|
187
191
|
- Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
|
192
|
+
- `timed` - `[Boolean]`
|
193
|
+
- Limit the invocation time period of the passed block of code by the lock's TTL.
|
188
194
|
- `retry_count` - `[Integer,NilClass]`
|
189
195
|
- How many times we should try to acquire a lock. Nil means "infinite retries".
|
190
196
|
- `retry_delay` - `[Integer]`
|
@@ -206,6 +212,8 @@ def lock(
|
|
206
212
|
pods or/and nodes of your application;
|
207
213
|
- It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
|
208
214
|
ivar (accessed via `uniq_dentity` accessor method);
|
215
|
+
- `metadata` - `[NilClass,Any]`
|
216
|
+
- A custom metadata wich will be passed to the instrumenter's payload with :meta key;
|
209
217
|
- `block` - `[Block]`
|
210
218
|
- A block of code that should be executed after the successfully acquired lock.
|
211
219
|
- If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
|
@@ -259,6 +267,7 @@ def lock!(
|
|
259
267
|
retry_jitter: config[:retry_jitter],
|
260
268
|
identity: uniq_identity,
|
261
269
|
fail_fast: false,
|
270
|
+
metadata: nil,
|
262
271
|
&block
|
263
272
|
)
|
264
273
|
```
|
@@ -533,6 +542,7 @@ Detalized event semantics and payload structure:
|
|
533
542
|
- `:lock_key` - `string` - lock name;
|
534
543
|
- `:ts` - `integer`/`epoch` - the time when the lock was obtaiend;
|
535
544
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
545
|
+
- `:meta` - `nil`/`Any` - custom metadata passed to the `lock`/`lock!` method;
|
536
546
|
- `"redis_queued_locks.lock_hold_and_release"`
|
537
547
|
- an event signalizes about the "hold+and+release" process
|
538
548
|
when the lock obtained and hold by the block of logic;
|
@@ -543,6 +553,7 @@ Detalized event semantics and payload structure:
|
|
543
553
|
- `:lock_key` - `string` - lock name;
|
544
554
|
- `:ts` - `integer`/`epoch` - the time when lock was obtained;
|
545
555
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
556
|
+
- `:meta` - `nil`/`Any` - custom metadata passed to the `lock`/`lock!` method;
|
546
557
|
- `"redis_queued_locks.explicit_lock_release"`
|
547
558
|
- an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
|
548
559
|
- payload:
|
@@ -568,6 +579,7 @@ Detalized event semantics and payload structure:
|
|
568
579
|
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
569
580
|
whose ttl may expire before the block execution completes);
|
570
581
|
- an ability to add custom metadata to the lock and an ability to read this data;
|
582
|
+
- lock prioritization;
|
571
583
|
- **Minor**
|
572
584
|
- GitHub Actions CI;
|
573
585
|
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.1.0
|
5
|
+
module RedisQueuedLocks::Acquier::YieldExpire
|
6
|
+
# @param redis [RedisClient] Redis connection manager.
|
7
|
+
# @param lock_key [String] Lock key to be expired.
|
8
|
+
# @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
|
9
|
+
# @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
|
10
|
+
# @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
|
11
|
+
# @param block [Block] Custom logic that should be invoked unter the obtained lock.
|
12
|
+
# @return [Any,NilClass] nil is returned no block parametr is provided.
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
# @since 0.1.0
|
16
|
+
def yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
|
17
|
+
if block_given?
|
18
|
+
if timed && ttl != nil
|
19
|
+
timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
|
20
|
+
yield_with_timeout(timeout, lock_key, ttl, &block)
|
21
|
+
else
|
22
|
+
yield
|
23
|
+
end
|
24
|
+
end
|
25
|
+
ensure
|
26
|
+
redis.call('EXPIRE', lock_key, '0')
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @param timeout [Float]
|
32
|
+
# @parma lock_key [String]
|
33
|
+
# @param lock_ttl [Integer,NilClass]
|
34
|
+
# @param block [Blcok]
|
35
|
+
# @return [Any]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
# @since 0.1.0
|
39
|
+
def yield_with_timeout(timeout, lock_key, lock_ttl, &block)
|
40
|
+
::Timeout.timeout(timeout, &block)
|
41
|
+
rescue ::Timeout::Error
|
42
|
+
raise(RedisQueuedLocks::TimedLockTimeoutError, <<~ERROR_MESSAGE)
|
43
|
+
Passed <timed> block of code exceeded the lock TTL (lock: "#{lock_key}", ttl: #{lock_ttl})
|
44
|
+
ERROR_MESSAGE
|
45
|
+
end
|
46
|
+
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
module RedisQueuedLocks::Acquier
|
7
7
|
require_relative 'acquier/try'
|
8
8
|
require_relative 'acquier/delay'
|
9
|
-
require_relative 'acquier/
|
9
|
+
require_relative 'acquier/yield_expire'
|
10
10
|
require_relative 'acquier/release'
|
11
11
|
|
12
12
|
# @since 0.1.0
|
@@ -14,7 +14,7 @@ module RedisQueuedLocks::Acquier
|
|
14
14
|
# @since 0.1.0
|
15
15
|
extend Delay
|
16
16
|
# @since 0.1.0
|
17
|
-
extend
|
17
|
+
extend YieldExpire
|
18
18
|
# @since 0.1.0
|
19
19
|
extend Release
|
20
20
|
|
@@ -44,6 +44,8 @@ module RedisQueuedLocks::Acquier
|
|
44
44
|
# Lifetime of the acuier's lock request. In seconds.
|
45
45
|
# @option timeout [Integer]
|
46
46
|
# Time period whe should try to acquire the lock (in seconds).
|
47
|
+
# @option timed [Boolean]
|
48
|
+
# Limit the invocation time period of the passed block of code by the lock's TTL.
|
47
49
|
# @option retry_count [Integer,NilClass]
|
48
50
|
# How many times we should try to acquire a lock. Nil means "infinite retries".
|
49
51
|
# @option retry_delay [Integer]
|
@@ -61,6 +63,8 @@ module RedisQueuedLocks::Acquier
|
|
61
63
|
# @option fail_fast [Boolean]
|
62
64
|
# Should the required lock to be checked before the try and exit immidetly if lock is
|
63
65
|
# already obtained.
|
66
|
+
# @option metadata [NilClass,Any]
|
67
|
+
# - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
|
64
68
|
# @param [Block]
|
65
69
|
# A block of code that should be executed after the successfully acquired lock.
|
66
70
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -80,6 +84,7 @@ module RedisQueuedLocks::Acquier
|
|
80
84
|
ttl:,
|
81
85
|
queue_ttl:,
|
82
86
|
timeout:,
|
87
|
+
timed:,
|
83
88
|
retry_count:,
|
84
89
|
retry_delay:,
|
85
90
|
retry_jitter:,
|
@@ -87,6 +92,7 @@ module RedisQueuedLocks::Acquier
|
|
87
92
|
instrumenter:,
|
88
93
|
identity:,
|
89
94
|
fail_fast:,
|
95
|
+
metadata:,
|
90
96
|
&block
|
91
97
|
)
|
92
98
|
# Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
|
@@ -119,7 +125,7 @@ module RedisQueuedLocks::Acquier
|
|
119
125
|
acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
|
120
126
|
|
121
127
|
# Step 2: try to lock with timeout
|
122
|
-
|
128
|
+
with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
|
123
129
|
acq_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
124
130
|
|
125
131
|
# Step 2.1: caclically try to obtain the lock
|
@@ -140,6 +146,7 @@ module RedisQueuedLocks::Acquier
|
|
140
146
|
|
141
147
|
# Step X: save the intermediate results to the result observer
|
142
148
|
acq_process[:result] = result
|
149
|
+
acq_process[:acq_end_time] = acq_end_time
|
143
150
|
|
144
151
|
# Step 2.1: analyze an acquirement attempt
|
145
152
|
if ok
|
@@ -149,7 +156,8 @@ module RedisQueuedLocks::Acquier
|
|
149
156
|
ttl: result[:ttl],
|
150
157
|
acq_id: result[:acq_id],
|
151
158
|
ts: result[:ts],
|
152
|
-
acq_time: acq_time
|
159
|
+
acq_time: acq_time,
|
160
|
+
meta: metadata
|
153
161
|
})
|
154
162
|
|
155
163
|
# Step 2.1.a: successfully acquired => build the result
|
@@ -212,7 +220,9 @@ module RedisQueuedLocks::Acquier
|
|
212
220
|
# Step 3.a: acquired successfully => run logic or return the result of acquirement
|
213
221
|
if block_given?
|
214
222
|
begin
|
215
|
-
|
223
|
+
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
224
|
+
ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
|
225
|
+
yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
|
216
226
|
ensure
|
217
227
|
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
218
228
|
acq_process[:hold_time] = (
|
@@ -227,7 +237,8 @@ module RedisQueuedLocks::Acquier
|
|
227
237
|
acq_id: acq_process[:lock_info][:acq_id],
|
228
238
|
ts: acq_process[:lock_info][:ts],
|
229
239
|
lock_key: acq_process[:lock_info][:lock_key],
|
230
|
-
acq_time: acq_process[:acq_time]
|
240
|
+
acq_time: acq_process[:acq_time],
|
241
|
+
meta: metadata
|
231
242
|
})
|
232
243
|
end
|
233
244
|
end
|
@@ -526,7 +537,7 @@ module RedisQueuedLocks::Acquier
|
|
526
537
|
#
|
527
538
|
# @api private
|
528
539
|
# @since 0.1.0
|
529
|
-
def
|
540
|
+
def with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: nil, &block)
|
530
541
|
::Timeout.timeout(timeout, &block)
|
531
542
|
rescue ::Timeout::Error
|
532
543
|
on_timeout.call unless on_timeout == nil
|
@@ -71,6 +71,8 @@ class RedisQueuedLocks::Client
|
|
71
71
|
# Lifetime of the acuier's lock request. In seconds.
|
72
72
|
# @option timeout [Integer,NilClass]
|
73
73
|
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
|
74
|
+
# @option timed [Boolean]
|
75
|
+
# Limit the invocation time period of the passed block of code by the lock's TTL.
|
74
76
|
# @option retry_count [Integer,NilClass]
|
75
77
|
# How many times we should try to acquire a lock. Nil means "infinite retries".
|
76
78
|
# @option retry_delay [Integer]
|
@@ -90,6 +92,8 @@ class RedisQueuedLocks::Client
|
|
90
92
|
# already obtained;
|
91
93
|
# - Should the logic exit immidietly after the first try if the lock was obtained
|
92
94
|
# by another process while the lock request queue was initially empty;
|
95
|
+
# @option metadata [NilClass,Any]
|
96
|
+
# - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
|
93
97
|
# @param block [Block]
|
94
98
|
# A block of code that should be executed after the successfully acquired lock.
|
95
99
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
|
@@ -103,12 +107,14 @@ class RedisQueuedLocks::Client
|
|
103
107
|
ttl: config[:default_lock_ttl],
|
104
108
|
queue_ttl: config[:default_queue_ttl],
|
105
109
|
timeout: config[:try_to_lock_timeout],
|
110
|
+
timed: false,
|
106
111
|
retry_count: config[:retry_count],
|
107
112
|
retry_delay: config[:retry_delay],
|
108
113
|
retry_jitter: config[:retry_jitter],
|
109
114
|
raise_errors: false,
|
110
115
|
fail_fast: false,
|
111
116
|
identity: uniq_identity,
|
117
|
+
metadata: nil,
|
112
118
|
&block
|
113
119
|
)
|
114
120
|
RedisQueuedLocks::Acquier.acquire_lock!(
|
@@ -121,6 +127,7 @@ class RedisQueuedLocks::Client
|
|
121
127
|
ttl:,
|
122
128
|
queue_ttl:,
|
123
129
|
timeout:,
|
130
|
+
timed:,
|
124
131
|
retry_count:,
|
125
132
|
retry_delay:,
|
126
133
|
retry_jitter:,
|
@@ -128,6 +135,7 @@ class RedisQueuedLocks::Client
|
|
128
135
|
instrumenter: config[:instrumenter],
|
129
136
|
identity:,
|
130
137
|
fail_fast:,
|
138
|
+
metadata:,
|
131
139
|
&block
|
132
140
|
)
|
133
141
|
end
|
@@ -141,11 +149,13 @@ class RedisQueuedLocks::Client
|
|
141
149
|
ttl: config[:default_lock_ttl],
|
142
150
|
queue_ttl: config[:default_queue_ttl],
|
143
151
|
timeout: config[:try_to_lock_timeout],
|
152
|
+
timed: false,
|
144
153
|
retry_count: config[:retry_count],
|
145
154
|
retry_delay: config[:retry_delay],
|
146
155
|
retry_jitter: config[:retry_jitter],
|
147
156
|
fail_fast: false,
|
148
157
|
identity: uniq_identity,
|
158
|
+
metadata: nil,
|
149
159
|
&block
|
150
160
|
)
|
151
161
|
lock(
|
@@ -153,12 +163,14 @@ class RedisQueuedLocks::Client
|
|
153
163
|
ttl:,
|
154
164
|
queue_ttl:,
|
155
165
|
timeout:,
|
166
|
+
timed:,
|
156
167
|
retry_count:,
|
157
168
|
retry_delay:,
|
158
169
|
retry_jitter:,
|
159
170
|
raise_errors: true,
|
160
171
|
identity:,
|
161
172
|
fail_fast:,
|
173
|
+
metadata:,
|
162
174
|
&block
|
163
175
|
)
|
164
176
|
end
|
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.20
|
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-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -57,9 +57,9 @@ files:
|
|
57
57
|
- lib/redis_queued_locks.rb
|
58
58
|
- lib/redis_queued_locks/acquier.rb
|
59
59
|
- lib/redis_queued_locks/acquier/delay.rb
|
60
|
-
- lib/redis_queued_locks/acquier/expire.rb
|
61
60
|
- lib/redis_queued_locks/acquier/release.rb
|
62
61
|
- lib/redis_queued_locks/acquier/try.rb
|
62
|
+
- lib/redis_queued_locks/acquier/yield_expire.rb
|
63
63
|
- lib/redis_queued_locks/client.rb
|
64
64
|
- lib/redis_queued_locks/data.rb
|
65
65
|
- lib/redis_queued_locks/debugger.rb
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# @api private
|
4
|
-
# @since 0.1.0
|
5
|
-
module RedisQueuedLocks::Acquier::Expire
|
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
|
-
#
|
11
|
-
# @api private
|
12
|
-
# @since 0.1.0
|
13
|
-
def yield_with_expire(redis, lock_key, &block)
|
14
|
-
yield if block_given?
|
15
|
-
ensure
|
16
|
-
redis.call('EXPIRE', lock_key, '0')
|
17
|
-
end
|
18
|
-
end
|