redis_queued_locks 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,16 +2,20 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
+ # @version 1.7.0
5
6
  # rubocop:disable Metrics/ModuleLength
6
7
  # rubocop:disable Metrics/MethodLength
7
8
  # rubocop:disable Metrics/ClassLength
8
9
  # rubocop:disable Metrics/BlockNesting
9
10
  # rubocop:disable Style/IfInsideElse
10
11
  module RedisQueuedLocks::Acquier::AcquireLock
12
+ require_relative 'acquire_lock/log_visitor'
13
+ require_relative 'acquire_lock/instr_visitor'
11
14
  require_relative 'acquire_lock/delay_execution'
12
15
  require_relative 'acquire_lock/with_acq_timeout'
13
16
  require_relative 'acquire_lock/yield_expire'
14
17
  require_relative 'acquire_lock/try_to_lock'
18
+ require_relative 'acquire_lock/dequeue_from_lock_queue'
15
19
 
16
20
  # @since 1.0.0
17
21
  extend TryToLock
@@ -21,8 +25,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
21
25
  extend YieldExpire
22
26
  # @since 1.0.0
23
27
  extend WithAcqTimeout
24
- # @since 1.0.0
25
- extend RedisQueuedLocks::Utilities
28
+ # @since 1.7.0
29
+ extend DequeueFromLockQueue
26
30
 
27
31
  class << self
28
32
  # @param redis [RedisClient]
@@ -86,6 +90,16 @@ module RedisQueuedLocks::Acquier::AcquireLock
86
90
  # - `:extendable_work_through`;
87
91
  # - `:wait_for_lock`;
88
92
  # - `:dead_locking`;
93
+ # @option access_strategy [Symbol]
94
+ # - The way in which the lock will be obtained;
95
+ # - By default it uses `:queued` strategy;
96
+ # - Supports following strategies:
97
+ # - `:queued` (FIFO): the classic queued behavior (default), your lock will be
98
+ # obitaned if you are first in queue and the required lock is free;
99
+ # - `:random` (RANDOM): obtain a lock without checking the positions in the queue
100
+ # (but with checking the limist, retries, timeouts and so on). if lock is
101
+ # free to obtain - it will be obtained;
102
+ # - pre-configured in `config[:default_access_strategy]`;
89
103
  # @option log_sampling_enabled [Boolean]
90
104
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
91
105
  # - disabled by default;
@@ -129,7 +143,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
129
143
  #
130
144
  # @api private
131
145
  # @since 1.0.0
132
- # @version 1.6.0
146
+ # @version 1.7.0
133
147
  def acquire_lock(
134
148
  redis,
135
149
  lock_name,
@@ -153,6 +167,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
153
167
  logger:,
154
168
  log_lock_try:,
155
169
  conflict_strategy:,
170
+ access_strategy:,
156
171
  log_sampling_enabled:,
157
172
  log_sampling_percent:,
158
173
  log_sampler:,
@@ -236,19 +251,16 @@ module RedisQueuedLocks::Acquier::AcquireLock
236
251
  lock_key_queue,
237
252
  queue_ttl,
238
253
  acquier_id,
254
+ access_strategy,
239
255
  log_sampled,
240
256
  instr_sampled
241
257
  )
242
258
  end
243
259
 
244
- run_non_critical do
245
- logger.debug do
246
- "[redis_queued_locks.start_lock_obtaining] " \
247
- "lock_key => '#{lock_key}' " \
248
- "queue_ttl => #{queue_ttl} " \
249
- "acq_id => '#{acquier_id}'"
250
- end
251
- end if log_sampled
260
+ LogVisitor.start_lock_obtaining(
261
+ logger, log_sampled,
262
+ lock_key, queue_ttl, acquier_id, access_strategy
263
+ )
252
264
 
253
265
  # Step 2: try to lock with timeout
254
266
  with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
@@ -256,28 +268,21 @@ module RedisQueuedLocks::Acquier::AcquireLock
256
268
 
257
269
  # Step 2.1: cyclically try to obtain the lock
258
270
  while acq_process[:should_try]
