redis_queued_locks 1.12.1 → 1.14.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +45 -5
  5. data/LICENSE.txt +1 -1
  6. data/README.md +574 -296
  7. data/Rakefile +12 -4
  8. data/Steepfile +15 -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 -9
  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 +42 -19
  21. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock.rb +74 -47
  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 +26 -21
  32. data/lib/redis_queued_locks/{acquier → acquirer}/release_lock.rb +28 -22
  33. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +211 -0
  34. data/lib/redis_queued_locks/acquirer.rb +19 -0
  35. data/lib/redis_queued_locks/client.rb +317 -254
  36. data/lib/redis_queued_locks/config/dsl.rb +94 -0
  37. data/lib/redis_queued_locks/config.rb +236 -0
  38. data/lib/redis_queued_locks/data.rb +2 -0
  39. data/lib/redis_queued_locks/errors.rb +27 -11
  40. data/lib/redis_queued_locks/instrument.rb +11 -4
  41. data/lib/redis_queued_locks/logging/void_logger.rb +38 -1
  42. data/lib/redis_queued_locks/logging.rb +20 -5
  43. data/lib/redis_queued_locks/resource.rb +49 -11
  44. data/lib/redis_queued_locks/swarm/acquirers.rb +17 -16
  45. data/lib/redis_queued_locks/swarm/flush_zombies.rb +26 -25
  46. data/lib/redis_queued_locks/swarm/probe_hosts.rb +20 -19
  47. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +3 -3
  48. data/lib/redis_queued_locks/swarm/supervisor.rb +19 -6
  49. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +20 -18
  50. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +35 -27
  51. data/lib/redis_queued_locks/swarm/zombie_info.rb +9 -9
  52. data/lib/redis_queued_locks/swarm.rb +20 -41
  53. data/lib/redis_queued_locks/utilities.rb +11 -2
  54. data/lib/redis_queued_locks/version.rb +2 -2
  55. data/lib/redis_queued_locks.rb +2 -3
  56. data/rbs_collection.lock.yaml +28 -0
  57. data/rbs_collection.yaml +17 -0
  58. data/redis_queued_locks.gemspec +22 -23
  59. data/sig/manifest.yml +6 -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 +52 -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/acquirer/release_locks_of.rbs +46 -0
  84. data/sig/redis_queued_locks/client.rbs +235 -0
  85. data/sig/redis_queued_locks/config/dsl.rbs +26 -0
  86. data/sig/redis_queued_locks/config.rbs +23 -0
  87. data/sig/redis_queued_locks/data.rbs +4 -0
  88. data/sig/redis_queued_locks/debugger/interface.rbs +9 -0
  89. data/sig/redis_queued_locks/debugger.rbs +13 -0
  90. data/sig/redis_queued_locks/errors.rbs +43 -0
  91. data/sig/redis_queued_locks/instrument/active_support.rbs +7 -0
  92. data/sig/redis_queued_locks/instrument/sampler.rbs +9 -0
  93. data/sig/redis_queued_locks/instrument/void_notifier.rbs +7 -0
  94. data/sig/redis_queued_locks/instrument.rbs +15 -0
  95. data/sig/redis_queued_locks/logging/sampler.rbs +9 -0
  96. data/sig/redis_queued_locks/logging/void_logger.rbs +15 -0
  97. data/sig/redis_queued_locks/logging.rbs +15 -0
  98. data/sig/redis_queued_locks/resource.rbs +42 -0
  99. data/sig/redis_queued_locks/swarm/acquirers.rbs +10 -0
  100. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +13 -0
  101. data/sig/redis_queued_locks/swarm/probe_hosts.rbs +13 -0
  102. data/sig/redis_queued_locks/swarm/redis_client_builder.rbs +19 -0
  103. data/sig/redis_queued_locks/swarm/supervisor.rbs +26 -0
  104. data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +52 -0
  105. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +61 -0
  106. data/sig/redis_queued_locks/swarm/swarm_element.rbs +8 -0
  107. data/sig/redis_queued_locks/swarm/zombie_info.rbs +24 -0
  108. data/sig/redis_queued_locks/swarm.rbs +41 -0
  109. data/sig/redis_queued_locks/utilities/lock.rbs +10 -0
  110. data/sig/redis_queued_locks/utilities.rbs +12 -0
  111. data/sig/redis_queued_locks/version.rbs +3 -0
  112. data/sig/redis_queued_locks.rbs +14 -0
  113. data/sig/vendor/active_support.rbs +9 -0
  114. data/sig/vendor/redis_client.rbs +39 -0
  115. data/sig/vendor/semantic_logger.rbs +4 -0
  116. metadata +98 -54
  117. data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +0 -40
  118. data/lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb +0 -166
  119. data/lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb +0 -216
  120. data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb +0 -541
  121. data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb +0 -76
  122. 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,7 +144,7 @@ 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}\", " \
