redis_queued_locks 1.21.0 → 1.23.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/project-overview.md +294 -51
  3. data/.claude/rules/acquirer.md +16 -11
  4. data/.claude/rules/arguments.md +2 -2
  5. data/.claude/rules/changelog.md +27 -0
  6. data/.claude/rules/git.md +1 -1
  7. data/.claude/rules/logic.md +14 -15
  8. data/.claude/rules/swarm.md +3 -4
  9. data/.claude/rules/tests.md +9 -9
  10. data/.claude/rules/type-checking.md +14 -16
  11. data/.claude/rules/visitors.md +11 -9
  12. data/.claude/skills/rql-code-review/SKILL.md +129 -47
  13. data/.ruby-version +1 -1
  14. data/CHANGELOG.md +122 -245
  15. data/CLAUDE.md +12 -10
  16. data/README.md +516 -175
  17. data/Rakefile +11 -0
  18. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb +4 -4
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +28 -12
  20. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +38 -16
  21. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +92 -15
  22. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +945 -272
  23. data/lib/redis_queued_locks/acquirer/acquire_lock.rb +82 -32
  24. data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +240 -45
  25. data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +15 -13
  26. data/lib/redis_queued_locks/acquirer/keys.rb +6 -2
  27. data/lib/redis_queued_locks/acquirer/lock_series/instr_visitor.rb +243 -0
  28. data/lib/redis_queued_locks/acquirer/lock_series/log_visitor.rb +472 -0
  29. data/lib/redis_queued_locks/acquirer/lock_series/release_lock_series.rb +181 -0
  30. data/lib/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rb +1051 -0
  31. data/lib/redis_queued_locks/acquirer/lock_series.rb +948 -0
  32. data/lib/redis_queued_locks/acquirer/locks.rb +6 -2
  33. data/lib/redis_queued_locks/acquirer/queue_info.rb +30 -7
  34. data/lib/redis_queued_locks/acquirer/queues.rb +17 -6
  35. data/lib/redis_queued_locks/acquirer/release_all_locks.rb +6 -2
  36. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +6 -2
  37. data/lib/redis_queued_locks/acquirer.rb +1 -1
  38. data/lib/redis_queued_locks/client.rb +116 -44
  39. data/lib/redis_queued_locks/config.rb +93 -1
  40. data/lib/redis_queued_locks/resource.rb +124 -9
  41. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +6 -5
  42. data/lib/redis_queued_locks/swarm/zombie_info.rb +6 -2
  43. data/lib/redis_queued_locks/version.rb +2 -2
  44. data/rbs_collection.lock.yaml +4 -0
  45. data/rbs_collection.yaml +2 -1
  46. data/redis_queued_locks.gemspec +21 -14
  47. data/sig/manifest.yml +1 -0
  48. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +2 -2
  49. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +2 -2
  50. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +12 -6
  51. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +21 -14
  52. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +61 -40
  53. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +160 -11
  54. data/sig/redis_queued_locks/acquirer/acquire_lock/{yield_with_expire → yield_expire}/log_visitor.rbs +4 -4
  55. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
  56. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +5 -3
  57. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +14 -1
  58. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +2 -2
  59. data/sig/redis_queued_locks/acquirer/lock_series/instr_visitor.rbs +102 -0
  60. data/sig/redis_queued_locks/acquirer/lock_series/log_visitor.rbs +154 -0
  61. data/sig/redis_queued_locks/acquirer/lock_series/release_lock_series.rbs +37 -0
  62. data/sig/redis_queued_locks/acquirer/lock_series/try_to_lock_series.rbs +198 -0
  63. data/sig/redis_queued_locks/acquirer/lock_series.rbs +106 -0
  64. data/sig/redis_queued_locks/acquirer/queues.rbs +1 -1
  65. data/sig/redis_queued_locks/client.rbs +77 -7
  66. data/sig/redis_queued_locks/config.rbs +54 -1
  67. data/sig/redis_queued_locks/instrument/sampler.rbs +3 -1
  68. data/sig/redis_queued_locks/instrument/void_notifier.rbs +3 -1
  69. data/sig/redis_queued_locks/logging/sampler.rbs +3 -1
  70. data/sig/redis_queued_locks/resource.rbs +22 -12
  71. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +1 -1
  72. data/sig/redis_queued_locks/utilities.rbs +2 -1
  73. data/sig/redis_queued_locks.rbs +7 -0
  74. metadata +35 -13
  75. data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +0 -56
  76. data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +0 -95
  77. data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +0 -484
  78. /data/sig/redis_queued_locks/{acquier.rbs → acquirer.rbs} +0 -0
