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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b4ae233fe0c41b159f78f44fde1623d54e5bb80bc675c06e11d89741f41cc8d
4
- data.tar.gz: 0c79574d32f3e807d7005e64616ce316be64e3e8553df8e65d15e821ee3e0559
3
+ metadata.gz: 3270e2a14a4eed87379d84fe0622f4a231a681fb1e48325a1d6e22becafb5a60
4
+ data.tar.gz: b67274aeec060e1bef07d64933928c5be3632cc68f88f12cbe01b9105ae41478
5
5
  SHA512:
6
- metadata.gz: 9a0cbdc2c84e9b5da48008a881fe2994bbdaba859d1795cd599d272acb6004c445b3784c311d90f1924bf74c161a58784d6025b3185f3da02f6f97486783eb81
7
- data.tar.gz: 61c730ae6bdd25a3a49ca4da36388e9c08280e36eb83de6edaf6dc879b447c5109e6321e7e8b160a973d5eb8abfcb35d8090984f26aca04c738ad7a6e797c967
6
+ metadata.gz: a40531666d37dc44d738fba4e67e027520a58f029c392a46db8a2e465200d4162ad1fdd44e16b17001edab183b8f9f90621d35f6b026a9c2628abc00ab9ea5b3
7
+ data.tar.gz: 40392bb5de7d615e52e4a92b5f193c7c72970d220a9963ef89346364788c2dc1d11ef5664b20d0864dd3b151aba6a1f6bb76b45cdc5c016882dd51883df25281
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.15] - 2024-02-28
4
+ ### Added
5
+ - An ability to fail fast if the required lock is already obtained;
6
+
3
7
  ## [0.0.14] - 2024-02-28
4
8
  ### Changed
5
9
  - Minor documentation updates;
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
- - **redis_queued_locks.lock_obtained**
411
- - **redis_queued_locks.lock_hold_and_release**
412
- - **redis_queued_locks.explicit_lock_release**
413
- - **redis_queued_locks.explicit_all_locks_release**
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
- - `ttl` - `integer`/`milliseconds` - lock ttl;
421
- - `acq_id` - `string` - lock acquier identifier;
422
- - `lock_key` - `string` - lock name;
423
- - `ts` - `integer`/`epoch` - the time when the lock was obtaiend;
424
- - `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
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
- - `hold_time` - `float`/`milliseconds` - lock hold time;
430
- - `ttl` - `integer`/`milliseconds` - lock ttl;
431
- - `acq_id` - `string` - lock acquier identifier;
432
- - `lock_key` - `string` - lock name;
433
- - `ts` - `integer`/`epoch` - the time when lock was obtained;
434
- - `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
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
- - `at` - `integer`/`epoch` - the time when the lock was released;
439
- - `rel_time` - `float`/`milliseconds` - time spent on lock releasing;
440
- - `lock_key` - `string` - released lock (lock name);
441
- - `lock_key_queue` - `string` - released lock queue (lock queue name);
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
- - `rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
446
- - `at` - `integer`/`epoch` - the time when the operation has ended;
447
- - `rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
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` object and `in progress queue` in RedisDB that will extend an acquired lock for long-running blocks of code (that invoked "under" the lock);
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(redis, lock_key, lock_key_queue, acquier_id, acquier_position, ttl, queue_ttl)
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 1: add an acquier to the lock acquirement queue
26
- res = redis.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
27
-
28
- RedisQueuedLocks.debug(
29
- "Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
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
- RedisQueuedLocks.debug(
48
- "Step №3: какой процесс в очереди сейчас ждет. " \
49
- "[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
50
- )
43
+ RedisQueuedLocks.debug(
44
+ "Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
45
+ )
51
46
 
52
- # Step 4: check the actual acquier: is it ours? are we aready to lock?
53
- unless waiting_acquier == acquier_id
54
- # Step ROLLBACK 1.1: our time hasn't come yet. retry!
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 ROLLBACK 1: не одинаковые ключи. выходим. " \
58
- "[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
56
+ "Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
59
57
  )
60
58
 
61
- inter_result = :acquier_is_not_first_in_queue
62
- else
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
- "Ste5: Ищем требуемый лок. " \
70
- "[HGET<#{lock_key}>: " \
71
- "#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
63
+ "Step3: какой процесс в очереди сейчас ждет. " \
64
+ "[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
72
65
  )
73
66
 
74
- if locked_by_acquier
75
- # Step ROLLBACK 2: required lock is stil acquired. retry!
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 №2: Ключ уже занят. Ничего не делаем. " \
79
- "[Занят процессом: #{locked_by_acquier}]"
72
+ "Step ROLLBACK №1: не одинаковые ключи. выходим. " \
73
+ "[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
80
74
  )
81
75
 
82
- inter_result = :lock_is_still_acquired
76
+ inter_result = :acquier_is_not_first_in_queue
83
77
  else
84
- # NOTE: required lock is free and ready to be acquired! acquire!
78
+ # NOTE: our time has come! let's try to acquire the lock!
85
79
 
86
- # Step 6.1: remove our acquier from waiting queue
87
- transact.call('ZPOPMIN', lock_key_queue, '1')
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
- "===> <FINAL> Step 6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
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
+ "Ste5: Ищем требуемый лок. " \
85
+ "[HGET<#{lock_key}>: " \
86
+ "#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
104
87
  )
105
88
 
106
- # Step 6.3: set the lock expiration time in order to prevent "infinite locks"
107
- transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
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.a: lock is still acquired by another process => failed to acquire
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.b: lock is already acquired durign the acquire race => failed to acquire
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.c: locked! :) let's go! => successfully acquired
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.d: unknown behaviour :thinking:
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: reached the retry limit => quit from the loop
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
- # NOTE: reached the retry limit => dequeue from the lock queue
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
- raise(LockAcquiermentRetryLimitError, <<~ERROR_MESSAGE.strip) if raise_errors
173
- Failed to acquire the lock "#{lock_key}"
174
- for the given retry_count limit (#{retry_count} times).
175
- ERROR_MESSAGE
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
- unless acq_process[:result] == :retry_limit_reached
215
- # NOTE: we have only two situations if lock is not acquired:
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', :integer)
26
- validate('retry_jitter', :integer)
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[:default_timeout],
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)
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.14
9
- VERSION = '0.0.14'
8
+ # @version 0.0.15
9
+ VERSION = '0.0.15'
10
10
  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.14
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-27 00:00:00.000000000 Z
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.3.7
95
+ rubygems_version: 3.5.1
96
96
  signing_key:
97
97
  specification_version: 4
98
98
  summary: Queued distributed locks based on Redis.