147
+ "acq_id: \"#{acquirer_id}\", " \
125
148
  "hst_id: \"#{host_id}\")"
126
149
  )
127
150
  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'
@@ -17,6 +17,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
17
17
  require_relative 'acquire_lock/try_to_lock'
18
18
  require_relative 'acquire_lock/dequeue_from_lock_queue'
19
19
 
20
+ # @since 1.14.0
21
+ extend RedisQueuedLocks::Utilities
20
22
  # @since 1.0.0
21
23
  extend TryToLock
22
24
  # @since 1.0.0
@@ -32,7 +34,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
32
34
  # @param redis [RedisClient]
33
35
  # Redis connection client.
34
36
  # @param lock_name [String]
35
- # Lock name to be acquier.
37
+ # Lock name to be acquirer.
36
38
  # @option process_id [Integer,String]
37
39
  # The process that want to acquire a lock.
38
40
  # @option thread_id [Integer,String]
@@ -45,7 +47,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
45
47
  # Lock's time to live (in milliseconds). Nil means "without timeout".
46
48
  # @option queue_ttl [Integer]
47
49
  # Lifetime of the acuier's lock request. In seconds.
48
- # @option timeout [Integer]
50
+ # @option timeout [Integer,NilClass]
49
51
  # Time period whe should try to acquire the lock (in seconds).
50
52
  # @option timed [Boolean]
51
53
  # Limit the invocation time period of the passed block of code by the lock's TTL.
@@ -72,15 +74,15 @@ module RedisQueuedLocks::Acquier::AcquireLock
72
74
  # @option detailed_acq_timeout_error [Boolean]
73
75
  # - Add additional data to the acquirement timeout error such as the current lock queue state
74
76
  # and the required lock state;
75
- # - See `config[:detailed_acq_timeout_error]` for details;
77
+ # - See `config['detailed_acq_timeout_error']` for details;
76
78
  # @option logger [::Logger,#debug]
77
- # - Logger object used from the configuration layer (see config[:logger]);
79
+ # - Logger object used from the configuration layer (see config['logger']);
78
80
  # - See `RedisQueuedLocks::Logging::VoidLogger` for example;
79
81
  # - Supports `SemanticLogger::Logger` (see "semantic_logger" gem)
80
82
  # @option log_lock_try [Boolean]
81
83
  # - should be logged the each try of lock acquiring (a lot of logs can be generated depending
82
84
  # on your retry configurations);
83
- # - see `config[:log_lock_try]`;
85
+ # - see `config['log_lock_try']`;
84
86
  # @option instrument [NilClass,Any]
85
87
  # - Custom instrumentation data wich will be passed to the instrumenter's payload
86
88
  # with :instrument key;
@@ -88,12 +90,14 @@ module RedisQueuedLocks::Acquier::AcquireLock
88
90
  # - The conflict strategy mode for cases when the process that obtained the lock
89
91
  # want to acquire this lock again;
90
92
  # - By default uses `:wait_for_lock` strategy;
91
- # - pre-confured in `config[:default_conflict_strategy]`;
93
+ # - pre-confured in `config['default_conflict_strategy']`;
92
94
  # - Supports:
93
95
  # - `:work_through`;
94
96
  # - `:extendable_work_through`;
95
97
  # - `:wait_for_lock`;
96
98
  # - `:dead_locking`;
99
+ # @option read_write_mode [Symbol]
100
+ # - ?
97
101
  # @option access_strategy [Symbol]
98
102
  # - The way in which the lock will be obtained;
99
103
  # - By default it uses `:queued` strategy;
@@ -103,19 +107,19 @@ module RedisQueuedLocks::Acquier::AcquireLock
103
107
  # - `:random` (RANDOM): obtain a lock without checking the positions in the queue
104
108
  # (but with checking the limist, retries, timeouts and so on). if lock is
105
109
  # free to obtain - it will be obtained;
106
- # - pre-configured in `config[:default_access_strategy]`;
110
+ # - pre-configured in `config['default_access_strategy']`;
107
111
  # @option log_sampling_enabled [Boolean]
108
112
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
109
113
  # - disabled by default;
110
- # - works in tandem with <config.log_sampling_percent and <log.sampler>;
114
+ # - works in tandem with <config['log_sampling_percent'] and <config['log_sampler']>;
111
115
  # @option log_sampling_percent [Integer]
112
116
  # - 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;
117
+ # - take an effect when <config['log_sampling_enalbed']> is true;
118
+ # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
115
119
  # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
116
120
  # - 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;
121
+ # - works in tandem with <config['log_sampling_enabled']> and
122
+ # <config['log_sampling_percent']> configs;
119
123
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
120
124
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
121
125
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -128,15 +132,16 @@ module RedisQueuedLocks::Acquier::AcquireLock
128
132
  # - enables <instrumentaion sampling>: only the configured percent
129
133
  # of RQL cases will be instrumented;
130
134
  # - disabled by default;
131
- # - works in tandem with <config.instr_sampling_percent and <log.instr_sampler>;
135
+ # - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
132
136
  # @option instr_sampling_percent [Integer]
133
137
  # - 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;
138
+ # - take an effect when <config['instr_sampling_enalbed']> is true;
139
+ # - works in tandem with <config['instr_sampling_enabled']>
140
+ # and <config['instr_sampler']> configs;
136
141
  # @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
137
142
  # - 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;
143
+ # - works in tandem with <config['instr_sampling_enabled']> and
144
+ # <config['instr_sampling_percent']> configs;
140
145
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
141
146
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
142
147
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -148,13 +153,13 @@ module RedisQueuedLocks::Acquier::AcquireLock
148
153
  # - makes sense when instrumentation sampling is enabled;
149
154
  # @param [Block]
150
155
  # A block of code that should be executed after the successfully acquired lock.
151
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
156
+ # @return [Hash<Symbol,Any>,yield]
152
157
  # - Format: { ok: true/false, result: Any }
153
158
  # - If block is given the result of block's yeld will be returned.
154
159
  #
155
160
  # @api private
156
161
  # @since 1.0.0
