redis_queued_locks 1.12.0 → 1.13.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +42 -5
  5. data/LICENSE.txt +1 -1
  6. data/README.md +231 -203
  7. data/Rakefile +12 -4
  8. data/Steepfile +16 -0
  9. data/github_ci/ruby3.3.gemfile +17 -0
  10. data/github_ci/ruby3.3.gemfile.lock +217 -0
  11. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/delay_execution.rb +4 -4
  12. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +40 -0
  13. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/dequeue_from_lock_queue.rb +17 -8
  14. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +166 -0
  15. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +218 -0
  16. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +543 -0
  17. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/try_to_lock.rb +126 -92
  18. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/with_acq_timeout.rb +14 -13
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +76 -0
  20. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/yield_expire.rb +43 -20
  21. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock.rb +69 -42
  22. data/lib/redis_queued_locks/{acquier → acquirer}/clear_dead_requests.rb +5 -3
  23. data/lib/redis_queued_locks/{acquier → acquirer}/extend_lock_ttl.rb +4 -3
  24. data/lib/redis_queued_locks/{acquier → acquirer}/is_locked.rb +1 -1
  25. data/lib/redis_queued_locks/{acquier → acquirer}/is_queued.rb +1 -1
  26. data/lib/redis_queued_locks/{acquier → acquirer}/keys.rb +5 -5
  27. data/lib/redis_queued_locks/{acquier → acquirer}/lock_info.rb +9 -5
  28. data/lib/redis_queued_locks/{acquier → acquirer}/locks.rb +16 -3
  29. data/lib/redis_queued_locks/{acquier → acquirer}/queue_info.rb +8 -6
  30. data/lib/redis_queued_locks/{acquier → acquirer}/queues.rb +9 -2
  31. data/lib/redis_queued_locks/{acquier → acquirer}/release_all_locks.rb +23 -18
  32. data/lib/redis_queued_locks/{acquier → acquirer}/release_lock.rb +25 -19
  33. data/lib/redis_queued_locks/acquirer.rb +18 -0
  34. data/lib/redis_queued_locks/client.rb +164 -254
  35. data/lib/redis_queued_locks/config/dsl.rb +94 -0
  36. data/lib/redis_queued_locks/config.rb +231 -0
  37. data/lib/redis_queued_locks/data.rb +2 -0
  38. data/lib/redis_queued_locks/errors.rb +27 -11
  39. data/lib/redis_queued_locks/instrument.rb +11 -4
  40. data/lib/redis_queued_locks/logging/void_logger.rb +38 -1
  41. data/lib/redis_queued_locks/logging.rb +20 -5
  42. data/lib/redis_queued_locks/resource.rb +49 -11
  43. data/lib/redis_queued_locks/swarm/acquirers.rb +17 -16
  44. data/lib/redis_queued_locks/swarm/flush_zombies.rb +26 -25
  45. data/lib/redis_queued_locks/swarm/probe_hosts.rb +20 -19
  46. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +3 -3
  47. data/lib/redis_queued_locks/swarm/supervisor.rb +19 -6
  48. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +20 -18
  49. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +35 -27
  50. data/lib/redis_queued_locks/swarm/zombie_info.rb +9 -9
  51. data/lib/redis_queued_locks/swarm.rb +20 -41
  52. data/lib/redis_queued_locks/utilities/lock.rb +4 -2
  53. data/lib/redis_queued_locks/utilities.rb +2 -2
  54. data/lib/redis_queued_locks/version.rb +2 -2
  55. data/lib/redis_queued_locks.rb +2 -2
  56. data/rbs_collection.lock.yaml +40 -0
  57. data/rbs_collection.yaml +16 -0
  58. data/redis_queued_locks.gemspec +22 -23
  59. data/sig/manifest.yml +7 -0
  60. data/sig/redis_queued_locks/acquier.rbs +4 -0
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/delay_execution.rbs +9 -0
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +21 -0
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +26 -0
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +71 -0
  65. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +72 -0
  66. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +179 -0
  67. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +48 -0
  68. data/sig/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rbs +19 -0
  69. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +41 -0
  70. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +32 -0
  71. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +51 -0
  72. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +28 -0
  73. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +28 -0
  74. data/sig/redis_queued_locks/acquirer/is_locked.rbs +9 -0
  75. data/sig/redis_queued_locks/acquirer/is_queued.rbs +9 -0
  76. data/sig/redis_queued_locks/acquirer/keys.rbs +10 -0
  77. data/sig/redis_queued_locks/acquirer/lock_info.rbs +10 -0
  78. data/sig/redis_queued_locks/acquirer/locks.rbs +16 -0
  79. data/sig/redis_queued_locks/acquirer/queue_info.rbs +13 -0
  80. data/sig/redis_queued_locks/acquirer/queues.rbs +16 -0
  81. data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +30 -0
  82. data/sig/redis_queued_locks/acquirer/release_lock.rbs +38 -0
  83. data/sig/redis_queued_locks/client.rbs +195 -0
  84. data/sig/redis_queued_locks/config/dsl.rbs +26 -0
  85. data/sig/redis_queued_locks/config.rbs +23 -0
  86. data/sig/redis_queued_locks/data.rbs +4 -0
  87. data/sig/redis_queued_locks/debugger/interface.rbs +9 -0
  88. data/sig/redis_queued_locks/debugger.rbs +13 -0
  89. data/sig/redis_queued_locks/errors.rbs +43 -0
  90. data/sig/redis_queued_locks/instrument/active_support.rbs +7 -0
  91. data/sig/redis_queued_locks/instrument/sampler.rbs +9 -0
  92. data/sig/redis_queued_locks/instrument/void_notifier.rbs +7 -0
  93. data/sig/redis_queued_locks/instrument.rbs +15 -0
  94. data/sig/redis_queued_locks/logging/sampler.rbs +9 -0
  95. data/sig/redis_queued_locks/logging/void_logger.rbs +15 -0
  96. data/sig/redis_queued_locks/logging.rbs +15 -0
  97. data/sig/redis_queued_locks/resource.rbs +42 -0
  98. data/sig/redis_queued_locks/swarm/acquirers.rbs +10 -0
  99. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +13 -0
  100. data/sig/redis_queued_locks/swarm/probe_hosts.rbs +13 -0
  101. data/sig/redis_queued_locks/swarm/redis_client_builder.rbs +19 -0
  102. data/sig/redis_queued_locks/swarm/supervisor.rbs +26 -0
  103. data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +52 -0
  104. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +61 -0
  105. data/sig/redis_queued_locks/swarm/swarm_element.rbs +8 -0
  106. data/sig/redis_queued_locks/swarm/zombie_info.rbs +24 -0
  107. data/sig/redis_queued_locks/swarm.rbs +41 -0
  108. data/sig/redis_queued_locks/utilities/lock.rbs +10 -0
  109. data/sig/redis_queued_locks/utilities.rbs +11 -0
  110. data/sig/redis_queued_locks/version.rbs +3 -0
  111. data/sig/redis_queued_locks.rbs +14 -0
  112. data/sig/vendor/active_support.rbs +9 -0
  113. data/sig/vendor/redis_client.rbs +39 -0
  114. data/sig/vendor/semantic_logger.rbs +4 -0
  115. metadata +96 -54
  116. data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +0 -40
  117. data/lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb +0 -166
  118. data/lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb +0 -216
  119. data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb +0 -541
  120. data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb +0 -76
  121. data/lib/redis_queued_locks/acquier.rb +0 -18
