redis_queued_locks 1.16.2 → 1.18.0

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +281 -0
  3. data/.claude/rules/acquirer.md +81 -0
  4. data/.claude/rules/arguments.md +60 -0
  5. data/.claude/rules/git.md +46 -0
  6. data/.claude/rules/logic.md +63 -0
  7. data/.claude/rules/swarm.md +120 -0
  8. data/.claude/rules/tests.md +48 -0
  9. data/.claude/rules/type-checking.md +50 -0
  10. data/.claude/rules/visitors.md +69 -0
  11. data/.rubocop.rbs.yml +31 -0
  12. data/.rubocop.yml +3 -3
  13. data/.ruby-version +1 -1
  14. data/CHANGELOG.md +80 -0
  15. data/CLAUDE.md +36 -0
  16. data/README.md +367 -82
  17. data/Rakefile +31 -2
  18. data/github_ci/.keep +0 -0
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/delay_execution.rb +2 -2
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +6 -3
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +9 -7
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +20 -10
  23. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +34 -16
  24. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +240 -28
  25. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +398 -132
  26. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +12 -6
  27. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +33 -9
  28. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +73 -24
  29. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +9 -1
  30. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +182 -3
  31. data/lib/redis_queued_locks/acquirer/is_locked.rb +19 -2
  32. data/lib/redis_queued_locks/acquirer/is_queued.rb +6 -2
  33. data/lib/redis_queued_locks/acquirer/lock_info.rb +96 -6
  34. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +8 -4
  35. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +18 -7
  36. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +126 -29
  37. data/lib/redis_queued_locks/acquirer/locks.rb +103 -6
  38. data/lib/redis_queued_locks/acquirer/queue_info.rb +38 -9
  39. data/lib/redis_queued_locks/acquirer/queues.rb +28 -4
  40. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +35 -1
  41. data/lib/redis_queued_locks/acquirer/release_lock.rb +40 -9
  42. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +43 -3
  43. data/lib/redis_queued_locks/acquirer/release_read_lock.rb +143 -0
  44. data/lib/redis_queued_locks/acquirer.rb +1 -0
  45. data/lib/redis_queued_locks/client.rb +274 -158
  46. data/lib/redis_queued_locks/config/dsl.rb +4 -10
  47. data/lib/redis_queued_locks/resource.rb +117 -9
  48. data/lib/redis_queued_locks/swarm/flush_zombies.rb +46 -6
  49. data/lib/redis_queued_locks/swarm/supervisor.rb +1 -1
  50. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +166 -44
  51. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +45 -47
  52. data/lib/redis_queued_locks/swarm/zombie_info.rb +42 -0
  53. data/lib/redis_queued_locks/version.rb +2 -2
  54. data/rbs_collection.lock.yaml +10 -2
  55. data/rbs_collection.yaml +0 -2
  56. data/redis_queued_locks.gemspec +1 -1
  57. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -1
  58. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +0 -1
  59. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +5 -0
  60. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +12 -6
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +74 -7
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +4 -2
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -0
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +4 -2
  65. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +19 -1
  66. data/sig/redis_queued_locks/acquirer/lock_info.rbs +9 -1
  67. data/sig/redis_queued_locks/acquirer/locks.rbs +10 -1
  68. data/sig/redis_queued_locks/acquirer/queue_info.rbs +2 -4
  69. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  70. data/sig/redis_queued_locks/acquirer/release_lock.rbs +8 -1
  71. data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +44 -0
  72. data/sig/redis_queued_locks/client.rbs +22 -2
  73. data/sig/redis_queued_locks/resource.rbs +9 -2
  74. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +1 -1
  75. data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +13 -5
  76. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +6 -6
  77. data/sig/redis_queued_locks/swarm/zombie_info.rbs +5 -0
  78. metadata +17 -5
  79. data/github_ci/ruby3.3.gemfile +0 -17
  80. data/github_ci/ruby3.3.gemfile.lock +0 -216
@@ -11,11 +11,12 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
11
11
  # @param acquirer_id [String]