259
- run_non_critical do
260
- logger.debug do
261
- "[redis_queued_locks.start_try_to_lock_cycle] " \
262
- "lock_key => '#{lock_key}' " \
263
- "queue_ttl => #{queue_ttl} " \
264
- "acq_id => '{#{acquier_id}'"
265
- end
266
- end if log_sampled
271
+
272
+ LogVisitor.start_try_to_lock_cycle(
273
+ logger, log_sampled,
274
+ lock_key, queue_ttl, acquier_id, access_strategy
275
+ )
267
276
 
268
277
  # Step 2.X: check the actual score: is it in queue ttl limit or not?
269
278
  if RedisQueuedLocks::Resource.dead_score_reached?(acquier_position, queue_ttl)
270
279
  # Step 2.X.X: dead score reached => re-queue the lock request with the new score;
271
280
  acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
272
281
 
273
- run_non_critical do
274
- logger.debug do
275
- "[redis_queued_locks.dead_score_reached__reset_acquier_position] " \
276
- "lock_key => '#{lock_key} " \
277
- "queue_ttl => #{queue_ttl} " \
278
- "acq_id => '#{acquier_id}'"
279
- end
280
- end if log_sampled
282
+ LogVisitor.dead_score_reached__reset_acquier_position(
283
+ logger, log_sampled,
284
+ lock_key, queue_ttl, acquier_id, access_strategy
285
+ )
281
286
  end
282
287
 