@@ -3,7 +3,7 @@
3
3
  # @api private
4
4
  # @since 1.3.0
5
5
  # @version 1.7.0
6
- module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
6
+ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
7
7
  require_relative 'yield_expire/log_visitor'
8
8
 
9
9
  # @return [String]
@@ -18,14 +18,13 @@ module RedisQueuedLocks::Acquier::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 acquier_id [String] Acquier identifier.
21
+ # @param acquirer_id [String] Acquirer identifier.
22
22
  # @param host_id [String] Host identifier.
23
23
  # @param access_strategy [Symbol] Lock obtaining strategy.
24
24
  # @param timed [Boolean] Should the lock be wrapped by Timeout with with lock's ttl
25
- # @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
25
+ # @param ttl_shift [Numeric] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
26
26
  # @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
27
27
  # @param queue_ttl [Integer] Lock request lifetime.
28
- # @param block [Block] Custom logic that should be invoked unter the obtained lock.
29
28
  # @param meta [NilClass,Hash<String|Symbol,Any>] Custom metadata wich is passed to the lock data;
30
29
  # @param log_sampled [Boolean] Should the logic be logged or not.
31
30
  # @param instr_sampled [Boolean] Should the logic be instrumented or not.
@@ -33,7 +32,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
33
32
  # @param should_decrease [Boolean]
34
33
  # - Should decrease the lock TTL after the lock invocation;
35
34
  # - It is suitable for extendable reentrant locks;
36
- # @return [Any,NilClass] nil is returned no block parametr is provided.
35
+ # @param block [Block] Custom logic that should be invoked under the obtained lock.
36
+ # @return [Any,NilClass] nil is returned when no block parametr is provided.
37
37
  #
38
38
  # @api private
39
39
  # @since 1.3.0
@@ -43,7 +43,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
43
43
  redis,
44
44
  logger,
