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 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.