12
12
  # @param host_id [String]
13
13
  # @param access_strategy [Symbol]
14
+ # @param rw_mode [Symbol]
14
15
  # @return [void]
15
16
  #
16
17
  # @api private
17
18
  # @since 1.7.0
18
- # @version 1.9.0
19
+ # @version 1.18.0
19
20
  def expire_lock(
20
21
  logger,
21
22
  log_sampled,
@@ -23,7 +24,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
23
24
  queue_ttl,
24
25
  acquirer_id,
25
26
  host_id,
26
- access_strategy
27
+ access_strategy,
28
+ rw_mode
27
29
  )
28
30
  return unless log_sampled
29
31
 
@@ -33,7 +35,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
33
35
  "queue_ttl => #{queue_ttl} " \
34
36
  "acq_id => '#{acquirer_id}' " \
35
37
  "hst_id => '#{host_id}' " \
36
- "acs_strat => '#{access_strategy}'"
38
+ "acs_strat => '#{access_strategy}' " \
39
+ "rw_mode => '#{rw_mode}'"
37
40
  end rescue nil
38
41
  end
39
42
 
@@ -45,11 +48,12 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
45
48
  # @param acquirer_id [String]
46
49
  # @param host_id [String]
47
50
  # @param access_strategy [Symbol]
51
+ # @param rw_mode [Symbol]
48
52
  # @return [void]
49
53
  #
50
54
  # @api private
51
55
  # @since 1.7.0
52
- # @version 1.9.0
56
+ # @version 1.18.0
53
57
  def decrease_lock(
54
58
  logger,
55
59
  log_sampled,
@@ -58,7 +62,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
58
62
  queue_ttl,
59
63
  acquirer_id,
60
64
  host_id,
61
- access_strategy
65
+ access_strategy,
66
+ rw_mode
62
67
  )
63
68
  return unless log_sampled
64
69
 
@@ -69,7 +74,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
69
74
  "queue_ttl => #{queue_ttl} " \
70
75
  "acq_id => '#{acquirer_id}' " \
71
76
  "hst_id => '#{host_id}' " \
72
- "acs_strat => '#{access_strategy}'"
77
+ "acs_strat => '#{access_strategy}' " \
78
+ "rw_mode => '#{rw_mode}'"
73
79
  end rescue nil
74
80
  end
75
81
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.3.0
5
- # @version 1.7.0
5
+ # @version 1.18.0
6
6
  module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
7
7
  require_relative 'yield_expire/log_visitor'
8
8
 
@@ -18,6 +18,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
18
18
  # @param redis [RedisClient] Redis connection.
19
19
  # @param logger [::Logger,#debug] Logger object.
20
20
  # @param lock_key [String] Obtained lock key that should be expired.
21
+ # @param lock_readers_key [String] Registry of read lock holders.
22
+ # @param read_lock_key [String] Read lock data of the current acquirer.
23
+ # @param rw_mode [Symbol]
24
+ # - The mode of the obtained lock (`:read` or `:write`);
25
+ # - `:read` => only the read lock of the current acquirer is released/decreased;
21
26
  # @param acquirer_id [String] Acquirer identifier.
22
27
  # @param host_id [String] Host identifier.
23
28
  # @param access_strategy [Symbol] Lock obtaining strategy.
@@ -38,12 +43,15 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
38
43
  #
39
44
  # @api private
40
45
  # @since 1.3.0
41
- # @version 1.9.0
46
+ # @version 1.18.0
42
47
  # rubocop:disable Metrics/MethodLength
43
48
  def yield_expire(
44
49
  redis,
45
50
  logger,
46
51
  lock_key,
52
+ lock_readers_key,
53
+ read_lock_key,
54
+ rw_mode,
47
55
  acquirer_id,
48
56
  host_id,
49
57
  access_strategy,
@@ -87,7 +95,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
87
95
  acquirer_id,
88
96
  host_id,
89
97
  meta,
90
- &block # steep:ignore
98
+ &block
91
99
  )