45
45
  lock_key,
46
- acquier_id,
46
+ acquirer_id,
47
47
  host_id,
48
48
  access_strategy,
49
49
  timed,
@@ -60,33 +60,56 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
60
60
  initial_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
61
61
 
62
62
  if block_given?
63
- timeout = ((ttl - ttl_shift) / 1_000.0).yield_self do |time|
64
- # NOTE: time in <seconds> cuz Ruby's Timeout requires <seconds>
65
- (time < 0) ? 0.0 : time
66
- end
67
-
68
63
  if timed && ttl != nil
69
- yield_with_timeout(timeout, lock_key, ttl, acquier_id, host_id, meta, &block)
64
+ # NOTE:
65
+ # - steep is ignored cuz steep can not recognize `::Integer - ::Integer|::Float`
66
+ # operation here for some mystical reason (it tryes to find overloadd `-` method for
67
+ # integer and fails on it);
68
+ # - so we need to ignore steep here at all and manually set the type of each
69
+ # variable for the correct following variable type recognitions;
70
+
71
+ # steep:ignore:start
72
+ # @type var ttl: Integer
73
+ # @type var ttl_shift: Float|Integer
74
+ # @type var timeout: Float
75
+ timeout = ((ttl - ttl_shift) / 1_000.0).yield_self do |time|
76
+ # @type var time: Float
77
+ # NOTE: time in <seconds> cuz Ruby's Timeout requires <seconds>
78
+ (time < 0) ? 0.0 : time
79
+ end
80
+ # steep:ignore:end
81
+
82
+ yield_with_timeout(
83
+ timeout,
84
+ lock_key,
85
+ ttl,
86
+ acquirer_id,
87
+ host_id,
88
+ meta,
89
+ &block # steep:ignore
90
+ )
70
91
  else
71
92
  yield
72
93
  end
73
94
  end
74
95
  ensure
75
- if should_expire
96
+ if should_expire # TODO: comment all cases/examples when should_expire is true
76
97
  LogVisitor.expire_lock(
77
98
  logger, log_sampled, lock_key,
78
- queue_ttl, acquier_id, host_id, access_strategy
99
+ queue_ttl, acquirer_id, host_id, access_strategy
79
100
  )
80
101
  redis.call('EXPIRE', lock_key, '0')
81
- elsif should_decrease
102
+ elsif should_decrease # TODO: comment all cases/examples when should_expire is true
82
103
  finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
104
+ # @type var initial_time: Integer
83
105
  spent_time = (finish_time - initial_time)
106
+ # @type var ttl: Integer
84
107
  decreased_ttl = ttl - spent_time - RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
85
108
 
86
109
  if decreased_ttl > 0
87
110
  LogVisitor.decrease_lock(
88
111
  logger, log_sampled, lock_key,
89
- decreased_ttl, queue_ttl, acquier_id, host_id, access_strategy
112
+ decreased_ttl, queue_ttl, acquirer_id, host_id, access_strategy
90
113
  )
91
114
  # NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
92
115
  redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
@@ -101,7 +124,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
101
124
  # @param timeout [Float]
102
125
  # @parma lock_key [String]
103
126
  # @param lock_ttl [Integer,NilClass]
104
- # @param acquier_id [String]
127
+ # @param acquirer_id [String]
105
128
  # @param host_id [String]
106
129
  # @param meta [NilClass,Hash<Symbol|String,Any>]
107
130
  # @param block [Blcok]
@@ -112,7 +135,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
112
135
  # @api private
113
136
  # @since 1.3.0
114
137
  # @version 1.12.0
115
- def yield_with_timeout(timeout, lock_key, lock_ttl, acquier_id, host_id, meta, &block)
138
+ def yield_with_timeout(timeout, lock_key, lock_ttl, acquirer_id, host_id, meta, &block)
116
139
  ::Timeout.timeout(timeout, RedisQueuedLocks::TimedLockIntermediateTimeoutError, &block)
117
140
  rescue RedisQueuedLocks::TimedLockIntermediateTimeoutError
118
141
  raise(
@@ -121,8 +144,8 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
121
144
  "(lock: \"#{lock_key}\", " \
122
145
  "ttl: #{lock_ttl}, " \
123
146
  "meta: #{meta ? meta.inspect : '<no-meta>'}, " \
124
- "acq_id: \"#{acquier_id}\", " \
125
- "hst_id: \"#{host_id}\")",
147
+ "acq_id: \"#{acquirer_id}\", " \
148
+ "hst_id: \"#{host_id}\")"
126
149
  )
127
150
  end
128
151
  end
@@ -8,7 +8,7 @@
8
8
  # rubocop:disable Metrics/ClassLength
