redis_queued_locks 0.0.14 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +35 -23
- data/lib/redis_queued_locks/acquier/try.rb +89 -68
- data/lib/redis_queued_locks/acquier.rb +37 -11
- data/lib/redis_queued_locks/client.rb +13 -3
- data/lib/redis_queued_locks/errors.rb +4 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3270e2a14a4eed87379d84fe0622f4a231a681fb1e48325a1d6e22becafb5a60
|
4
|
+
data.tar.gz: b67274aeec060e1bef07d64933928c5be3632cc68f88f12cbe01b9105ae41478
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a40531666d37dc44d738fba4e67e027520a58f029c392a46db8a2e465200d4162ad1fdd44e16b17001edab183b8f9f90621d35f6b026a9c2628abc00ab9ea5b3
|
7
|
+
data.tar.gz: 40392bb5de7d615e52e4a92b5f193c7c72970d220a9963ef89346364788c2dc1d11ef5664b20d0864dd3b151aba6a1f6bb76b45cdc5c016882dd51883df25281
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -158,6 +158,7 @@ def lock(
|
|
158
158
|
retry_delay: config[:retry_delay],
|
159
159
|
retry_jitter: config[:retry_jitter],
|
160
160
|
raise_errors: false,
|
161
|
+
fail_fast: false,
|
161
162
|
identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
|
162
163
|
&block
|
163
164
|
)
|
@@ -181,6 +182,11 @@ def lock(
|
|
181
182
|
- See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
182
183
|
- `raise_errors` - `[Boolean]`
|
183
184
|
- Raise errors on library-related limits such as timeout or retry count limit.
|
185
|
+
- `fail_fast` - `[Boolean]`
|
186
|
+
- Should the required lock to be checked before the try and exit immidietly if lock is
|
187
|
+
already obtained;
|
188
|
+
- Should the logic exit immidietly after the first try if the lock was obtained
|
189
|
+
by another process while the lock request queue was initially empty;
|
184
190
|
- `identity` - `[String]`
|
185
191
|
- An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
|
186
192
|
collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
|
@@ -215,6 +221,8 @@ Return value:
|
|
215
221
|
```ruby
|
216
222
|
{ ok: false, result: :timeout_reached }
|
217
223
|
{ ok: false, result: :retry_count_reached }
|
224
|
+
{ ok: false, result: :fail_fast_no_try } # see <fail_fast> option
|
225
|
+
{ ok: false, result: :fail_fast_after_try } # see <fail_fast> option
|
218
226
|
{ ok: false, result: :unknown }
|
219
227
|
```
|
220
228
|
|
@@ -236,6 +244,7 @@ def lock!(
|
|
236
244
|
retry_delay: config[:retry_delay],
|
237
245
|
retry_jitter: config[:retry_jitter],
|
238
246
|
identity: uniq_identity,
|
247
|
+
fail_fast: false,
|
239
248
|
&block
|
240
249
|
)
|
241
250
|
```
|
@@ -407,44 +416,44 @@ By default `RedisQueuedLocks::Client` is configured with the void notifier (whic
|
|
407
416
|
|
408
417
|
List of instrumentation events
|
409
418
|
|
410
|
-
-
|
411
|
-
-
|
412
|
-
-
|
413
|
-
-
|
419
|
+
- `redis_queued_locks.lock_obtained`
|
420
|
+
- `redis_queued_locks.lock_hold_and_release`
|
421
|
+
- `redis_queued_locks.explicit_lock_release`
|
422
|
+
- `redis_queued_locks.explicit_all_locks_release`
|
414
423
|
|
415
424
|
Detalized event semantics and payload structure:
|
416
425
|
|
417
426
|
- `"redis_queued_locks.lock_obtained"`
|
418
427
|
- a moment when the lock was obtained;
|
419
428
|
- payload:
|
420
|
-
-
|
421
|
-
-
|
422
|
-
-
|
423
|
-
-
|
424
|
-
-
|
429
|
+
- `:ttl` - `integer`/`milliseconds` - lock ttl;
|
430
|
+
- `:acq_id` - `string` - lock acquier identifier;
|
431
|
+
- `:lock_key` - `string` - lock name;
|
432
|
+
- `:ts` - `integer`/`epoch` - the time when the lock was obtaiend;
|
433
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
425
434
|
- `"redis_queued_locks.lock_hold_and_release"`
|
426
435
|
- an event signalizes about the "hold+and+release" process
|
427
436
|
when the lock obtained and hold by the block of logic;
|
428
437
|
- payload:
|
429
|
-
-
|
430
|
-
-
|
431
|
-
-
|
432
|
-
-
|
433
|
-
-
|
434
|
-
-
|
438
|
+
- `:hold_time` - `float`/`milliseconds` - lock hold time;
|
439
|
+
- `:ttl` - `integer`/`milliseconds` - lock ttl;
|
440
|
+
- `:acq_id` - `string` - lock acquier identifier;
|
441
|
+
- `:lock_key` - `string` - lock name;
|
442
|
+
- `:ts` - `integer`/`epoch` - the time when lock was obtained;
|
443
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
435
444
|
- `"redis_queued_locks.explicit_lock_release"`
|
436
445
|
- an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
|
437
446
|
- payload:
|
438
|
-
-
|
439
|
-
-
|
440
|
-
-
|
441
|
-
-
|
447
|
+
- `:at` - `integer`/`epoch` - the time when the lock was released;
|
448
|
+
- `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
|
449
|
+
- `:lock_key` - `string` - released lock (lock name);
|
450
|
+
- `:lock_key_queue` - `string` - released lock queue (lock queue name);
|
442
451
|
- `"redis_queued_locks.explicit_all_locks_release"`
|
443
452
|
- an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
|
444
453
|
- payload:
|
445
|
-
-
|
446
|
-
-
|
447
|
-
-
|
454
|
+
- `:rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
|
455
|
+
- `:at` - `integer`/`epoch` - the time when the operation has ended;
|
456
|
+
- `:rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
|
448
457
|
|
449
458
|
---
|
450
459
|
|
@@ -453,7 +462,10 @@ Detalized event semantics and payload structure:
|
|
453
462
|
- **Major**
|
454
463
|
- Semantic Error objects for unexpected Redis errors;
|
455
464
|
- `100%` test coverage;
|
456
|
-
- sidecar `Ractor`
|
465
|
+
- per-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
|
466
|
+
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
467
|
+
whose ttl may expire before the block execution completes);
|
468
|
+
- an ability to add custom metadata to the lock and an ability to read this data;
|
457
469
|
- **Minor**
|
458
470
|
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
459
471
|
- better code stylization and interesting refactorings;
|
@@ -10,112 +10,132 @@ module RedisQueuedLocks::Acquier::Try
|
|
10
10
|
# @param acquier_position [Numeric]
|
11
11
|
# @param ttl [Integer]
|
12
12
|
# @param queue_ttl [Integer]
|
13
|
+
# @param fail_fast [Boolean]
|
13
14
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol|Hash<Symbol,Any> }
|
14
15
|
#
|
15
16
|
# @api private
|
16
17
|
# @since 0.1.0
|
17
18
|
# rubocop:disable Metrics/MethodLength
|
18
|
-
def try_to_lock(
|
19
|
+
def try_to_lock(
|
20
|
+
redis,
|
21
|
+
lock_key,
|
22
|
+
lock_key_queue,
|
23
|
+
acquier_id,
|
24
|
+
acquier_position,
|
25
|
+
ttl,
|
26
|
+
queue_ttl,
|
27
|
+
fail_fast
|
28
|
+
)
|
19
29
|
# Step X: intermediate invocation results
|
20
30
|
inter_result = nil
|
21
31
|
timestamp = nil
|
22
32
|
|
23
33
|
# Step 0: watch the lock key changes (and discard acquirement if lock is already acquired)
|
24
34
|
result = redis.multi(watch: [lock_key]) do |transact|
|
25
|
-
# Step
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# Step 2.1: drop expired acquiers from the lock queue
|
33
|
-
res = redis.call(
|
34
|
-
'ZREMRANGEBYSCORE',
|
35
|
-
lock_key_queue,
|
36
|
-
'-inf',
|
37
|
-
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
38
|
-
)
|
39
|
-
|
40
|
-
RedisQueuedLocks.debug(
|
41
|
-
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
42
|
-
)
|
43
|
-
|
44
|
-
# Step 3: get the actual acquier waiting in the queue
|
45
|
-
waiting_acquier = Array(redis.call('ZRANGE', lock_key_queue, '0', '0')).first
|
35
|
+
# Fast-Step X0: fail-fast check
|
36
|
+
if fail_fast && redis.call('HGET', lock_key, 'acq_id')
|
37
|
+
# Fast-Step X1: is lock already obtained. fail fast - no try.
|
38
|
+
inter_result = :fail_fast_no_try
|
39
|
+
else
|
40
|
+
# Step 1: add an acquier to the lock acquirement queue
|
41
|
+
res = redis.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
46
42
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
)
|
43
|
+
RedisQueuedLocks.debug(
|
44
|
+
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
45
|
+
)
|
51
46
|
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
+
)
|
55
54
|
|
56
55
|
RedisQueuedLocks.debug(
|
57
|
-
"Step
|
58
|
-
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
56
|
+
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
59
57
|
)
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
# NOTE: our time has come! let's try to acquire the lock!
|
64
|
-
|
65
|
-
# Step 5: check if the our lock is already acquired
|
66
|
-
locked_by_acquier = redis.call('HGET', lock_key, 'acq_id')
|
59
|
+
# Step 3: get the actual acquier waiting in the queue
|
60
|
+
waiting_acquier = Array(redis.call('ZRANGE', lock_key_queue, '0', '0')).first
|
67
61
|
|
68
62
|
RedisQueuedLocks.debug(
|
69
|
-
"
|
70
|
-
"[
|
71
|
-
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
63
|
+
"Step №3: какой процесс в очереди сейчас ждет. " \
|
64
|
+
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
72
65
|
)
|
73
66
|
|
74
|
-
|
75
|
-
|
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!
|
76
70
|
|
77
71
|
RedisQueuedLocks.debug(
|
78
|
-
"Step ROLLBACK №
|
79
|
-
"[
|
72
|
+
"Step ROLLBACK №1: не одинаковые ключи. выходим. " \
|
73
|
+
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
80
74
|
)
|
81
75
|
|
82
|
-
inter_result = :
|
76
|
+
inter_result = :acquier_is_not_first_in_queue
|
83
77
|
else
|
84
|
-
# NOTE:
|
78
|
+
# NOTE: our time has come! let's try to acquire the lock!
|
85
79
|
|
86
|
-
# Step
|
87
|
-
|
88
|
-
|
89
|
-
RedisQueuedLocks.debug(
|
90
|
-
'Step №4: Забираем наш текущий процесс из очереди. [ZPOPMIN]'
|
91
|
-
)
|
80
|
+
# Step 5: check if the our lock is already acquired
|
81
|
+
locked_by_acquier = redis.call('HGET', lock_key, 'acq_id')
|
92
82
|
|
93
83
|
RedisQueuedLocks.debug(
|
94
|
-
"
|
95
|
-
|
96
|
-
|
97
|
-
# Step 6.2: acquire a lock and store an info about the acquier
|
98
|
-
transact.call(
|
99
|
-
'HSET',
|
100
|
-
lock_key,
|
101
|
-
'acq_id', acquier_id,
|
102
|
-
'ts', (timestamp = Time.now.to_i),
|
103
|
-
'ini_ttl', ttl
|
84
|
+
"Ste №5: Ищем требуемый лок. " \
|
85
|
+
"[HGET<#{lock_key}>: " \
|
86
|
+
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
104
87
|
)
|
105
88
|
|
106
|
-
|
107
|
-
|
89
|
+
if locked_by_acquier
|
90
|
+
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
91
|
+
|
92
|
+
RedisQueuedLocks.debug(
|
93
|
+
"Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
|
94
|
+
"[Занят процессом: #{locked_by_acquier}]"
|
95
|
+
)
|
96
|
+
|
97
|
+
inter_result = :lock_is_still_acquired
|
98
|
+
else
|
99
|
+
# NOTE: required lock is free and ready to be acquired! acquire!
|
100
|
+
|
101
|
+
# Step 6.1: remove our acquier from waiting queue
|
102
|
+
transact.call('ZPOPMIN', lock_key_queue, '1')
|
103
|
+
|
104
|
+
RedisQueuedLocks.debug(
|
105
|
+
'Step №4: Забираем наш текущий процесс из очереди. [ZPOPMIN]'
|
106
|
+
)
|
107
|
+
|
108
|
+
RedisQueuedLocks.debug(
|
109
|
+
"===> <FINAL> Step №6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
|
110
|
+
)
|
111
|
+
|
112
|
+
# Step 6.2: acquire a lock and store an info about the acquier
|
113
|
+
transact.call(
|
114
|
+
'HSET',
|
115
|
+
lock_key,
|
116
|
+
'acq_id', acquier_id,
|
117
|
+
'ts', (timestamp = Time.now.to_i),
|
118
|
+
'ini_ttl', ttl
|
119
|
+
)
|
120
|
+
|
121
|
+
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
122
|
+
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
123
|
+
end
|
108
124
|
end
|
109
125
|
end
|
110
126
|
end
|
111
127
|
|
112
128
|
# Step 7: Analyze the aquirement attempt:
|
129
|
+
# rubocop:disable Lint/DuplicateBranch
|
113
130
|
case
|
131
|
+
when fail_fast && inter_result == :fail_fast_no_try
|
132
|
+
# Step 7.a: lock is still acquired and we should exit from the logic as soon as possible
|
133
|
+
{ ok: false, result: inter_result }
|
114
134
|
when inter_result == :lock_is_still_acquired || inter_result == :acquier_is_not_first_in_queue
|
115
|
-
# Step 7.
|
135
|
+
# Step 7.b: lock is still acquired by another process => failed to acquire
|
116
136
|
{ ok: false, result: inter_result }
|
117
137
|
when result == nil || (result.is_a?(::Array) && result.empty?)
|
118
|
-
# Step 7.
|
138
|
+
# Step 7.c: lock is already acquired durign the acquire race => failed to acquire
|
119
139
|
{ ok: false, result: :lock_is_acquired_during_acquire_race }
|
120
140
|
when result.is_a?(::Array) && result.size == 3 # NOTE: 3 is a count of redis lock commands
|
121
141
|
# TODO:
|
@@ -126,12 +146,13 @@ module RedisQueuedLocks::Acquier::Try
|
|
126
146
|
# 2. hset should return 2 (lock key is added to the redis as a hashmap with 2 fields)
|
127
147
|
# 3. pexpire should return 1 (expiration time is successfully applied)
|
128
148
|
|
129
|
-
# Step 7.
|
149
|
+
# Step 7.d: locked! :) let's go! => successfully acquired
|
130
150
|
{ ok: true, result: { lock_key: lock_key, acq_id: acquier_id, ts: timestamp, ttl: ttl } }
|
131
151
|
else
|
132
|
-
# Ste 7.
|
152
|
+
# Ste 7.3: unknown behaviour :thinking:
|
133
153
|
{ ok: false, result: :unknown }
|
134
154
|
end
|
155
|
+
# rubocop:enable Lint/DuplicateBranch
|
135
156
|
end
|
136
157
|
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
|
137
158
|
|
@@ -58,6 +58,9 @@ module RedisQueuedLocks::Acquier
|
|
58
58
|
# Unique acquire identifier that is also should be unique between processes and pods
|
59
59
|
# on different machines. By default the uniq identity string is
|
60
60
|
# represented as 10 bytes hexstr.
|
61
|
+
# @option fail_fast [Boolean]
|
62
|
+
# Should the required lock to be checked before the try and exit immidetly if lock is
|
63
|
+
# already obtained.
|
61
64
|
# @param [Block]
|
62
65
|
# A block of code that should be executed after the successfully acquired lock.
|
63
66
|
# @return [Hash<Symbol,Any>,yield]
|
@@ -83,6 +86,7 @@ module RedisQueuedLocks::Acquier
|
|
83
86
|
raise_errors:,
|
84
87
|
instrumenter:,
|
85
88
|
identity:,
|
89
|
+
fail_fast:,
|
86
90
|
&block
|
87
91
|
)
|
88
92
|
# Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
|
@@ -127,7 +131,8 @@ module RedisQueuedLocks::Acquier
|
|
127
131
|
acquier_id,
|
128
132
|
acquier_position,
|
129
133
|
lock_ttl,
|
130
|
-
queue_ttl
|
134
|
+
queue_ttl,
|
135
|
+
fail_fast
|
131
136
|
) => { ok:, result: }
|
132
137
|
|
133
138
|
acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
@@ -158,21 +163,40 @@ module RedisQueuedLocks::Acquier
|
|
158
163
|
acq_process[:should_try] = false
|
159
164
|
acq_process[:acq_time] = acq_time
|
160
165
|
acq_process[:acq_end_time] = acq_end_time
|
166
|
+
elsif fail_fast && acq_process[:result] == :fail_fast_no_try
|
167
|
+
acq_process[:should_try] = false
|
168
|
+
if raise_errors
|
169
|
+
raise(RedisQueuedLocks::LockAlreadyObtainedError, <<~ERROR_MESSAGE.strip)
|
170
|
+
Lock "#{lock_key}" is already obtained.
|
171
|
+
ERROR_MESSAGE
|
172
|
+
end
|
161
173
|
else
|
162
174
|
# Step 2.1.b: failed acquirement => retry
|
163
175
|
acq_process[:tries] += 1
|
164
176
|
|
165
|
-
if retry_count != nil && acq_process[:tries] >= retry_count
|
166
|
-
# NOTE:
|
177
|
+
if (retry_count != nil && acq_process[:tries] >= retry_count) || fail_fast
|
178
|
+
# NOTE:
|
179
|
+
# - reached the retry limit => quit from the loop
|
180
|
+
# - should fail fast => quit from the loop
|
167
181
|
acq_process[:should_try] = false
|
168
|
-
acq_process[:result] = :retry_limit_reached
|
169
|
-
|
182
|
+
acq_process[:result] = fail_fast ? :fail_fast_after_try : :retry_limit_reached
|
183
|
+
|
184
|
+
# NOTE:
|
185
|
+
# - reached the retry limit => dequeue from the lock queue
|
186
|
+
# - should fail fast => dequeue from the lock queue
|
170
187
|
acq_dequeue.call
|
188
|
+
|
171
189
|
# NOTE: check and raise an error
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
190
|
+
if fail_fast && raise_errors
|
191
|
+
raise(RedisQueuedLocks::LockAlreadyObtainedError, <<~ERROR_MESSAGE.strip)
|
192
|
+
Lock "#{lock_key}" is already obtained.
|
193
|
+
ERROR_MESSAGE
|
194
|
+
elsif raise_errors
|
195
|
+
raise(RedisQueuedLocks::LockAcquiermentRetryLimitError, <<~ERROR_MESSAGE.strip)
|
196
|
+
Failed to acquire the lock "#{lock_key}"
|
197
|
+
for the given retry_count limit (#{retry_count} times).
|
198
|
+
ERROR_MESSAGE
|
199
|
+
end
|
176
200
|
else
|
177
201
|
# NOTE:
|
178
202
|
# delay the exceution in order to prevent chaotic attempts
|
@@ -211,8 +235,10 @@ module RedisQueuedLocks::Acquier
|
|
211
235
|
{ ok: true, result: acq_process[:lock_info] }
|
212
236
|
end
|
213
237
|
else
|
214
|
-
|
215
|
-
|
238
|
+
if acq_process[:result] != :retry_limit_reached &&
|
239
|
+
acq_process[:result] != :fail_fast_no_try &&
|
240
|
+
acq_process[:result] != :fail_fast_after_try
|
241
|
+
# NOTE: we have only two situations if lock is not acquired withou fast-fail flag:
|
216
242
|
# - time limit is reached
|
217
243
|
# - retry count limit is reached
|
218
244
|
# In other cases the lock obtaining time and tries count are infinite.
|
@@ -22,8 +22,8 @@ class RedisQueuedLocks::Client
|
|
22
22
|
# TODO: setting :debug, true/false
|
23
23
|
|
24
24
|
validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
|
25
|
-
validate('retry_delay'
|
26
|
-
validate('retry_jitter'
|
25
|
+
validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
|
26
|
+
validate('retry_jitter') { |val| val.is_a?(::Integer) && val >= 0 }
|
27
27
|
validate('try_to_lock_timeout') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
|
28
28
|
validate('default_lock_tt', :integer)
|
29
29
|
validate('default_queue_ttl', :integer)
|
@@ -43,6 +43,7 @@ class RedisQueuedLocks::Client
|
|
43
43
|
# @api private
|
44
44
|
# @since 0.1.0
|
45
45
|
attr_accessor :uniq_identity
|
46
|
+
|
46
47
|
# NOTE: attr_access is chosen intentionally in order to have an ability to change
|
47
48
|
# uniq_identity values for debug purposes in runtime;
|
48
49
|
|
@@ -83,6 +84,11 @@ class RedisQueuedLocks::Client
|
|
83
84
|
# Unique acquire identifier that is also should be unique between processes and pods
|
84
85
|
# on different machines. By default the uniq identity string is
|
85
86
|
# represented as 10 bytes hexstr.
|
87
|
+
# @option fail_fast [Boolean]
|
88
|
+
# - Should the required lock to be checked before the try and exit immidietly if lock is
|
89
|
+
# already obtained;
|
90
|
+
# - Should the logic exit immidietly after the first try if the lock was obtained
|
91
|
+
# by another process while the lock request queue was initially empty;
|
86
92
|
# @param block [Block]
|
87
93
|
# A block of code that should be executed after the successfully acquired lock.
|
88
94
|
# @return [Hash<Symbol,Any>,yield]
|
@@ -100,6 +106,7 @@ class RedisQueuedLocks::Client
|
|
100
106
|
retry_delay: config[:retry_delay],
|
101
107
|
retry_jitter: config[:retry_jitter],
|
102
108
|
raise_errors: false,
|
109
|
+
fail_fast: false,
|
103
110
|
identity: uniq_identity,
|
104
111
|
&block
|
105
112
|
)
|
@@ -119,6 +126,7 @@ class RedisQueuedLocks::Client
|
|
119
126
|
raise_errors:,
|
120
127
|
instrumenter: config[:instrumenter],
|
121
128
|
identity:,
|
129
|
+
fail_fast:,
|
122
130
|
&block
|
123
131
|
)
|
124
132
|
end
|
@@ -131,10 +139,11 @@ class RedisQueuedLocks::Client
|
|
131
139
|
lock_name,
|
132
140
|
ttl: config[:default_lock_ttl],
|
133
141
|
queue_ttl: config[:default_queue_ttl],
|
134
|
-
timeout: config[:
|
142
|
+
timeout: config[:try_to_lock_timeout],
|
135
143
|
retry_count: config[:retry_count],
|
136
144
|
retry_delay: config[:retry_delay],
|
137
145
|
retry_jitter: config[:retry_jitter],
|
146
|
+
fail_fast: false,
|
138
147
|
identity: uniq_identity,
|
139
148
|
&block
|
140
149
|
)
|
@@ -148,6 +157,7 @@ class RedisQueuedLocks::Client
|
|
148
157
|
retry_jitter:,
|
149
158
|
raise_errors: true,
|
150
159
|
identity:,
|
160
|
+
fail_fast:,
|
151
161
|
&block
|
152
162
|
)
|
153
163
|
end
|
@@ -9,6 +9,10 @@ module RedisQueuedLocks
|
|
9
9
|
# @since 0.1.0
|
10
10
|
ArgumentError = Class.new(::ArgumentError)
|
11
11
|
|
12
|
+
# @api public
|
13
|
+
# @since 0.1.0
|
14
|
+
LockAlreadyObtainedError = Class.new(Error)
|
15
|
+
|
12
16
|
# @api public
|
13
17
|
# @since 0.1.0
|
14
18
|
LockAcquiermentTimeoutError = Class.new(Error)
|
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.15
|
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-02-
|
11
|
+
date: 2024-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -92,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
94
|
requirements: []
|
95
|
-
rubygems_version: 3.
|
95
|
+
rubygems_version: 3.5.1
|
96
96
|
signing_key:
|
97
97
|
specification_version: 4
|
98
98
|
summary: Queued distributed locks based on Redis.
|