92
100
  else
93
101
  yield
@@ -97,9 +105,17 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
97
105
  if should_expire # TODO: comment all cases/examples when should_expire is true
98
106
  LogVisitor.expire_lock(
99
107
  logger, log_sampled, lock_key,
100
- queue_ttl, acquirer_id, host_id, access_strategy
108
+ queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
101
109
  )
102
- redis.call('EXPIRE', lock_key, '0')
110
+ if rw_mode == :read
111
+ # NOTE: release only the read lock of the current acquirer (other readers keep working)
112
+ redis.multi do |transact|
113
+ transact.call('ZREM', lock_readers_key, acquirer_id)
114
+ transact.call('DEL', read_lock_key)
115
+ end
116
+ else
117
+ redis.call('EXPIRE', lock_key, '0')
118
+ end
103
119
  elsif should_decrease # TODO: comment all cases/examples when should_expire is true
104
120
  finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
105
121
  # @type var initial_time: Integer
@@ -110,11 +126,19 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
110
126
  if decreased_ttl > 0
111
127
  LogVisitor.decrease_lock(
112
128
  logger, log_sampled, lock_key,
113
- decreased_ttl, queue_ttl, acquirer_id, host_id, access_strategy
129
+ decreased_ttl, queue_ttl, acquirer_id, host_id, access_strategy, rw_mode
114
130
  )
115
- # NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
116
- redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
117
- # TODO: upload scripts to the redis
131
+ if rw_mode == :read
132
+ # NOTE:
133
+ # - decrease the read lock expiration of the current acquirer;
134
+ # - XX => do nothing if the read lock is already expired and removed from the registry;
135
+ # - the read lock data TTL is not decreased (it is not used without the read lock);
136
+ redis.call('ZADD', lock_readers_key, 'XX', 'INCR', -decreased_ttl, acquirer_id)
137
+ else
138
+ # NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
139
+ redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
140
+ # TODO: upload scripts to the redis
141
+ end
118
142
  end
119
143
  end
120
144
  end
@@ -2,12 +2,11 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- # @version 1.7.0
5
+ # @version 1.18.0
6
6
  # rubocop:disable Metrics/ModuleLength
7
7
  # rubocop:disable Metrics/MethodLength
8
8
  # rubocop:disable Metrics/ClassLength
9
9
  # rubocop:disable Metrics/BlockNesting
10
- # rubocop:disable Style/IfInsideElse
11
10
  module RedisQueuedLocks::Acquirer::AcquireLock
12
11
  require_relative 'acquire_lock/log_visitor'
13
12
  require_relative 'acquire_lock/instr_visitor'
@@ -97,7 +96,15 @@ module RedisQueuedLocks::Acquirer::AcquireLock
97
96
  # - `:wait_for_lock`;
98
97
  # - `:dead_locking`;
99
98
  # @option read_write_mode [Symbol]
100
- # - ?
99
+ # - `:write` (default) - exclusive lock: waits for the write lock and for all read locks;
100
+ # - `:read` - shared lock: waits for the write lock only (read locks do not block each other);
101
+ # - `:queued` access strategy orders read and write requests in FIFO between modes
102
+ # (read request waits for earlier write requests, write request waits for earlier
103
+ # read requests and for earlier write requests);
104
+ # - `:meta` of the read lock is stored in the read lock data of the acquirer;
105
+ # - same-process conflicts: read-to-write lock upgrade fails with
106
+ # `RedisQueuedLocks::ConflictLockObtainError` for any conflict strategy except
107
+ # `:wait_for_lock` (it waits for the read lock expiration);
101
108
  # @option access_strategy [Symbol]
102
109
  # - The way in which the lock will be obtained;
103
110
  # - By default it uses `:queued` strategy;
@@ -159,7 +166,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
159
166
  #
160
167
  # @api private
161
168
  # @since 1.0.0