9
9
  # rubocop:disable Metrics/BlockNesting
10
10
  # rubocop:disable Style/IfInsideElse
11
- module RedisQueuedLocks::Acquier::AcquireLock
11
+ module RedisQueuedLocks::Acquirer::AcquireLock
12
12
  require_relative 'acquire_lock/log_visitor'
13
13
  require_relative 'acquire_lock/instr_visitor'
14
14
  require_relative 'acquire_lock/delay_execution'
@@ -32,7 +32,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
32
32
  # @param redis [RedisClient]
33
33
  # Redis connection client.
34
34
  # @param lock_name [String]
35
- # Lock name to be acquier.
35
+ # Lock name to be acquirer.
36
36
  # @option process_id [Integer,String]
37
37
  # The process that want to acquire a lock.
38
38
  # @option thread_id [Integer,String]
@@ -45,7 +45,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
45
45
  # Lock's time to live (in milliseconds). Nil means "without timeout".
46
46
  # @option queue_ttl [Integer]
47
47
  # Lifetime of the acuier's lock request. In seconds.
48
- # @option timeout [Integer]
48
+ # @option timeout [Integer,NilClass]
49
49
  # Time period whe should try to acquire the lock (in seconds).
50
50
  # @option timed [Boolean]
51
51
  # Limit the invocation time period of the passed block of code by the lock's TTL.
@@ -72,15 +72,15 @@ module RedisQueuedLocks::Acquier::AcquireLock
72
72
  # @option detailed_acq_timeout_error [Boolean]
73
73
  # - Add additional data to the acquirement timeout error such as the current lock queue state
74
74
  # and the required lock state;
75
- # - See `config[:detailed_acq_timeout_error]` for details;
75
+ # - See `config['detailed_acq_timeout_error']` for details;
76
76
  # @option logger [::Logger,#debug]
77
- # - Logger object used from the configuration layer (see config[:logger]);
77
+ # - Logger object used from the configuration layer (see config['logger']);
78
78
  # - See `RedisQueuedLocks::Logging::VoidLogger` for example;
79
79
  # - Supports `SemanticLogger::Logger` (see "semantic_logger" gem)
80
80
  # @option log_lock_try [Boolean]
81
81
  # - should be logged the each try of lock acquiring (a lot of logs can be generated depending
82
82
  # on your retry configurations);
83
- # - see `config[:log_lock_try]`;
83
+ # - see `config['log_lock_try']`;
84
84
  # @option instrument [NilClass,Any]
85
85
  # - Custom instrumentation data wich will be passed to the instrumenter's payload
86
86
  # with :instrument key;
@@ -88,12 +88,14 @@ module RedisQueuedLocks::Acquier::AcquireLock
88
88
  # - The conflict strategy mode for cases when the process that obtained the lock
89
89
  # want to acquire this lock again;
90
90
  # - By default uses `:wait_for_lock` strategy;
91
- # - pre-confured in `config[:default_conflict_strategy]`;
91
+ # - pre-confured in `config['default_conflict_strategy']`;
92
92
  # - Supports:
93
93
  # - `:work_through`;
94
94
  # - `:extendable_work_through`;
95
95
  # - `:wait_for_lock`;
96
96
  # - `:dead_locking`;
97
+ # @option read_write_mode [Symbol]
98
+ # - ?
97
99
  # @option access_strategy [Symbol]
98
100
  # - The way in which the lock will be obtained;
99
101
  # - By default it uses `:queued` strategy;
@@ -103,19 +105,19 @@ module RedisQueuedLocks::Acquier::AcquireLock
103
105
  # - `:random` (RANDOM): obtain a lock without checking the positions in the queue
104
106
  # (but with checking the limist, retries, timeouts and so on). if lock is
105
107
  # free to obtain - it will be obtained;
106
- # - pre-configured in `config[:default_access_strategy]`;
108
+ # - pre-configured in `config['default_access_strategy']`;
107
109
  # @option log_sampling_enabled [Boolean]
108
110
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
109
111
  # - disabled by default;
110
- # - works in tandem with <config.log_sampling_percent and <log.sampler>;
112
+ # - works in tandem with <config['log_sampling_percent'] and <config['log_sampler']>;
111
113
  # @option log_sampling_percent [Integer]
112
114
  # - the percent of cases that should be logged;
113
- # - take an effect when <config.log_sampling_enalbed> is true;
114
- # - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
115
+ # - take an effect when <config['log_sampling_enalbed']> is true;
116
+ # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
115
117
  # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
116
118
  # - percent-based log sampler that decides should be RQL case logged or not;