157
- # @version 1.12.1
162
+ # @version 1.14.0
158
163
  def acquire_lock(
159
164
  redis,
160
165
  lock_name,
@@ -179,6 +184,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
179
184
  logger:,
180
185
  log_lock_try:,
181
186
  conflict_strategy:,
187
+ read_write_mode:,
182
188
  access_strategy:,
183
189
  log_sampling_enabled:,
184
190
  log_sampling_percent:,
@@ -224,7 +230,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
224
230
  end
225
231
 
226
232
  # Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
227
- acquier_id = RedisQueuedLocks::Resource.acquier_identifier(
233
+ acquirer_id = RedisQueuedLocks::Resource.acquirer_identifier(
228
234
  process_id,
229
235
  thread_id,
230
236
  fiber_id,
@@ -240,7 +246,11 @@ module RedisQueuedLocks::Acquier::AcquireLock
240
246
  lock_ttl = ttl
241
247
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
242
248
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
243
- acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
249
+
250
+ 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)
252
+
253
+ acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
244
254
 
245
255
  log_sampled = RedisQueuedLocks::Logging.should_log?(
246
256
  log_sampling_enabled,
@@ -256,6 +266,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
256
266
  )
257
267
 
258
268
  # Step X: intermediate result observer
269
+ # @type var acq_process: Hash[Symbol,untyped]
259
270
  acq_process = {
260
271
  lock_info: {},
261
272
  should_try: true,
@@ -272,9 +283,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
272
283
  redis,
273
284
  logger,
274
285
  lock_key,
286
+ read_write_mode,
275
287
  lock_key_queue,
288
+ read_lock_key_queue,
289
+ write_lock_key_queue,
276
290
  queue_ttl,
277
- acquier_id,
291
+ acquirer_id,
278
292
  host_id,
279
293
  access_strategy,
280
294
  log_sampled,
@@ -284,7 +298,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
284
298
 
285
299
  LogVisitor.start_lock_obtaining(
286
300
  logger, log_sampled, lock_key,
287
- queue_ttl, acquier_id, host_id, access_strategy
301
+ queue_ttl, acquirer_id, host_id, access_strategy
288
302
  )
289
303
 
290
304
  # Step 2: try to lock with timeout
@@ -297,36 +311,41 @@ module RedisQueuedLocks::Acquier::AcquireLock
297
311
  detailed_acq_timeout_error,
298
312
  on_timeout: acq_dequeue
299
313
  ) do
300
- acq_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
314
+ acq_start_time = clock_gettime
301
315
 
302
316
  # Step 2.1: cyclically try to obtain the lock
303
317
  while acq_process[:should_try]
304
318
 
305
319
  LogVisitor.start_try_to_lock_cycle(
306
320
  logger, log_sampled, lock_key,
307
- queue_ttl, acquier_id, host_id, access_strategy
321
+ queue_ttl, acquirer_id, host_id, access_strategy
308
322
  )
309
323
 
310
324
  # 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)
325
+ if RedisQueuedLocks::Resource.dead_score_reached?(acquirer_position, queue_ttl)
312
326
  # 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
327
+ acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
314
328
 
315
- LogVisitor.dead_score_reached__reset_acquier_position(
329
+ LogVisitor.dead_score_reached__reset_acquirer_position(
316
330
  logger, log_sampled, lock_key,
317
- queue_ttl, acquier_id, host_id, access_strategy
331
+ queue_ttl, acquirer_id, host_id, access_strategy
318
332
  )
319
333
  end
320
334
 
335
+ # NOTE: (steep ignorance) pattern matching is not supported in steep
336
+ # steep:ignore:start
321
337
  try_to_lock(
322
338
  redis,
323
339
  logger,
324
340
  log_lock_try,
325
341
  lock_key,
342
+ read_write_mode,
326
343
  lock_key_queue,
327
- acquier_id,
344
+ read_lock_key_queue,
345
+ write_lock_key_queue,
346
+ acquirer_id,
328
347
  host_id,
329
- acquier_position,
348
+ acquirer_position,
330
349
  lock_ttl,
331
350
  queue_ttl,
332
351
  fail_fast,
@@ -336,8 +355,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
336
355
  log_sampled,
337
356
  instr_sampled
338
357
  ) => { ok:, result: }
358
+ # steep:ignore:end
359
+
360
+ # @type var ok: bool
361
+ # @type var result: Symbol|Hash[Symbol,untyped]
339
362
 
340
- acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
363
+ acq_end_time = clock_gettime
341
364
  acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
342
365
 
343
366
  # Step X: save the intermediate results to the result observer
@@ -346,12 +369,14 @@ module RedisQueuedLocks::Acquier::AcquireLock
346
369
 
347
370
  # Step 2.1: analyze an acquirement attempt
348
371
  if ok
372
+ # @type var result: Hash[Symbol,untyped]
373
+
349
374
  # Step X: (instrumentation)
350
375
  if acq_process[:result][:process] == :extendable_conflict_work_through
351
376
  # instrumetnation: (reentrant lock with ttl extension)
352
377
  LogVisitor.extendable_reentrant_lock_obtained(
353
378
  logger, log_sampled, result[:lock_key],
354
- queue_ttl, acquier_id, host_id, acq_time, access_strategy
379
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy
355
380
  )
356
381
  InstrVisitor.extendable_reentrant_lock_obtained(
357
382
  instrumenter, instr_sampled, result[:lock_key],
@@ -362,7 +387,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
362
387
  # instrumetnation: (reentrant lock without ttl extension)
363
388
  LogVisitor.reentrant_lock_obtained(
364
389
  logger, log_sampled, result[:lock_key],
365
- queue_ttl, acquier_id, host_id, acq_time, access_strategy
390
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy
366
391
  )
367
392
  InstrVisitor.reentrant_lock_obtained(
368
393
  instrumenter, instr_sampled, result[:lock_key],
@@ -374,7 +399,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
374
399
  # NOTE: classic is: acq_process[:result][:process] == :lock_obtaining
375
400
  LogVisitor.lock_obtained(
376
401
  logger, log_sampled, result[:lock_key],
377
- queue_ttl, acquier_id, host_id, acq_time, access_strategy
402
+ queue_ttl, acquirer_id, host_id, acq_time, access_strategy
378
403
  )
379
404
  InstrVisitor.lock_obtained(
380
405
  instrumenter, instr_sampled, result[:lock_key],
@@ -397,6 +422,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
397
422
  acq_process[:acq_time] = acq_time
398
423
  acq_process[:acq_end_time] = acq_end_time
399
424
  else
425
+ # @type var result: Symbol
426
+
400
427
  # Step 2.2: failed to acquire. anylize each case and act in accordance
401
428
  if acq_process[:result] == :fail_fast_no_try # Step 2.2.a: fail without try
402
429
  acq_process[:should_try] = false
@@ -415,9 +442,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
415
442
 
416
443
  if raise_errors
417
444
  raise(
418
- RedisQueuedLock::ConflictLockObtainError,
445
+ RedisQueuedLocks::ConflictLockObtainError,
419
446
  "Lock Conflict: trying to acquire the lock \"#{lock_key}\" " \
420
- "that is already acquired by the current acquier (acq_id: \"#{acquired_id}\")."
447
+ "that is already acquired by the current acquirer (acq_id: \"#{acquirer_id}\")."
421
448
  )
422
449
  end
423
450
  else
@@ -443,7 +470,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
443
470
 
444
471
  if raise_errors
445
472
  raise(
446
- RedisQueuedLocks::LockAcquiermentRetryLimitError,
473
+ RedisQueuedLocks::LockAcquirementRetryLimitError,
447
474
  "Failed to acquire the lock \"#{lock_key}\" " \
448
475
  "for the given retry_count limit (#{retry_count} times)."
449
476
  )
@@ -466,7 +493,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
466
493
  # Step 3.a: acquired successfully => run logic or return the result of acquirement
467
494
  if block_given?
468
495
  begin
469
- yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
496
+ yield_time = clock_gettime
470
497
 
471
498
  ttl_shift = (
472
499
  (yield_time - acq_process[:acq_end_time]) / 1_000.0 -
@@ -484,7 +511,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
484
511
  redis,
485
512
  logger,
486
513
  lock_key,
487
- acquier_id,
514
+ acquirer_id,
488
515
  host_id,
489
516
  access_strategy,
490
517
  timed,
@@ -499,9 +526,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
499
526
  &block
500
527
  )
501
528
  ensure
502
- acq_process[:rel_time] = ::Process.clock_gettime(
503
- ::Process::CLOCK_MONOTONIC, :microsecond
504
- )
529
+ acq_process[:rel_time] = clock_gettime
505
530
  acq_process[:hold_time] = (
506
531
  (acq_process[:rel_time] - acq_process[:acq_end_time]) / 1_000.0
507
532
  ).ceil(2)
@@ -538,7 +563,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
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'])