162
- # @version 1.14.0
169
+ # @version 1.18.0
163
170
  def acquire_lock(
164
171
  redis,
165
172
  lock_name,
@@ -207,6 +214,24 @@ module RedisQueuedLocks::Acquirer::AcquireLock
207
214
  )
208
215
  end
209
216
 
217
+ # Step 0.1 (RW): prevent :read_write_mode incompatabilities
218
+ if read_write_mode != :read && read_write_mode != :write
219
+ raise(
220
+ RedisQueuedLocks::ArgumentError,
221
+ "`:read_write_mode` argument should be `:read` or `:write`, " \
222
+ "got #{read_write_mode.inspect}."
223
+ )
224
+ end
225
+
226
+ # Step 0.1 (RW): prevent read lock incompatabilities
227
+ # - read lock expiration time is calculated from the lock ttl;
228
+ if read_write_mode == :read && (!ttl.is_a?(::Integer) || ttl <= 0)
229
+ raise(
230
+ RedisQueuedLocks::ArgumentError,
231
+ "`:ttl` argument should be a positive Integer for read locks, got #{ttl.inspect}."
232
+ )
233
+ end
234
+
210
235
  # Step 0.2: prevent :meta incompatabiltiies (structure)
211
236
  if meta.is_a?(::Hash) && (meta.any? do |key, _value|
212
237
  key == 'acq_id' ||
@@ -219,13 +244,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock
219
244
  key == 'spc_cnt' ||
220
245
  key == 'l_spc_ext_ini_ttl' ||
221
246
  key == 'l_spc_ext_ts' ||
222
- key == 'l_spc_ts'
247
+ key == 'l_spc_ts' ||
248
+ key == 'rw_mode'
223
249
  end)
224
250
  raise(
225
251
  RedisQueuedLocks::ArgumentError,
226
252
  '`:meta` keys can not overlap reserved lock data keys ' \
227
253
  '"acq_id", "hst_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
228
- '"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts"'
254
+ '"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts", "rw_mode"'
229
255
  )
230
256
  end
231
257
 
@@ -247,8 +273,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock
247
273
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
248
274
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
249
275
 
276
+ # NOTE: (RW) write lock requests use the classic lock queue (lock_key_queue)
250
277
  read_lock_key_queue = RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
251
- write_lock_key_queue = RedisQueuedLocks::Resource.prepare_write_lock_queue(lock_name)
278
+ lock_readers_key = RedisQueuedLocks::Resource.prepare_lock_readers(lock_name)
279
+ read_lock_key = RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
252
280
 
253
281
  acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
254
282
 
@@ -286,7 +314,6 @@ module RedisQueuedLocks::Acquirer::AcquireLock
286
314
  read_write_mode,
287
315
  lock_key_queue,
288
316
  read_lock_key_queue,
289
- write_lock_key_queue,
290
317
  queue_ttl,
291
318
  acquirer_id,
292
319
  host_id,
@@ -298,7 +325,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
298
325
 
299
326
  LogVisitor.start_lock_obtaining(
300
327
  logger, log_sampled, lock_key,
301
- queue_ttl, acquirer_id, host_id, access_strategy
328
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
302
329
  )
303
330
 
304
331
  # Step 2: try to lock with timeout
@@ -318,7 +345,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
318
345
 
319
346
  LogVisitor.start_try_to_lock_cycle(
320
347
  logger, log_sampled, lock_key,
321
- queue_ttl, acquirer_id, host_id, access_strategy
348
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
322
349
  )
323
350
 
324
351
  # Step 2.X: check the actual score: is it in queue ttl limit or not?
@@ -328,7 +355,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
328
355
 
329
356
  LogVisitor.dead_score_reached__reset_acquirer_position(
330
357
  logger, log_sampled, lock_key,
331
- queue_ttl, acquirer_id, host_id, access_strategy
358
+ queue_ttl, acquirer_id, host_id, access_strategy, read_write_mode
332
359
  )
333
360
  end
334
361
 
@@ -342,7 +369,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
342
369
  read_write_mode,
343
370
  lock_key_queue,
