redis_queued_locks 0.0.14 → 0.0.15
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 +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.
|