117
- # - works in tandem with <config.log_sampling_enabled> and
118
- # <config.log_sampling_percent> configs;
119
+ # - works in tandem with <config['log_sampling_enabled']> and
120
+ # <config['log_sampling_percent']> configs;
119
121
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
120
122
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
121
123
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -128,15 +130,16 @@ module RedisQueuedLocks::Acquier::AcquireLock
128
130
  # - enables <instrumentaion sampling>: only the configured percent
129
131
  # of RQL cases will be instrumented;
130
132
  # - disabled by default;
131
- # - works in tandem with <config.instr_sampling_percent and <log.instr_sampler>;
133
+ # - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
132
134
  # @option instr_sampling_percent [Integer]
133
135
  # - the percent of cases that should be instrumented;
134
- # - take an effect when <config.instr_sampling_enalbed> is true;
135
- # - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampler> configs;
136
+ # - take an effect when <config['instr_sampling_enalbed']> is true;
137
+ # - works in tandem with <config['instr_sampling_enabled']>
138
+ # and <config['instr_sampler']> configs;
136
139
  # @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
137
140
  # - percent-based log sampler that decides should be RQL case instrumented or not;
138
- # - works in tandem with <config.instr_sampling_enabled> and
139
- # <config.instr_sampling_percent> configs;
141
+ # - works in tandem with <config['instr_sampling_enabled']> and
142
+ # <config['instr_sampling_percent']> configs;
140
143
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
141
144
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
142
145
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -148,13 +151,13 @@ module RedisQueuedLocks::Acquier::AcquireLock
148
151
  # - makes sense when instrumentation sampling is enabled;
149
152
  # @param [Block]
150
153
  # A block of code that should be executed after the successfully acquired lock.
151
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
154
+ # @return [Hash<Symbol,Any>,yield]
152
155
  # - Format: { ok: true/false, result: Any }
153
156
  # - If block is given the result of block's yeld will be returned.
154
157
  #
155
158
  # @api private
156
159
  # @since 1.0.0