344
371
  read_lock_key_queue,
345
- write_lock_key_queue,
372
+ lock_readers_key,
373
+ read_lock_key,
346
374
  acquirer_id,
347
375
  host_id,
348
376
  acquirer_position,
@@ -376,10 +404,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock
376
404
  # instrumetnation: (reentrant lock with ttl extension)
377
405
  LogVisitor.extendable_reentrant_lock_obtained(
378
406
  logger, log_sampled, result[:lock_key],
379
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy
407
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
380
408
  )
381
409
  InstrVisitor.extendable_reentrant_lock_obtained(
382
- instrumenter, instr_sampled, result[:lock_key],
410
+ instrumenter, instr_sampled, result[:lock_key], read_write_mode,
383
411
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
384
412
  instrument
385
413
  )
@@ -387,10 +415,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock
387
415
  # instrumetnation: (reentrant lock without ttl extension)
388
416
  LogVisitor.reentrant_lock_obtained(
389
417
  logger, log_sampled, result[:lock_key],
390
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy
418
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
391
419
  )
392
420
  InstrVisitor.reentrant_lock_obtained(
393
- instrumenter, instr_sampled, result[:lock_key],
421
+ instrumenter, instr_sampled, result[:lock_key], read_write_mode,
394
422
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
395
423
  instrument
396
424
  )
@@ -399,10 +427,10 @@ module RedisQueuedLocks::Acquirer::AcquireLock
399
427
  # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
400
428
  LogVisitor.lock_obtained(
401
429
  logger, log_sampled, result[:lock_key],
402
- queue_ttl, acquirer_id, host_id, acq_time, access_strategy
430
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy, read_write_mode
403
431
  )
404
432
  InstrVisitor.lock_obtained(
405
- instrumenter, instr_sampled, result[:lock_key],
433
+ instrumenter, instr_sampled, result[:lock_key], read_write_mode,
406
434
  result[:ttl], result[:acq_id], result[:hst_id], result[:ts], acq_time,
407
435
  instrument
408
436
  )
@@ -417,6 +445,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
417
445
  ttl: result[:ttl],
418
446
  process: result[:process]
419
447
  }
448
+ # NOTE: (RW) the mode of the really held lock (internal, used for the lock release)
449
+ acq_process[:held_rw_mode] = result[:rw_mode]
420
450
  acq_process[:acquired] = true
421
451
  acq_process[:should_try] = false
422
452
  acq_process[:acq_time] = acq_time
@@ -425,7 +455,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock
425
455
  # @type var result: Symbol
426
456
 
427
457
  # Step 2.2: failed to acquire. anylize each case and act in accordance
428
- if acq_process[:result] == :fail_fast_no_try # Step 2.2.a: fail without try
458
+ case acq_process[:result]
459
+ when :fail_fast_no_try # Step 2.2.a: fail without try
429
460
  acq_process[:should_try] = false
430
461
 
431
462
  if raise_errors
@@ -434,7 +465,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
434
465
  "Lock \"#{lock_key}\" is already obtained."
435
466
  )
436
467
  end
437
- elsif acq_process[:result] == :conflict_dead_lock # Step 2.2.b: fail after dead lock
468
+ when :conflict_dead_lock # Step 2.2.b: fail after dead lock
438
469
  acq_process[:tries] += 1
439
470
  acq_process[:should_try] = false
440
471
  acq_process[:result] = :conflict_dead_lock
@@ -447,6 +478,21 @@ module RedisQueuedLocks::Acquirer::AcquireLock
447
478
  "that is already acquired by the current acquirer (acq_id: \"#{acquirer_id}\")."
448
479
  )
449
480
  end