283
288
  try_to_lock(
@@ -292,6 +297,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
292
297
  queue_ttl,
293
298
  fail_fast,
294
299
  conflict_strategy,
300
+ access_strategy,
295
301
  meta,
296
302
  log_sampled,
297
303
  instr_sampled
@@ -309,72 +315,38 @@ module RedisQueuedLocks::Acquier::AcquireLock
309
315
  # Step X: (instrumentation)
310
316
  if acq_process[:result][:process] == :extendable_conflict_work_through
311
317
  # instrumetnation: (reentrant lock with ttl extension)
312
- run_non_critical do
313
- logger.debug do
314
- "[redis_queued_locks.extendable_reentrant_lock_obtained] " \
315
- "lock_key => '#{result[:lock_key]}' " \
316
- "queue_ttl => #{queue_ttl} " \
317
- "acq_id => '#{acquier_id}' " \
318
- "acq_time => #{acq_time} (ms)"
319
- end
320
- end if log_sampled
321
-
322
- run_non_critical do
323
- instrumenter.notify('redis_queued_locks.extendable_reentrant_lock_obtained', {
324
- lock_key: result[:lock_key],
325
- ttl: result[:ttl],
326
- acq_id: result[:acq_id],
327
- ts: result[:ts],
328
- acq_time: acq_time,
329
- instrument:
330
- })
331
- end if instr_sampled
318
+ LogVisitor.extendable_reentrant_lock_obtained(
319
+ logger, log_sampled,
320
+ result[:lock_key], queue_ttl, acquier_id, acq_time, access_strategy
321
+ )
322
+ InstrVisitor.extendable_reentrant_lock_obtained(
323
+ instrumenter, instr_sampled,
324
+ result[:lock_key], result[:ttl], result[:acq_id], result[:ts], acq_time,
325
+ instrument
326
+ )
332
327
  elsif acq_process[:result][:process] == :conflict_work_through
333
328
  # instrumetnation: (reentrant lock without ttl extension)
334
- run_non_critical do
335
- logger.debug do
336
- "[redis_queued_locks.reentrant_lock_obtained] " \
337
- "lock_key => '#{result[:lock_key]}' " \
338
- "queue_ttl => #{queue_ttl} " \
339
- "acq_id => '#{acquier_id}' " \
340
- "acq_time => #{acq_time} (ms)"
341
- end
342
- end if log_sampled
343
-
344
- run_non_critical do
345
- instrumenter.notify('redis_queued_locks.reentrant_lock_obtained', {
346
- lock_key: result[:lock_key],
347
- ttl: result[:ttl],
348
- acq_id: result[:acq_id],
349
- ts: result[:ts],
350
- acq_time: acq_time,
351
- instrument:
352
- })
353
- end if instr_sampled
329
+ LogVisitor.reentrant_lock_obtained(
330
+ logger, log_sampled,
331
+ result[:lock_key], queue_ttl, acquier_id, acq_time, access_strategy
332
+ )
333
+ InstrVisitor.reentrant_lock_obtained(
334
+ instrumenter, instr_sampled,
335
+ result[:lock_key], result[:ttl], result[:acq_id], result[:ts], acq_time,
336
+ instrument
337
+ )
354
338
  else
355
339
  # instrumentation: (classic lock obtain)
356
340
  # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
357
- run_non_critical do
358
- logger.debug do
359
- "[redis_queued_locks.lock_obtained] " \
360
- "lock_key => '#{result[:lock_key]}' " \
361
- "queue_ttl => #{queue_ttl} " \
362
- "acq_id => '#{acquier_id}' " \
363
- "acq_time => #{acq_time} (ms)"
364
- end
365
- end if log_sampled
366
-
367
- # Step X (instrumentation): lock obtained
368
- run_non_critical do
369
- instrumenter.notify('redis_queued_locks.lock_obtained', {
370
- lock_key: result[:lock_key],
371
- ttl: result[:ttl],
372
- acq_id: result[:acq_id],
373
- ts: result[:ts],
374
- acq_time: acq_time,
375
- instrument:
376
- })
377
- end if instr_sampled
341
+ LogVisitor.lock_obtained(
342
+ logger, log_sampled,
343
+ result[:lock_key], queue_ttl, acquier_id, acq_time, access_strategy
344
+ )
345
+ InstrVisitor.lock_obtained(
346
+ instrumenter, instr_sampled,
347
+ result[:lock_key], result[:ttl], result[:acq_id], result[:ts], acq_time,
348
+ instrument
349
+ )
378
350
  end
379
351
 
380
352
  # Step 2.1.a: successfully acquired => build the result
@@ -478,6 +450,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
478
450
  logger,
479
451
  lock_key,
480
452
  acquier_id,
453
+ access_strategy,
481
454
  timed,
482
455
  ttl_shift,
483
456
  ttl,
@@ -499,30 +472,30 @@ module RedisQueuedLocks::Acquier::AcquireLock
499
472
  if acq_process[:result][:process] == :extendable_conflict_work_through ||
500
473
  acq_process[:result][:process] == :conflict_work_through
501
474
  # Step X (instrumentation): reentrant_lock_hold_completes
502
- run_non_critical do
503
- instrumenter.notify('redis_queued_locks.reentrant_lock_hold_completes', {
504
- hold_time: acq_process[:hold_time],
505
- ttl: acq_process[:lock_info][:ttl],
506
- acq_id: acq_process[:lock_info][:acq_id],
507
- ts: acq_process[:lock_info][:ts],
508
- lock_key: acq_process[:lock_info][:lock_key],
509
- acq_time: acq_process[:acq_time],
510
- instrument:
511
- })
512
- end if instr_sampled
475
+ InstrVisitor.reentrant_lock_hold_completes(
476
+ instrumenter,
477
+ instr_sampled,
478
+ acq_process[:lock_info][:lock_key],
479
+ acq_process[:lock_info][:ttl],
480
+ acq_process[:lock_info][:acq_id],
481
+ acq_process[:lock_info][:ts],
482
+ acq_process[:acq_time],
483
+ acq_process[:hold_time],
484
+ instrument
485
+ )
513
486
  else
514
487
  # Step X (instrumentation): lock_hold_and_release
515
- run_non_critical do
516
- instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
517
- hold_time: acq_process[:hold_time],
518
- ttl: acq_process[:lock_info][:ttl],
519
- acq_id: acq_process[:lock_info][:acq_id],
520
- ts: acq_process[:lock_info][:ts],
521
- lock_key: acq_process[:lock_info][:lock_key],
522
- acq_time: acq_process[:acq_time],
523
- instrument:
524
- })
525
- end if instr_sampled
488
+ InstrVisitor.lock_hold_and_release(
489
+ instrumenter,
490
+ instr_sampled,
491
+ acq_process[:lock_info][:lock_key],
492
+ acq_process[:lock_info][:ttl],
493
+ acq_process[:lock_info][:acq_id],
494
+ acq_process[:lock_info][:ts],
495
+ acq_process[:lock_info][:lock_key],
496
+ acq_process[:acq_time],
497
+ instrument
498
+ )
526
499
  end