157
- # @version 1.9.0
160
+ # @version 1.13.0
158
161
  def acquire_lock(
159
162
  redis,
160
163
  lock_name,
@@ -179,6 +182,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
179
182
  logger:,
180
183
  log_lock_try:,
181
184
  conflict_strategy:,
185
+ read_write_mode:,
182
186
  access_strategy:,
183
187
  log_sampling_enabled:,
184
188
  log_sampling_percent:,
@@ -224,7 +228,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
224
228
  end
225
229
 
226
230
  # Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
227
- acquier_id = RedisQueuedLocks::Resource.acquier_identifier(
231
+ acquirer_id = RedisQueuedLocks::Resource.acquirer_identifier(
228
232
  process_id,
229
233
  thread_id,
230
234
  fiber_id,
@@ -240,7 +244,11 @@ module RedisQueuedLocks::Acquier::AcquireLock
240
244
  lock_ttl = ttl
241
245
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
242
246
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
243
- acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
247
+
248
+ read_lock_key_queue = RedisQueuedLocks::Resource.prepare_read_lock_queue(lock_name)
249
+ write_lock_key_queue = RedisQueuedLocks::Resource.prepare_write_lock_queue(lock_name)
250
+
251
+ acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
244
252
 
245
253
  log_sampled = RedisQueuedLocks::Logging.should_log?(
246
254
  log_sampling_enabled,
@@ -256,6 +264,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
256
264
  )
257
265
 
258
266
  # Step X: intermediate result observer
267
+ # @type var acq_process: Hash[Symbol,untyped]
259
268
  acq_process = {
260
269
  lock_info: {},
261
270
  should_try: true,
@@ -272,9 +281,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
272
281
  redis,
273
282
  logger,
274
283
  lock_key,
284
+ read_write_mode,
275
285
  lock_key_queue,
286
+ read_lock_key_queue,
287
+ write_lock_key_queue,
276
288
  queue_ttl,
277
- acquier_id,
289
+ acquirer_id,
278
290
  host_id,
279
291
  access_strategy,
280
292
  log_sampled,
@@ -284,7 +296,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
284
296
 
285
297
  LogVisitor.start_lock_obtaining(
286
298
  logger, log_sampled, lock_key,
287
- queue_ttl, acquier_id, host_id, access_strategy
299
+ queue_ttl, acquirer_id, host_id, access_strategy
288
300
  )
289
301
 
290
302
  # Step 2: try to lock with timeout
@@ -304,29 +316,34 @@ module RedisQueuedLocks::Acquier::AcquireLock
304
316
 
305
317
  LogVisitor.start_try_to_lock_cycle(
306
318
  logger, log_sampled, lock_key,
307
- queue_ttl, acquier_id, host_id, access_strategy
319
+ queue_ttl, acquirer_id, host_id, access_strategy
308
320
  )
309
321
 
310
322
  # Step 2.X: check the actual score: is it in queue ttl limit or not?
311
- if RedisQueuedLocks::Resource.dead_score_reached?(acquier_position, queue_ttl)
323
+ if RedisQueuedLocks::Resource.dead_score_reached?(acquirer_position, queue_ttl)
312
324
  # Step 2.X.X: dead score reached => re-queue the lock request with the new score;
313
- acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
325
+ acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
314
326
 
315
- LogVisitor.dead_score_reached__reset_acquier_position(
327
+ LogVisitor.dead_score_reached__reset_acquirer_position(
316
328
  logger, log_sampled, lock_key,
317
- queue_ttl, acquier_id, host_id, access_strategy
329
+ queue_ttl, acquirer_id, host_id, access_strategy
318
330
  )
319
331
  end
320
332
 
333
+ # NOTE: (steep ignorance) pattern matching is not supported in steep
334
+ # steep:ignore:start
321
335
  try_to_lock(
322
336
  redis,
323
337
  logger,
324
338
  log_lock_try,
325
339
  lock_key,
340
+ read_write_mode,
326
341
  lock_key_queue,
327
- acquier_id,
342
+ read_lock_key_queue,
343
+ write_lock_key_queue,
344
+ acquirer_id,
328
345
  host_id,
329
- acquier_position,
346
+ acquirer_position,
330
347
  lock_ttl,
331
348
  queue_ttl,
332
349
  fail_fast,
@@ -336,6 +353,10 @@ module RedisQueuedLocks::Acquier::AcquireLock
336
353
  log_sampled,
337
354
  instr_sampled
338
355
  ) => { ok:, result: }
356
+ # steep:ignore:end
357
+
358
+ # @type var ok: bool
359
+ # @type var result: Symbol|Hash[Symbol,untyped]
339
360
 
340
361
  acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
341
362
  acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
@@ -346,12 +367,14 @@ module RedisQueuedLocks::Acquier::AcquireLock
346
367
 
347
368
  # Step 2.1: analyze an acquirement attempt
348
369
  if ok
370
+ # @type var result: Hash[Symbol,untyped]
371
+
349
372
  # Step X: (instrumentation)
350
373
  if acq_process[:result][:process] == :extendable_conflict_work_through
351
374
  # instrumetnation: (reentrant lock with ttl extension)
352
375
  LogVisitor.extendable_reentrant_lock_obtained(
353
376
  logger, log_sampled, result[:lock_key],
354
- queue_ttl, acquier_id, host_id, acq_time, access_strategy
377
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy
355
378
  )
356
379
  InstrVisitor.extendable_reentrant_lock_obtained(
357
380
  instrumenter, instr_sampled, result[:lock_key],
@@ -362,7 +385,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
362
385
  # instrumetnation: (reentrant lock without ttl extension)
363
386
  LogVisitor.reentrant_lock_obtained(
364
387
  logger, log_sampled, result[:lock_key],
365
- queue_ttl, acquier_id, host_id, acq_time, access_strategy
388
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy
366
389
  )
367
390
  InstrVisitor.reentrant_lock_obtained(
368
391
  instrumenter, instr_sampled, result[:lock_key],
@@ -374,7 +397,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
374
397
  # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
375
398
  LogVisitor.lock_obtained(
376
399
  logger, log_sampled, result[:lock_key],
377
- queue_ttl, acquier_id, host_id, acq_time, access_strategy
400
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy
378
401
  )
379
402
  InstrVisitor.lock_obtained(
380
403
  instrumenter, instr_sampled, result[:lock_key],
@@ -397,6 +420,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
397
420
  acq_process[:acq_time] = acq_time
398
421
  acq_process[:acq_end_time] = acq_end_time
399
422
  else
423
+ # @type var result: Symbol
424
+
400
425
  # Step 2.2: failed to acquire. anylize each case and act in accordance
401
426
  if acq_process[:result] == :fail_fast_no_try # Step 2.2.a: fail without try
402
427
  acq_process[:should_try] = false
@@ -415,9 +440,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
415
440
 
416
441
  if raise_errors
417
442
  raise(
418
- RedisQueuedLock::ConflictLockObtainError,
443
+ RedisQueuedLocks::ConflictLockObtainError,
419
444
  "Lock Conflict: trying to acquire the lock \"#{lock_key}\" " \
420
- "that is already acquired by the current acquier (acq_id: \"#{acquired_id}\")."
445
+ "that is already acquired by the current acquirer (acq_id: \"#{acquirer_id}\")."
421
446
  )
422
447
  end
423
448
  else
@@ -443,7 +468,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
443
468
 
444
469
  if raise_errors
445
470
  raise(
446
- RedisQueuedLocks::LockAcquiermentRetryLimitError,
471
+ RedisQueuedLocks::LockAcquirementRetryLimitError,
447
472
  "Failed to acquire the lock \"#{lock_key}\" " \
448
473
  "for the given retry_count limit (#{retry_count} times)."
449
474
  )
@@ -484,7 +509,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
484
509
  redis,
485
510
  logger,
486
511
  lock_key,
487
- acquier_id,
512
+ acquirer_id,
488
513
  host_id,
489
514
  access_strategy,
490
515
  timed,
@@ -531,14 +556,16 @@ module RedisQueuedLocks::Acquier::AcquireLock
531
556
  acq_process[:lock_info][:acq_id],
532
557
  acq_process[:lock_info][:hst_id],
533
558
  acq_process[:lock_info][:ts],
534
- acq_process[:lock_info][:lock_key],
535
559
  acq_process[:acq_time],
560
+ acq_process[:hold_time],
536
561
  instrument
537
562
  )
538
563
  end
539
564
  end
540
565
  else
541
- RedisQueuedLocks::Data[ok: true, result: acq_process[:lock_info]]
566
+ # rubocop:disable Layout/LineLength
567
+ { ok: true, result: acq_process[:lock_info] } #: { ok: bool, result: Hash[Symbol,untyped] }
568
+ # rubocop:enable Layout/LineLength
542
569
  end
543
570
  else
544
571
  if acq_process[:result] != :retry_limit_reached &&
@@ -551,8 +578,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
551
578
  # - **(notice: in other cases the lock obtaining time and tries count are infinite)
552
579
  acq_process[:result] = :timeout_reached
553
580
  end
554
- # Step 3.b: lock is not acquired (acquier is dequeued by timeout callback)
555
- RedisQueuedLocks::Data[ok: false, result: acq_process[:result]]
581
+ # Step 3.b: lock is not acquired (acquirer is dequeued by timeout callback)
582
+ { ok: false, result: acq_process[:result] } #: { ok: bool, result: Symbol }
556
583
  end
557
584
  end
558
585
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::ClearDeadRequests
5
+ module RedisQueuedLocks::Acquirer::ClearDeadRequests
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @param scan_size [Integer]
@@ -39,9 +39,11 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
39
39
  instr_sampler,
40
40
  instr_sample_this
41
41
  )
42
- dead_score = RedisQueuedLocks::Resource.acquier_dead_score(dead_ttl / 1_000.0)
42
+ dead_score = RedisQueuedLocks::Resource.acquirer_dead_score(dead_ttl / 1_000.0)
43
43
 
44
+ # @type var result: Set[String]
44
45
  result = Set.new.tap do |processed_queues|
46
+ # @type var processed_queues: Set[String]
45
47
  redis_client.with do |rconn|
46
48
  each_lock_queue(rconn, scan_size) do |lock_queue|
47
49
  rconn.call('ZREMRANGEBYSCORE', lock_queue, '-inf', dead_score)
@@ -50,7 +52,7 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
50
52
  end
51
53
  end
52
54
 
53
- RedisQueuedLocks::Data[ok: true, result: { processed_queues: result }]
55
+ { ok: true, result: { processed_queues: result } }
54
56
  end
55
57
 
56
58
  private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::ExtendLockTTL
5
+ module RedisQueuedLocks::Acquirer::ExtendLockTTL
6
6
  # @return [String]
7
7
  #
8
8
  # @api private
@@ -54,10 +54,11 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
54
54
  result = redis_client.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, milliseconds)
55
55
  # TODO: upload scripts to the redis
56
56
 
57
+ # @type var result: Integer
57
58
  if result == 1
58
- RedisQueuedLocks::Data[ok: true, result: :ttl_extended]
59
+ { ok: true, result: :ttl_extended }
59
60
  else
60
- RedisQueuedLocks::Data[ok: false, result: :async_expire_or_no_lock]
61
+ { ok: false, result: :async_expire_or_no_lock }
61
62
  end
62
63
  end
63
64
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::IsLocked
5
+ module RedisQueuedLocks::Acquirer::IsLocked
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @param lock_name [String]
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::IsQueued
5
+ module RedisQueuedLocks::Acquirer::IsQueued
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @param lock_name [String]
@@ -2,23 +2,23 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::Keys
5
+ module RedisQueuedLocks::Acquirer::Keys
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @option scan_size [Integer]
9
- # @return [Array<String>]
9
+ # @return [Set<String>]
10
10
  #
11
11
  # @api private
12
12
  # @since 1.0.0
13
13
  def keys(redis_client, scan_size:)
14
- Set.new.tap do |keys|
14
+ Set.new.tap do |keyset|
15
+ # @type var keyset: Set[String]
15
16
  redis_client.scan(
16
17
  'MATCH',
17
18
  RedisQueuedLocks::Resource::KEY_PATTERN,
18
19
  count: scan_size
19
20
  ) do |key|
20
- # TODO: reduce unnecessary iterations
21
- keys.add(key)
21
+ keyset.add(key)
22
22
  end
23
23
  end
24
24
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::LockInfo
5
+ module RedisQueuedLocks::Acquirer::LockInfo
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @param lock_name [String]
@@ -10,7 +10,7 @@ module RedisQueuedLocks::Acquier::LockInfo
10
10
  # - `nil` is returned when lock key does not exist or expired;
11
11
  # - result format: {
12
12
  # 'lock_key' => "rql:lock:your_lockname", # acquired lock key
13
- # 'acq_id' => "rql:acq:123/456/789/987/uniqstring", # lock acquier identifier
13
+ # 'acq_id' => "rql:acq:123/456/789/987/uniqstring", # lock acquirer identifier
14
14
  # 'hst_id' => "rql:hst:123/456/987/uniqstring", # lock host identifier
15
15
  # 'ts' => 123456789.2649841, # <locked at> time stamp (epoch, seconds.microseconds)
16
16
  # 'ini_ttl' => 123456789, # initial lock key ttl (milliseconds)
@@ -43,15 +43,19 @@ module RedisQueuedLocks::Acquier::LockInfo
43
43
  # - lock is expired + re-obtained;
44
44
  nil
45
45
  else
46
+ # NOTE: the result of MULTI-command is an array of results of each internal command
47
+ # - result[0] (HGETALL) (Hash<String,String>)
48
+ # => (will be mutated further with different value types)
49
+ # - result[1] (PTTL) (Integer)
50
+ # => (without any mutation, integer is atomic)
51
+
52
+ # @type var result: [Hash[String,String|Float|Integer],Integer]
46
53
  hget_cmd_res = result[0]
47
54
  pttl_cmd_res = result[1]
48
55
 
49
56
  if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
50
57
  nil
51
58
  else
52
- # NOTE: the result of MULTI-command is an array of results of each internal command
53
- # - result[0] (HGETALL) (Hash<String,String>)
54
- # - result[1] (PTTL) (Integer)
55
59
  hget_cmd_res.tap do |lock_data|
56
60
  lock_data['lock_key'] = lock_key
57
61
  lock_data['ts'] = Float(lock_data['ts'])
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::Locks
5
+ module RedisQueuedLocks::Acquirer::Locks
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @option scan_size [Integer]
@@ -50,23 +50,36 @@ module RedisQueuedLocks::Acquier::Locks
50
50
  def extract_locks_info(redis_client, lock_keys)
51
51
  # TODO: refactor with RedisQueuedLocks::Acquier::LockInfo
52
52
  Set.new.tap do |seeded_locks|
53
+ # rubocop:disable Layout/LineLength
54
+ # @type var seeded_locks: Set[{ lock: String, status: :released|:alive, info: Hash[String,untyped] }]
55
+ # rubocop:enable Layout/LineLength
56
+
53
57
  # Step X: iterate each lock and extract their info
54
58
  lock_keys.each do |lock_key|
55
59
  # Step 1: extract lock info from redis
60
+
61
+ # @type var lock_info: Hash[String,String|Float|Integer]
56
62
  lock_info = redis_client.pipelined do |pipeline|
57
63
  pipeline.call('HGETALL', lock_key)
58
64
  pipeline.call('PTTL', lock_key)
59
65
  end.yield_self do |result| # Step 2: format the result
60
66
  # Step 2.X: lock is released
61
67
  if result == nil
62
- {}
68
+ {} #: Hash[String,String|Float|Integer]
63
69
  else
70
+ # NOTE: the result of MULTI-command is an array of results of each internal command
71
+ # - result[0] (HGETALL) (Hash<String,String>)
72
+ # => (will be mutated further with different value types)
73
+ # - result[1] (PTTL) (Integer)
74
+ # => (without any mutation, integer is atomic)
75
+
76
+ # @type var result: [Hash[String,String|Float|Integer],Integer]
64
77
  hget_cmd_res = result[0] # NOTE: HGETALL result (hash)
65
78
  pttl_cmd_res = result[1] # NOTE: PTTL result (integer)
66
79
 
67
80
  # Step 2.Y: lock is released
68
81
  if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
69
- {}
82
+ {} #: Hash[String,String|Float|Integer]
70
83
  else
71
84
  # Step 2.Z: lock is alive => format received info + add additional rem_ttl info
72
85
  hget_cmd_res.tap do |lock_data|