481
+ when :conflict_lock_upgrade # Step 2.2.c (RW): fail after lock upgrade attempt
482
+ acq_process[:tries] += 1
483
+ acq_process[:should_try] = false
484
+ acq_process[:result] = :conflict_lock_upgrade
485
+ acq_dequeue.call
486
+
487
+ if raise_errors
488
+ raise(
489
+ RedisQueuedLocks::ConflictLockObtainError,
490
+ "Lock Conflict: trying to acquire the write lock \"#{lock_key}\" " \
491
+ "while the current acquirer holds its read lock (acq_id: \"#{acquirer_id}\"): " \
492
+ "read-to-write lock upgrade is not supported " \
493
+ "(conflict strategy: :#{conflict_strategy})."
494
+ )
495
+ end
450
496
  else
451
497
  acq_process[:tries] += 1 # Step RETRY: possible retry case
452
498
 
@@ -511,6 +557,9 @@ module RedisQueuedLocks::Acquirer::AcquireLock
511
557
  redis,
512
558
  logger,
513
559
  lock_key,
560
+ lock_readers_key,
561
+ read_lock_key,
562
+ acq_process[:held_rw_mode],
514
563
  acquirer_id,
515
564
  host_id,
516
565
  access_strategy,
@@ -538,6 +587,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
538
587
  instrumenter,
539
588
  instr_sampled,
540
589
  acq_process[:lock_info][:lock_key],
590
+ read_write_mode,
541
591
  acq_process[:lock_info][:ttl],
542
592
  acq_process[:lock_info][:acq_id],
543
593
  acq_process[:lock_info][:hst_id],
@@ -552,6 +602,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock
552
602
  instrumenter,
553
603
  instr_sampled,
554
604
  acq_process[:lock_info][:lock_key],
605
+ read_write_mode,
555
606
  acq_process[:lock_info][:ttl],
556
607
  acq_process[:lock_info][:acq_id],
557
608
  acq_process[:lock_info][:hst_id],
@@ -563,15 +614,14 @@ module RedisQueuedLocks::Acquirer::AcquireLock
563
614
  end
564
615
  end
565
616
  else
566
- # rubocop:disable Layout/LineLength
567
617
  { ok: true, result: acq_process[:lock_info] } #: { ok: bool, result: Hash[Symbol,untyped] }
568
- # rubocop:enable Layout/LineLength
569
618
  end
570
619
  else
571
620
  if acq_process[:result] != :retry_limit_reached &&
572
621
  acq_process[:result] != :fail_fast_no_try &&
573
622
  acq_process[:result] != :fail_fast_after_try &&
574
- acq_process[:result] != :conflict_dead_lock
623
+ acq_process[:result] != :conflict_dead_lock &&
624
+ acq_process[:result] != :conflict_lock_upgrade
575
625
  # NOTE: we have only two situations if lock is not acquired without explicit failures:
576
626
  # - time limit is reached;
577
627
  # - retry count limit is reached;
@@ -588,4 +638,3 @@ end
588
638
  # rubocop:enable Metrics/MethodLength
589
639
  # rubocop:enable Metrics/ClassLength
590
640
  # rubocop:enable Metrics/BlockNesting
591
- # rubocop:enable Style/IfInsideElse
@@ -22,7 +22,7 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
22
22
  #
23
23
  # @api private
24
24
  # @since 1.0.0
25
- # @version 1.6.0
25
+ # @version 1.18.0
26
26
  def clear_dead_requests(
27
27
  redis_client,
28
28
  scan_size,
@@ -66,6 +66,7 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
66
66
  #
67
67
  # @api private
68
68
  # @since 1.0.0
69
+ # @version 1.18.0
69
70
  def each_lock_queue(redis_client, scan_size, &block)
70
71
  redis_client.scan(
71
72
  'MATCH',
@@ -73,6 +74,13 @@ module RedisQueuedLocks::Acquirer::ClearDeadRequests
73
74
  count: scan_size,
74
75
  &block
75
76
  )
77
+ # NOTE: (RW) read lock queues
78
+ redis_client.scan(
79
+ 'MATCH',
80
+ RedisQueuedLocks::Resource::READ_LOCK_QUEUE_PATTERN,
81
+ count: scan_size,
82
+ &block
83
+ )
76
84
  end
77
85
  end
78
86
  end