@@ -1,484 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # NOTE: Lock Series PoC
4
- # rubocop:disable all
5
- # @api private
6
- # @since 1.16.0
7
- # @version 1.20.0
8
- module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
9
- require_relative 'lock_series_poc/log_visitor'
10
- require_relative 'lock_series_poc/instr_visitor'
11
-
12
- # @since 1.16.0
13
- extend RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
14
-
15
- class << self
16
- # @api private
17
- # @since 1.16.0
18
- # @version 1.21.0
19
- def lock_series_poc( # steep:ignore
20
- redis,
21
- lock_names,
22
- detailed_result:,
23
- process_id:,
24
- thread_id:,
25
- fiber_id:,
26
- ractor_id:,
27
- ttl:,
28
- queue_ttl:,
29
- lock_index_enabled:,
30
- lock_index_grace_period:,
31
- timeout:,
32
- timed:,
33
- retry_count:,
34
- retry_delay:,
35
- retry_jitter:,
36
- raise_errors:,
37
- instrumenter:,
38
- identity:,
39
- fail_fast:,
40
- meta:,
41
- detailed_acq_timeout_error:,
42
- instrument:,
43
- logger:,
44
- log_lock_try:,
45
- conflict_strategy:,
46
- read_write_mode:,
47
- access_strategy:,
48
- log_sampling_enabled:,
49
- log_sampling_percent:,
50
- log_sampler:,
51
- log_sample_this:,
52
- instr_sampling_enabled:,
53
- instr_sampling_percent:,
54
- instr_sampler:,
55
- instr_sample_this:,
56
- &block
57
- )
58
- # NOTE: (RW) the lock mode is the same for all locks of the series
59
- # (see RedisQueuedLocks::Acquirer::AcquireLock for read_write_mode validations);
60
-
61
- case meta
62
- when Hash, NilClass then nil
63
- else
64
- raise(
65
- RedisQueuedLocks::ArgumentError,
66
- "`:meta` argument should be a type of NilClass or Hash, got #{meta.class}."
67
- )
68
- end
69
-
70
- # NOTE: Symbol keys are written to the lock data as Strings (the same check as in
71
- # RedisQueuedLocks::Acquirer::AcquireLock);
72
- if meta.is_a?(::Hash) && (meta.any? do |key, _value|
73
- meta_key = key.is_a?(::Symbol) ? key.name : key
74
- meta_key == 'acq_id' ||
75
- meta_key == 'hst_id' ||
76
- meta_key == 'ts' ||
77
- meta_key == 'ini_ttl' ||
78
- meta_key == 'lock_key' ||
79
- meta_key == 'rem_ttl' ||
80
- meta_key == 'spc_ext_ttl' ||
81
- meta_key == 'spc_cnt' ||
82
- meta_key == 'l_spc_ext_ini_ttl' ||
83
- meta_key == 'l_spc_ext_ts' ||
84
- meta_key == 'l_spc_ts' ||
85
- meta_key == 'rw_mode'
86
- end)
87
- raise(
88
- RedisQueuedLocks::ArgumentError,
89
- '`:meta` keys can not overlap reserved lock data keys ' \
90
- '"acq_id", "hst_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
91
- '"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts", "rw_mode"'
92
- )
93
- end
94
-
95
- # NOTE: the series ttl is validated before the lock ttl calculation: each lock ttl of
96
- # the series includes the retry delay and jitter (a non-positive series ttl can produce
97
- # a positive lock ttl), the series ttl limits the `timed` block (the same check as in
98
- # RedisQueuedLocks::Acquirer::AcquireLock);
99
- if !ttl.is_a?(::Integer) || ttl <= 0
100
- raise(
101
- RedisQueuedLocks::ArgumentError,
102
- "`:ttl` argument should be a positive Integer, got #{ttl.inspect}."
103
- )
104
- end
105
-
106
- # NOTE: the retry delay and jitter are parts of the lock ttls of the series (validated before
107
- # the lock ttl calculation; other options are validated by
108
- # RedisQueuedLocks::Acquirer::AcquireLock on the first lock of the series, before any lock
109
- # is obtained);
110
- if !retry_delay.is_a?(::Integer) || retry_delay < 0
111
- raise(
112
- RedisQueuedLocks::ArgumentError,
113
- "`:retry_delay` argument should be a non-negative Integer, got #{retry_delay.inspect}."
114
- )
115
- end
116
- if !retry_jitter.is_a?(::Integer) || retry_jitter < 0
117
- raise(
118
- RedisQueuedLocks::ArgumentError,
119
- "`:retry_jitter` argument should be a non-negative Integer, got #{retry_jitter.inspect}."
120
- )
121
- end
122
-
123
- locks_count = lock_names.size
124
-
125
- lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
126
- lock_ttl =
127
- ttl * (locks_count - position) +
128
- retry_delay * (locks_count - position) +
129
- retry_jitter * (locks_count - position)
130
-
131
- { lock_name: lock_name, ttl: lock_ttl }
132
- end
133
-
134
- log_sampled = RedisQueuedLocks::Logging.should_log?(
135
- log_sampling_enabled,
136
- log_sample_this,
137
- log_sampling_percent,
138
- log_sampler
139
- )
140
-
141
- instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
142
- instr_sampling_enabled,
143
- instr_sample_this,
144
- instr_sampling_percent,
145
- instr_sampler
146
- )
147
-
148
- lock_keys_for_instrumentation = lock_names.map do |lock_name|
149
- RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
150
- end
151
-
152
- acquirer_id_for_instrumentation = RedisQueuedLocks::Resource.acquirer_identifier(
153
- process_id,
154
- thread_id,
155
- fiber_id,
156
- ractor_id,
157
- identity
158
- )
159
-
160
- host_id_for_instrumentation = RedisQueuedLocks::Resource.host_identifier(
161
- process_id,
162
- thread_id,
163
- ractor_id,
164
- identity
165
- )
166
-
167
- RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.start_lock_series_obtaining( # steep:ignore
168
- logger, log_sampled, lock_keys_for_instrumentation,
169
- queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
170
- read_write_mode
171
- )
172
-
173
- acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
174
- successfully_acquired_locks = [] # steep:ignore
175
- failed_locks_and_errors = {} # steep:ignore
176
- failed_on_lock = nil
177
- # NOTE: locks of the failed series are released once (exceptional or not)
178
- is_failed_series_released = false
179
-
180
- lock_acquirement_operations.map do |lock_operation_options|
181
- result =
182
- begin
183
- RedisQueuedLocks::Acquirer::AcquireLock.acquire_lock(
184
- redis,
185
- lock_operation_options[:lock_name],
186
- process_id:,
187
- thread_id:,
188
- fiber_id:,
189
- ractor_id:,
190
- ttl: lock_operation_options[:ttl],
191
- queue_ttl:,
192
- lock_index_enabled:,
193
- lock_index_grace_period:,
194
- timeout:,
195
- timed:,
196
- retry_count:,
197
- retry_delay:,
198
- retry_jitter:,
199
- raise_errors:,
200
- instrumenter:,
201
- identity:,
202
- fail_fast:,
203
- meta:,
204
- detailed_acq_timeout_error:,
205
- instrument:,
206
- logger:,
207
- log_lock_try:,
208
- conflict_strategy:,
209
- read_write_mode:,
210
- access_strategy:,
211
- log_sampling_enabled:,
212
- log_sampling_percent:,
213
- log_sampler:,
214
- log_sample_this:,
215
- instr_sampling_enabled:,
216
- instr_sampling_percent:,
217
- instr_sampler:,
218
- instr_sample_this:
219
- )
220
- rescue RedisQueuedLocks::LockAlreadyObtainedError,
221
- RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
222
- RedisQueuedLocks::LockAcquirementTimeoutError,
223
- RedisQueuedLocks::LockAcquirementRetryLimitError,
224
- RedisQueuedLocks::ConflictLockObtainError => error
225
- # NOTE: release all previously acquired locks if any next lock is already locked
226
- release_lock_series( # steep:ignore
227
- redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
228
- )
229
- is_failed_series_released = true
230
-
231
- raise(error) if raise_errors
232
- end
233
-
234
- if result[:ok]
235
- successfully_acquired_locks << {
236
- lock_name: lock_operation_options[:lock_name],
237
- ok: result[:ok],
238
- result: result[:result]
239
- }
240
- else
241
- failed_on_lock = lock_operation_options[:lock_name]
242
- failed_locks_and_errors[lock_operation_options[:lock_name]] = result
243
- # NOTE: release all previously acquired locks (non-exceptional failure: raise_errors: false)
244
- unless is_failed_series_released
245
- release_lock_series( # steep:ignore
246
- redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
247
- )
248
- is_failed_series_released = true
249
- end
250
- break
251
- end
252
- end
253
-
254
- if (successfully_acquired_locks.size == lock_names.size && (successfully_acquired_locks.all? { |res| res[:ok] }))
255
- acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
256
- acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
257
- ts = Time.now.to_f
258
-
259
- RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.lock_series_obtained( # steep:ignore
260
- logger, log_sampled, lock_keys_for_instrumentation,
261
- queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
262
- acq_time, access_strategy, read_write_mode
263
- )
264
-
265
- RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_obtained( # steep:ignore
266
- instrumenter, instr_sampled, lock_keys_for_instrumentation, read_write_mode,
267
- ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time, instrument
268
- )
269
-
270
- yield_time = RedisQueuedLocks::Utilities.clock_gettime
271
- ttl_shift = (
272
- (yield_time - acq_end_time) / 1_000.0 -
273
- RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
274
- ).ceil(2)
275
-
276
- yield_result = nil
277
- is_lock_manually_released = nil
278
- hold_time = nil
279
-
280
- begin
281
- yield_result = yield_expire( # steep:ignore
282
- redis,
283
- logger,
284
- lock_keys_for_instrumentation.last,
285
- RedisQueuedLocks::Resource.prepare_lock_readers(lock_names.last),
286
- RedisQueuedLocks::Resource.prepare_read_lock_key(
287
- lock_names.last, acquirer_id_for_instrumentation
288
- ),
289
- read_write_mode,
290
- acquirer_id_for_instrumentation,
291
- host_id_for_instrumentation,
292
- access_strategy,
293
- timed,
294
- ttl_shift,
295
- ttl,
296
- queue_ttl,
297
- meta,
298
- log_sampled,
299
- instr_sampled,
300
- false, # should_expire (expire manually)
301
- false, # should_decrease (expire manually)
302
- &block
303
- )
304
- ensure
305
- is_lock_manually_released = false
306
-
307
- # expire locks manually
308
- if block_given?
309
- release_lock_series( # steep:ignore
310
- redis, read_write_mode, successfully_acquired_locks, acquirer_id_for_instrumentation
311
- )
312
- is_lock_manually_released = true
313
- end
314
-
315
- rel_time = RedisQueuedLocks::Utilities.clock_gettime
316
- hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
317
- ts = Time.now.to_f
318
-
319
- RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.expire_lock_series( # steep:ignore
320
- logger, log_sampled, lock_keys_for_instrumentation,
321
- queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy,
322
- read_write_mode
323
- ) if is_lock_manually_released
324
-
325
- RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_hold_and_release( # steep:ignore
326
- instrumenter,
327
- instr_sampled,
328
- lock_keys_for_instrumentation,
329
- read_write_mode,
330
- ttl,
331
- acquirer_id_for_instrumentation,
332
- host_id_for_instrumentation,
333
- ts,
334
- acq_time,
335
- hold_time,
336
- instrument
337
- ) if is_lock_manually_released
338
- end
339
-
340
- if detailed_result
341
- {
342
- yield_result: yield_result,
343
- locks_release_strategy: block_given? ? :immediate_release_after_yield : :redis_key_ttl,
344
- locks_released_at: block_given? ? ts : nil,
345
- locks_acq_time: acq_time,
346
- locks_hold_time: is_lock_manually_released ? hold_time : nil,
347
- lock_series: lock_names,
348
- rql_lock_series: lock_keys_for_instrumentation
349
- }
350
- else
351
- yield_result
352
- end
353
- else
354
- acquired_locks = successfully_acquired_locks.map { |state| state[:lock_name] }
355
- missing_locks = lock_names - acquired_locks
356
-
357
- {
358
- ok: false,
359
- result: {
360
- error: :failed_to_acquire_lock_series,
361
- detailed_errors: failed_locks_and_errors,
362
- lock_series: lock_names,
363
- acquired_locks:,
364
- missing_locks:,
365
- failed_on_lock:,
366
- }
367
- }
368
- end
369
- end
370
-
371
- private
372
-
373
- # Releases locks of the series that are obtained by the series itself:
374
- # - reentrant locks (already obtained by the outer logic of the current acquirer) are kept;
375
- # - only locks of the current acquirer are released (owner check);
376
- #
377
- # @param redis [RedisClient]
378
- # @param read_write_mode [Symbol]
379
- # @param successfully_acquired_locks [Array<Hash<Symbol,Any>>]
380
- # @param acquirer_id [String]
381
- # @return [void]
382
- #
383
- # @api private
384
- # @since 1.18.0
385
- def release_lock_series(redis, read_write_mode, successfully_acquired_locks, acquirer_id) # steep:ignore
386
- lock_names = obtained_lock_names(successfully_acquired_locks) # steep:ignore
387
-
388
- if read_write_mode == :read
389
- release_read_lock_series(redis, lock_names, acquirer_id) # steep:ignore
390
- else
391
- release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
392
- end
393
- end
394
-
395
- # Releases write locks of the given acquirer (compare-and-delete: the lock can be expired
396
- # and obtained by another acquirer when the series logic exceeds the lock ttl) in one
397
- # transaction for the whole series (three round trips: WATCH, lock owners, MULTI/EXEC):
398
- # - an aborted transaction (one of the watched locks is changed) is retried with the locks
399
- # that were still owned by the acquirer at the last check: the locks of other acquirers
400
- # are neither deleted nor watched, so the retries converge;
401
- #
402
- # @param redis [RedisClient]
403
- # @param lock_names [Array<String>]
404
- # @param acquirer_id [String]
405
- # @return [void]
406
- #
407
- # @api private
408
- # @since 1.18.0
409
- # @version 1.21.0
410
- def release_write_lock_series(redis, lock_names, acquirer_id) # steep:ignore
411
- return if lock_names.empty?
412
-
413
- own_lock_keys = lock_names.map do |lock_name|
414
- RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
415
- end
416
-
417
- redis.with do |conn|
418
- until own_lock_keys.empty?
419
- released = conn.multi(watch: own_lock_keys) do |transaction|
420
- # NOTE: (pipeline layout) one reply (lock owner) per lock in the series order
421
- lock_owners = conn.pipelined do |pipeline|
422
- own_lock_keys.each { |lock_key| pipeline.call('HGET', lock_key, 'acq_id') }
423
- end
424
- own_lock_keys = own_lock_keys.select.with_index do |_lock_key, index|
425
- lock_owners[index] == acquirer_id
426
- end
427
- own_lock_keys.each { |lock_key| transaction.call('DEL', lock_key) }
428
- end
429
- # NOTE: `nil` => the transaction is aborted (a watched lock is changed) => retry
430
- break unless released.nil?
431
- end
432
- rescue RedisQueuedLocks::LockAcquirementIntermediateTimeoutError,
433
- RedisQueuedLocks::TimedLockIntermediateTimeoutError,
434
- ::Timeout::ExitException
435
- # NOTE: (timeouts) an interrupted WATCH block (the internal timeouts and the user's
436
- # `Timeout.timeout` without an exception class): the closed connection drops the WATCH
437
- # state (redis-client sends `UNWATCH` only on `StandardError`);
438
- conn.close
439
- raise
440
- end
441
- end
442
-
443
- # Locks of the series that are obtained by the series itself (reentrant locks are obtained
444
- # by the outer logic of the current acquirer and should be released by it).
445
- #
446
- # @param successfully_acquired_locks [Array<Hash<Symbol,Any>>]
447
- # @return [Array<String>]
448
- #
449
- # @api private
450
- # @since 1.18.0
451
- def obtained_lock_names(successfully_acquired_locks) # steep:ignore
452
- successfully_acquired_locks.filter_map do |operation_result|
453
- operation_result[:lock_name] if operation_result[:result][:process] == :lock_obtaining
454
- end
455
- end
456
-
457
- # Releases read locks of the given acquirer (other readers are not affected).
458
- #
459
- # @param redis [RedisClient]
460
- # @param lock_names [Array<String>]
461
- # @param acquirer_id [String]
462
- # @return [void]
463
- #
464
- # @api private
465
- # @since 1.18.0
466
- def release_read_lock_series(redis, lock_names, acquirer_id) # steep:ignore
467
- return if lock_names.empty?
468
-
469
- redis.with do |conn|
470
- conn.multi do |transaction|
471
- lock_names.each do |lock_name|
472
- transaction.call(
473
- 'ZREM', RedisQueuedLocks::Resource.prepare_lock_readers(lock_name), acquirer_id
474
- )
475
- transaction.call(
476
- 'DEL', RedisQueuedLocks::Resource.prepare_read_lock_key(lock_name, acquirer_id)
477
- )
478
- end
479
- end
480
- end
481
- end
482
- end
483
- end
484
- # rubocop:enable all