527
500
  end
528
501
  else
@@ -23,6 +23,7 @@ class RedisQueuedLocks::Client
23
23
  setting :dead_request_ttl, (1 * 24 * 60 * 60 * 1000) # NOTE: 1 day in milliseconds
24
24
  setting :is_timed_by_default, false
25
25
  setting :default_conflict_strategy, :wait_for_lock
26
+ setting :default_access_strategy, :queued
26
27
  setting :log_sampling_enabled, false
27
28
  setting :log_sampling_percent, 15
28
29
  setting :log_sampler, RedisQueuedLocks::Logging::Sampler
@@ -57,6 +58,12 @@ class RedisQueuedLocks::Client
57
58
  val == :dead_locking
58
59
  # rubocop:enable Layout/MultilineOperationIndentation
59
60
  end
61
+ validate('default_access_strategy') do |val|
62
+ # rubocop:disable Layout/MultilineOperationIndentation
63
+ val == :queued ||
64
+ val == :random
65
+ # rubocop:enable Layout/MultilineOperationIndentation
66
+ end
60
67
  end
61
68
 
62
69
  # @return [RedisClient]
@@ -126,6 +133,16 @@ class RedisQueuedLocks::Client
126
133
  # - `:wait_for_lock` - (default) - work in classic way
127
134
  # (with timeouts, retry delays, retry limits, etc - in classic way :));
128
135
  # - `:dead_locking` - fail with deadlock exception;
136
+ # @option access_strategy [Symbol]
137
+ # - The way in which the lock will be obtained;
138
+ # - By default it uses `:queued` strategy;
139
+ # - Supports following strategies:
140
+ # - `:queued` (FIFO): the classic queued behavior (default), your lock will be
141
+ # obitaned if you are first in queue and the required lock is free;
142
+ # - `:random` (RANDOM): obtain a lock without checking the positions in the queue
143
+ # (but with checking the limist, retries, timeouts and so on). if lock is
144
+ # free to obtain - it will be obtained;
145
+ # - pre-configured in `config[:default_access_strategy]`;
129
146
  # @option meta [NilClass,Hash<String|Symbol,Any>]
130
147
  # - A custom metadata wich will be passed to the lock data in addition to the existing data;
131
148
  # - Metadata can not contain reserved lock data keys;
@@ -189,7 +206,7 @@ class RedisQueuedLocks::Client
189
206
  #
190
207
  # @api public
191
208
  # @since 1.0.0
192
- # @version 1.6.0
209
+ # @version 1.7.0
193
210
  # rubocop:disable Metrics/MethodLength
