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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +42 -5
- data/LICENSE.txt +1 -1
- data/README.md +231 -203
- data/Rakefile +12 -4
- data/Steepfile +16 -0
- data/github_ci/ruby3.3.gemfile +17 -0
- data/github_ci/ruby3.3.gemfile.lock +217 -0
- data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/delay_execution.rb +4 -4
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +40 -0
- data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/dequeue_from_lock_queue.rb +17 -8
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +166 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +218 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +543 -0
- data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/try_to_lock.rb +126 -92
- data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/with_acq_timeout.rb +14 -13
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +76 -0
- data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/yield_expire.rb +43 -20
- data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock.rb +69 -42
- data/lib/redis_queued_locks/{acquier → acquirer}/clear_dead_requests.rb +5 -3
- data/lib/redis_queued_locks/{acquier → acquirer}/extend_lock_ttl.rb +4 -3
- data/lib/redis_queued_locks/{acquier → acquirer}/is_locked.rb +1 -1
- data/lib/redis_queued_locks/{acquier → acquirer}/is_queued.rb +1 -1
- data/lib/redis_queued_locks/{acquier → acquirer}/keys.rb +5 -5
- data/lib/redis_queued_locks/{acquier → acquirer}/lock_info.rb +9 -5
- data/lib/redis_queued_locks/{acquier → acquirer}/locks.rb +16 -3
- data/lib/redis_queued_locks/{acquier → acquirer}/queue_info.rb +8 -6
- data/lib/redis_queued_locks/{acquier → acquirer}/queues.rb +9 -2
- data/lib/redis_queued_locks/{acquier → acquirer}/release_all_locks.rb +23 -18
- data/lib/redis_queued_locks/{acquier → acquirer}/release_lock.rb +25 -19
- data/lib/redis_queued_locks/acquirer.rb +18 -0
- data/lib/redis_queued_locks/client.rb +164 -254
- data/lib/redis_queued_locks/config/dsl.rb +94 -0
- data/lib/redis_queued_locks/config.rb +231 -0
- data/lib/redis_queued_locks/data.rb +2 -0
- data/lib/redis_queued_locks/errors.rb +27 -11
- data/lib/redis_queued_locks/instrument.rb +11 -4
- data/lib/redis_queued_locks/logging/void_logger.rb +38 -1
- data/lib/redis_queued_locks/logging.rb +20 -5
- data/lib/redis_queued_locks/resource.rb +49 -11
- data/lib/redis_queued_locks/swarm/acquirers.rb +17 -16
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +26 -25
- data/lib/redis_queued_locks/swarm/probe_hosts.rb +20 -19
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +3 -3
- data/lib/redis_queued_locks/swarm/supervisor.rb +19 -6
- data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +20 -18
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +35 -27
- data/lib/redis_queued_locks/swarm/zombie_info.rb +9 -9
- data/lib/redis_queued_locks/swarm.rb +20 -41
- data/lib/redis_queued_locks/utilities/lock.rb +4 -2
- data/lib/redis_queued_locks/utilities.rb +2 -2
- data/lib/redis_queued_locks/version.rb +2 -2
- data/lib/redis_queued_locks.rb +2 -2
- data/rbs_collection.lock.yaml +40 -0
- data/rbs_collection.yaml +16 -0
- data/redis_queued_locks.gemspec +22 -23
- data/sig/manifest.yml +7 -0
- data/sig/redis_queued_locks/acquier.rbs +4 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/delay_execution.rbs +9 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +21 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +26 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +71 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +72 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +179 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +48 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rbs +19 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +41 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +32 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +51 -0
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +28 -0
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +28 -0
- data/sig/redis_queued_locks/acquirer/is_locked.rbs +9 -0
- data/sig/redis_queued_locks/acquirer/is_queued.rbs +9 -0
- data/sig/redis_queued_locks/acquirer/keys.rbs +10 -0
- data/sig/redis_queued_locks/acquirer/lock_info.rbs +10 -0
- data/sig/redis_queued_locks/acquirer/locks.rbs +16 -0
- data/sig/redis_queued_locks/acquirer/queue_info.rbs +13 -0
- data/sig/redis_queued_locks/acquirer/queues.rbs +16 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +30 -0
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +38 -0
- data/sig/redis_queued_locks/client.rbs +195 -0
- data/sig/redis_queued_locks/config/dsl.rbs +26 -0
- data/sig/redis_queued_locks/config.rbs +23 -0
- data/sig/redis_queued_locks/data.rbs +4 -0
- data/sig/redis_queued_locks/debugger/interface.rbs +9 -0
- data/sig/redis_queued_locks/debugger.rbs +13 -0
- data/sig/redis_queued_locks/errors.rbs +43 -0
- data/sig/redis_queued_locks/instrument/active_support.rbs +7 -0
- data/sig/redis_queued_locks/instrument/sampler.rbs +9 -0
- data/sig/redis_queued_locks/instrument/void_notifier.rbs +7 -0
- data/sig/redis_queued_locks/instrument.rbs +15 -0
- data/sig/redis_queued_locks/logging/sampler.rbs +9 -0
- data/sig/redis_queued_locks/logging/void_logger.rbs +15 -0
- data/sig/redis_queued_locks/logging.rbs +15 -0
- data/sig/redis_queued_locks/resource.rbs +42 -0
- data/sig/redis_queued_locks/swarm/acquirers.rbs +10 -0
- data/sig/redis_queued_locks/swarm/flush_zombies.rbs +13 -0
- data/sig/redis_queued_locks/swarm/probe_hosts.rbs +13 -0
- data/sig/redis_queued_locks/swarm/redis_client_builder.rbs +19 -0
- data/sig/redis_queued_locks/swarm/supervisor.rbs +26 -0
- data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +52 -0
- data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +61 -0
- data/sig/redis_queued_locks/swarm/swarm_element.rbs +8 -0
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +24 -0
- data/sig/redis_queued_locks/swarm.rbs +41 -0
- data/sig/redis_queued_locks/utilities/lock.rbs +10 -0
- data/sig/redis_queued_locks/utilities.rbs +11 -0
- data/sig/redis_queued_locks/version.rbs +3 -0
- data/sig/redis_queued_locks.rbs +14 -0
- data/sig/vendor/active_support.rbs +9 -0
- data/sig/vendor/redis_client.rbs +39 -0
- data/sig/vendor/semantic_logger.rbs +4 -0
- metadata +96 -54
- data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +0 -40
- data/lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb +0 -166
- data/lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb +0 -216
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb +0 -541
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb +0 -76
- 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::
|
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
|
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 [
|
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
|
-
# @
|
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
|
-
|
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
|
-
|
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,
|
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,
|
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
|
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,
|
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: \"#{
|
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::
|
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
|
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[
|
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[
|
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[
|
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[
|
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[
|
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
|
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
|
114
|
-
# - works in tandem with <config
|
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
|
118
|
-
# <config
|
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
|
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
|
135
|
-
# - works in tandem with <config
|
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
|
139
|
-
# <config
|
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 [
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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,
|
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?(
|
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
|
-
|
325
|
+
acquirer_position = RedisQueuedLocks::Resource.calc_initial_acquirer_position
|
314
326
|
|
315
|
-
LogVisitor.
|
327
|
+
LogVisitor.dead_score_reached__reset_acquirer_position(
|
316
328
|
logger, log_sampled, lock_key,
|
317
|
-
queue_ttl,
|
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
|
-
|
342
|
+
read_lock_key_queue,
|
343
|
+
write_lock_key_queue,
|
344
|
+
acquirer_id,
|
328
345
|
host_id,
|
329
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
443
|
+
RedisQueuedLocks::ConflictLockObtainError,
|
419
444
|
"Lock Conflict: trying to acquire the lock \"#{lock_key}\" " \
|
420
|
-
"that is already acquired by the current
|
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::
|
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
|
-
|
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
|
-
|
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 (
|
555
|
-
|
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::
|
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.
|
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
|
-
|
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::
|
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
|
-
|
59
|
+
{ ok: true, result: :ttl_extended }
|
59
60
|
else
|
60
|
-
|
61
|
+
{ ok: false, result: :async_expire_or_no_lock }
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
@@ -2,23 +2,23 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
# @since 1.0.0
|
5
|
-
module RedisQueuedLocks::
|
5
|
+
module RedisQueuedLocks::Acquirer::Keys
|
6
6
|
class << self
|
7
7
|
# @param redis_client [RedisClient]
|
8
8
|
# @option scan_size [Integer]
|
9
|
-
# @return [
|
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 |
|
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
|
-
|
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::
|
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
|
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::
|
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|
|