194
211
  def lock(
195
212
  lock_name,
@@ -203,6 +220,7 @@ class RedisQueuedLocks::Client
203
220
  raise_errors: false,
204
221
  fail_fast: false,
205
222
  conflict_strategy: config[:default_conflict_strategy],
223
+ access_strategy: config[:default_access_strategy],
206
224
  identity: uniq_identity,
207
225
  meta: nil,
208
226
  logger: config[:logger],
@@ -236,6 +254,7 @@ class RedisQueuedLocks::Client
236
254
  identity:,
237
255
  fail_fast:,
238
256
  conflict_strategy:,
257
+ access_strategy:,
239
258
  meta:,
240
259
  logger:,
241
260
  log_lock_try:,
@@ -255,7 +274,7 @@ class RedisQueuedLocks::Client
255
274
  #
256
275
  # @api public
257
276
  # @since 1.0.0
258
- # @version 1.6.0
277
+ # @version 1.7.0
259
278
  # rubocop:disable Metrics/MethodLength
260
279
  def lock!(
261
280
  lock_name,
@@ -268,6 +287,7 @@ class RedisQueuedLocks::Client
268
287
  retry_jitter: config[:retry_jitter],
269
288
  fail_fast: false,
270
289
  conflict_strategy: config[:default_conflict_strategy],
290
+ access_strategy: config[:default_access_strategy],
271
291
  identity: uniq_identity,
272
292
  instrumenter: config[:instrumenter],
273
293
  meta: nil,
@@ -300,6 +320,7 @@ class RedisQueuedLocks::Client
300
320
  instrument:,
301
321
  instrumenter:,
302
322
  conflict_strategy:,
323
+ access_strategy:,
303
324
  log_sampling_enabled:,
304
325
  log_sampling_percent:,
305
326
  log_sampler:,
@@ -2,7 +2,6 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- # @version 1.5.0
6
5
  module RedisQueuedLocks::Utilities
7
6
  module_function
8
7
 
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 1.6.0
9
- VERSION = '1.6.0'
8
+ # @version 1.7.0
9
+ VERSION = '1.7.0'
10
10
  end
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
@@ -10,9 +10,29 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['Rustam Ibragimov']
11
11
  spec.email = ['iamdaiver@gmail.com']
12
12
 
13
- spec.summary = 'Queued distributed locks based on Redis.'
14
- spec.description = 'Distributed locks with "lock acquisition queue" ' \
15
- 'capabilities based on the Redis Database.'
13
+ spec.summary =
14
+ 'Distributed locks with "prioritized lock acquisition queue" ' \
15
+ 'capabilities based on the Redis Database.'
16
+
17
+ spec.description =
18
+ 'Distributed locks with "prioritized lock acquisition queue" capabilities ' \
19
+ 'based on the Redis Database. ' \
20
+ # ---
21
+ 'Each lock request is put into the request queue ' \
22
+ "(each lock is hosted by it's own queue separately from other queues) and processed " \
23
+ 'in order of their priority (FIFO). ' \
24
+ # ---
25
+ 'Each lock request lives some period of time (RTTL) ' \
26
+ '(with requeue capabilities) which guarantees the request queue will never be stacked. ' \
27
+ # ---
28
+ 'In addition to the classic `queued` (FIFO) strategy RQL supports ' \
29
+ '`random` (RANDOM) lock obtaining strategy when any acquirer from the lock queue ' \
30
+ 'can obtain the lock regardless the position in the queue. ' \
31
+ # ---
32
+ 'Provides flexible invocation flow, parametrized limits ' \
33
+ '(lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), ' \
34
+ 'logging and instrumentation.'
35
+
16
36
  spec.homepage = 'https://github.com/0exp/redis_queued_locks'
17
37
  spec.license = 'MIT'
18
38
 
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: 1.6.0
4
+ version: 1.7.0
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-05-25 00:00:00.000000000 Z
11
+ date: 2024-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -38,8 +38,8 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.28'
41
- description: Distributed locks with "lock acquisition queue" capabilities based on
42
- the Redis Database.
41
+ description: 'Distributed locks with "prioritized lock acquisition queue" capabilities
42
+ based on the Redis Database. '
43
43
  email:
44
44
  - iamdaiver@gmail.com
45
45
  executables: []
@@ -58,9 +58,15 @@ files:
58
58
  - lib/redis_queued_locks/acquier.rb
59
59
  - lib/redis_queued_locks/acquier/acquire_lock.rb
60
60
  - lib/redis_queued_locks/acquier/acquire_lock/delay_execution.rb
61
+ - lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue.rb
62
+ - lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb
63
+ - lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb
64
+ - lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb
61
65
  - lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb
66
+ - lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb
62
67
  - lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb
63
68
  - lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb
69
+ - lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb
64
70
  - lib/redis_queued_locks/acquier/clear_dead_requests.rb
65
71
  - lib/redis_queued_locks/acquier/extend_lock_ttl.rb
66
72
  - lib/redis_queued_locks/acquier/is_locked.rb
@@ -87,6 +93,7 @@ files:
87
93
  - lib/redis_queued_locks/resource.rb
88
94
  - lib/redis_queued_locks/utilities.rb
89
95
  - lib/redis_queued_locks/version.rb
96
+ - lib/redis_queued_locks/watcher.rb
90
97
  - redis_queued_locks.gemspec
91
98
  homepage: https://github.com/0exp/redis_queued_locks
92
99
  licenses:
@@ -113,5 +120,6 @@ requirements: []
113
120
  rubygems_version: 3.3.7
114
121
  signing_key:
115
122
  specification_version: 4
116
- summary: Queued distributed locks based on Redis.
123
+ summary: Distributed locks with "prioritized lock acquisition queue" capabilities
124
+ based on the Redis Database.
117
125
  test_files: []