redis_queued_locks 0.0.38 → 0.0.40
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +32 -0
- data/README.md +393 -78
- data/Rakefile +14 -5
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +35 -73
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +2 -2
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +25 -15
- data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +52 -0
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +21 -2
- data/lib/redis_queued_locks/acquier/lock_info.rb +3 -3
- data/lib/redis_queued_locks/acquier/locks.rb +7 -5
- data/lib/redis_queued_locks/acquier/queues.rb +4 -2
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +14 -13
- data/lib/redis_queued_locks/acquier/release_lock.rb +25 -6
- data/lib/redis_queued_locks/acquier.rb +1 -0
- data/lib/redis_queued_locks/client.rb +96 -17
- data/lib/redis_queued_locks/data.rb +0 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +4 -3
data/Rakefile
CHANGED
@@ -2,11 +2,20 @@
|
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
require 'rspec/core/rake_task'
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new(:spec)
|
7
|
-
|
5
|
+
require 'rubocop'
|
8
6
|
require 'rubocop/rake_task'
|
7
|
+
require 'rubocop-performance'
|
8
|
+
require 'rubocop-rspec'
|
9
|
+
require 'rubocop-rake'
|
10
|
+
|
11
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
12
|
+
config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
|
13
|
+
t.options = ['--config', config_path]
|
14
|
+
t.requires << 'rubocop-rspec'
|
15
|
+
t.requires << 'rubocop-performance'
|
16
|
+
t.requires << 'rubocop-rake'
|
17
|
+
end
|
9
18
|
|
10
|
-
|
19
|
+
RSpec::Core::RakeTask.new(:rspec)
|
11
20
|
|
12
|
-
task default:
|
21
|
+
task default: :rspec
|
@@ -42,12 +42,12 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
42
42
|
|
43
43
|
if log_lock_try
|
44
44
|
run_non_critical do
|
45
|
-
logger.debug
|
45
|
+
logger.debug do
|
46
46
|
"[redis_queued_locks.try_lock.start] " \
|
47
47
|
"lock_key => '#{lock_key}' " \
|
48
48
|
"queue_ttl => #{queue_ttl} " \
|
49
49
|
"acq_id => '#{acquier_id}'"
|
50
|
-
|
50
|
+
end
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -55,12 +55,12 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
55
55
|
result = redis.with do |rconn|
|
56
56
|
if log_lock_try
|
57
57
|
run_non_critical do
|
58
|
-
logger.debug
|
58
|
+
logger.debug do
|
59
59
|
"[redis_queued_locks.try_lock.rconn_fetched] " \
|
60
60
|
"lock_key => '#{lock_key}' " \
|
61
61
|
"queue_ttl => #{queue_ttl} " \
|
62
62
|
"acq_id => '#{acquier_id}'"
|
63
|
-
|
63
|
+
end
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -74,25 +74,21 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
74
74
|
inter_result = :fail_fast_no_try
|
75
75
|
else
|
76
76
|
# Step 1: add an acquier to the lock acquirement queue
|
77
|
-
|
77
|
+
rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
78
78
|
|
79
79
|
if log_lock_try
|
80
80
|
run_non_critical do
|
81
|
-
logger.debug
|
81
|
+
logger.debug do
|
82
82
|
"[redis_queued_locks.try_lock.acq_added_to_queue] " \
|
83
83
|
"lock_key => '#{lock_key}' " \
|
84
84
|
"queue_ttl => #{queue_ttl} " \
|
85
85
|
"acq_id => '#{acquier_id}'"
|
86
|
-
|
86
|
+
end
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
-
RedisQueuedLocks.debug(
|
91
|
-
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
92
|
-
)
|
93
|
-
|
94
90
|
# Step 2.1: drop expired acquiers from the lock queue
|
95
|
-
|
91
|
+
rconn.call(
|
96
92
|
'ZREMRANGEBYSCORE',
|
97
93
|
lock_key_queue,
|
98
94
|
'-inf',
|
@@ -101,58 +97,44 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
101
97
|
|
102
98
|
if log_lock_try
|
103
99
|
run_non_critical do
|
104
|
-
logger.debug
|
100
|
+
logger.debug do
|
105
101
|
"[redis_queued_locks.try_lock.remove_expired_acqs] " \
|
106
102
|
"lock_key => '#{lock_key}' " \
|
107
103
|
"queue_ttl => #{queue_ttl} " \
|
108
104
|
"acq_id => '#{acquier_id}'"
|
109
|
-
|
105
|
+
end
|
110
106
|
end
|
111
107
|
end
|
112
108
|
|
113
|
-
RedisQueuedLocks.debug(
|
114
|
-
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
115
|
-
)
|
116
|
-
|
117
109
|
# Step 3: get the actual acquier waiting in the queue
|
118
110
|
waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
|
119
111
|
|
120
112
|
if log_lock_try
|
121
113
|
run_non_critical do
|
122
|
-
logger.debug
|
114
|
+
logger.debug do
|
123
115
|
"[redis_queued_locks.try_lock.get_first_from_queue] " \
|
124
116
|
"lock_key => '#{lock_key}' " \
|
125
117
|
"queue_ttl => #{queue_ttl} " \
|
126
118
|
"acq_id => '#{acquier_id}' " \
|
127
119
|
"first_acq_id_in_queue => '#{waiting_acquier}'"
|
128
|
-
|
120
|
+
end
|
129
121
|
end
|
130
122
|
end
|
131
123
|
|
132
|
-
RedisQueuedLocks.debug(
|
133
|
-
"Step №3: какой процесс в очереди сейчас ждет. " \
|
134
|
-
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
135
|
-
)
|
136
|
-
|
137
124
|
# Step PRE-4.x: check if the request time limit is reached
|
138
125
|
# (when the current try self-removes itself from queue (queue ttl has come))
|
139
126
|
if waiting_acquier == nil
|
140
127
|
if log_lock_try
|
141
128
|
run_non_critical do
|
142
|
-
logger.debug
|
129
|
+
logger.debug do
|
143
130
|
"[redis_queued_locks.try_lock.exit__queue_ttl_reached] " \
|
144
131
|
"lock_key => '#{lock_key}' " \
|
145
132
|
"queue_ttl => #{queue_ttl} " \
|
146
133
|
"acq_id => '#{acquier_id}'"
|
147
|
-
|
134
|
+
end
|
148
135
|
end
|
149
136
|
end
|
150
137
|
|
151
|
-
RedisQueuedLocks.debug(
|
152
|
-
"Step PRE-ROLLBACK №0: достигли лимита времени эквайра лока (queue ttl). выходим. " \
|
153
|
-
"[Наша позиция: #{acquier_id}. queue_ttl: #{queue_ttl}]"
|
154
|
-
)
|
155
|
-
|
156
138
|
inter_result = :dead_score_reached
|
157
139
|
# Step 4: check the actual acquier: is it ours? are we aready to lock?
|
158
140
|
elsif waiting_acquier != acquier_id
|
@@ -160,59 +142,41 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
160
142
|
|
161
143
|
if log_lock_try
|
162
144
|
run_non_critical do
|
163
|
-
logger.debug
|
145
|
+
logger.debug do
|
164
146
|
"[redis_queued_locks.try_lock.exit__no_first] " \
|
165
147
|
"lock_key => '#{lock_key}' " \
|
166
148
|
"queue_ttl => #{queue_ttl} " \
|
167
149
|
"acq_id => '#{acquier_id}' " \
|
168
150
|
"first_acq_id_in_queue => '#{waiting_acquier}' " \
|
169
151
|
"<current_lock_data> => <<#{rconn.call('HGETALL', lock_key).to_h}>>"
|
170
|
-
|
152
|
+
end
|
171
153
|
end
|
172
154
|
end
|
173
155
|
|
174
|
-
RedisQueuedLocks.debug(
|
175
|
-
"Step ROLLBACK №1: не одинаковые ключи. выходим. " \
|
176
|
-
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
177
|
-
)
|
178
|
-
|
179
156
|
inter_result = :acquier_is_not_first_in_queue
|
180
157
|
else
|
181
158
|
# NOTE: our time has come! let's try to acquire the lock!
|
182
159
|
|
183
|
-
# Step 5: check if the our lock is already acquired
|
160
|
+
# Step 5: find the lock -> check if the our lock is already acquired
|
184
161
|
locked_by_acquier = rconn.call('HGET', lock_key, 'acq_id')
|
185
162
|
|
186
|
-
# rubocop:disable Layout/LineLength
|
187
|
-
RedisQueuedLocks.debug(
|
188
|
-
"Ste №5: Ищем требуемый лок. " \
|
189
|
-
"[HGET<#{lock_key}>: " \
|
190
|
-
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
191
|
-
)
|
192
|
-
# rubocop:enable Layout/LineLength
|
193
|
-
|
194
163
|
if locked_by_acquier
|
195
164
|
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
196
165
|
|
197
166
|
if log_lock_try
|
198
167
|
run_non_critical do
|
199
|
-
logger.debug
|
200
|
-
"[redis_queued_locks.try_lock.
|
168
|
+
logger.debug do
|
169
|
+
"[redis_queued_locks.try_lock.exit__lock_still_obtained] " \
|
201
170
|
"lock_key => '#{lock_key}' " \
|
202
171
|
"queue_ttl => #{queue_ttl} " \
|
203
172
|
"acq_id => '#{acquier_id}' " \
|
204
173
|
"first_acq_id_in_queue => '#{waiting_acquier}' " \
|
205
174
|
"locked_by_acq_id => '#{locked_by_acquier}' " \
|
206
175
|
"<current_lock_data> => <<#{rconn.call('HGETALL', lock_key).to_h}>>"
|
207
|
-
|
176
|
+
end
|
208
177
|
end
|
209
178
|
end
|
210
179
|
|
211
|
-
RedisQueuedLocks.debug(
|
212
|
-
"Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
|
213
|
-
"[Занят процессом: #{locked_by_acquier}]"
|
214
|
-
)
|
215
|
-
|
216
180
|
inter_result = :lock_is_still_acquired
|
217
181
|
else
|
218
182
|
# NOTE: required lock is free and ready to be acquired! acquire!
|
@@ -220,16 +184,6 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
220
184
|
# Step 6.1: remove our acquier from waiting queue
|
221
185
|
transact.call('ZREM', lock_key_queue, acquier_id)
|
222
186
|
|
223
|
-
RedisQueuedLocks.debug(
|
224
|
-
'Step №4: Забираем наш текущий процесс из очереди. [ZREM]'
|
225
|
-
)
|
226
|
-
|
227
|
-
# rubocop:disable Layout/LineLength
|
228
|
-
RedisQueuedLocks.debug(
|
229
|
-
"===> <FINAL> Step №6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
|
230
|
-
)
|
231
|
-
# rubocop:enable Layout/LineLength
|
232
|
-
|
233
187
|
# Step 6.2: acquire a lock and store an info about the acquier
|
234
188
|
transact.call(
|
235
189
|
'HSET',
|
@@ -245,12 +199,12 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
245
199
|
|
246
200
|
if log_lock_try
|
247
201
|
run_non_critical do
|
248
|
-
logger.debug
|
249
|
-
"[redis_queued_locks.try_lock.
|
202
|
+
logger.debug do
|
203
|
+
"[redis_queued_locks.try_lock.obtain__free_to_acquire] " \
|
250
204
|
"lock_key => '#{lock_key}' " \
|
251
205
|
"queue_ttl => #{queue_ttl} " \
|
252
206
|
"acq_id => '#{acquier_id}'"
|
253
|
-
|
207
|
+
end
|
254
208
|
end
|
255
209
|
end
|
256
210
|
end
|
@@ -297,18 +251,26 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
297
251
|
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
|
298
252
|
|
299
253
|
# @param redis [RedisClient]
|
254
|
+
# @param logger [::Logger,#debug]
|
255
|
+
# @param lock_key [String]
|
300
256
|
# @param lock_key_queue [String]
|
257
|
+
# @param queue_ttl [Integer]
|
301
258
|
# @param acquier_id [String]
|
302
259
|
# @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
|
303
260
|
#
|
304
261
|
# @api private
|
305
262
|
# @since 0.1.0
|
306
|
-
def dequeue_from_lock_queue(redis, lock_key_queue, acquier_id)
|
263
|
+
def dequeue_from_lock_queue(redis, logger, lock_key, lock_key_queue, queue_ttl, acquier_id)
|
307
264
|
result = redis.call('ZREM', lock_key_queue, acquier_id)
|
308
265
|
|
309
|
-
|
310
|
-
|
311
|
-
|
266
|
+
run_non_critical do
|
267
|
+
logger.debug do
|
268
|
+
"[redis_queued_locks.fail_fast_or_limits_reached__dequeue] " \
|
269
|
+
"lock_key => '#{lock_key}' " \
|
270
|
+
"queue_ttl => '#{queue_ttl}' " \
|
271
|
+
"acq_id => '#{acquier_id}'"
|
272
|
+
end
|
273
|
+
end
|
312
274
|
|
313
275
|
RedisQueuedLocks::Data[ok: true, result: result]
|
314
276
|
end
|
@@ -40,12 +40,12 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
40
40
|
end
|
41
41
|
ensure
|
42
42
|
run_non_critical do
|
43
|
-
logger.debug
|
43
|
+
logger.debug do
|
44
44
|
"[redis_queued_locks.expire_lock] " \
|
45
45
|
"lock_key => '#{lock_key}' " \
|
46
46
|
"queue_ttl => #{queue_ttl} " \
|
47
47
|
"acq_id => '#{acquier_id}'"
|
48
|
-
|
48
|
+
end
|
49
49
|
end
|
50
50
|
redis.call('EXPIRE', lock_key, '0')
|
51
51
|
end
|
@@ -23,11 +23,11 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
23
23
|
# @since 0.1.0
|
24
24
|
extend RedisQueuedLocks::Utilities
|
25
25
|
|
26
|
-
# @return [Integer] Redis
|
26
|
+
# @return [Integer] Redis time error (in milliseconds).
|
27
27
|
#
|
28
28
|
# @api private
|
29
29
|
# @since 0.1.0
|
30
|
-
|
30
|
+
REDIS_TIMESHIFT_ERROR = 2
|
31
31
|
|
32
32
|
class << self
|
33
33
|
# @param redis [RedisClient]
|
@@ -146,9 +146,6 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
146
146
|
ractor_id,
|
147
147
|
identity
|
148
148
|
)
|
149
|
-
# NOTE:
|
150
|
-
# - think aobut the redis expiration error
|
151
|
-
# - (ttl - REDIS_EXPIRE_ERROR).yield_self { |val| (val == 0) ? ttl : val }
|
152
149
|
lock_ttl = ttl
|
153
150
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
154
151
|
lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
|
@@ -165,15 +162,24 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
165
162
|
hold_time: nil, # NOTE: in milliseconds
|
166
163
|
rel_time: nil # NOTE: in milliseconds
|
167
164
|
}
|
168
|
-
|
165
|
+
|
166
|
+
acq_dequeue = proc do
|
167
|
+
dequeue_from_lock_queue(
|
168
|
+
redis, logger,
|
169
|
+
lock_key,
|
170
|
+
lock_key_queue,
|
171
|
+
queue_ttl,
|
172
|
+
acquier_id
|
173
|
+
)
|
174
|
+
end
|
169
175
|
|
170
176
|
run_non_critical do
|
171
|
-
logger.debug
|
177
|
+
logger.debug do
|
172
178
|
"[redis_queued_locks.start_lock_obtaining] " \
|
173
179
|
"lock_key => '#{lock_key}' " \
|
174
180
|
"queue_ttl => #{queue_ttl} " \
|
175
181
|
"acq_id => '#{acquier_id}'"
|
176
|
-
|
182
|
+
end
|
177
183
|
end
|
178
184
|
|
179
185
|
# Step 2: try to lock with timeout
|
@@ -183,12 +189,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
183
189
|
# Step 2.1: caclically try to obtain the lock
|
184
190
|
while acq_process[:should_try]
|
185
191
|
run_non_critical do
|
186
|
-
logger.debug
|
192
|
+
logger.debug do
|
187
193
|
"[redis_queued_locks.start_try_to_lock_cycle] " \
|
188
194
|
"lock_key => '#{lock_key}' " \
|
189
195
|
"queue_ttl => #{queue_ttl} " \
|
190
196
|
"acq_id => '{#{acquier_id}'"
|
191
|
-
|
197
|
+
end
|
192
198
|
end
|
193
199
|
|
194
200
|
# Step 2.X: check the actual score: is it in queue ttl limit or not?
|
@@ -197,12 +203,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
197
203
|
acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
|
198
204
|
|
199
205
|
run_non_critical do
|
200
|
-
logger.debug
|
206
|
+
logger.debug do
|
201
207
|
"[redis_queued_locks.dead_score_reached__reset_acquier_position] " \
|
202
208
|
"lock_key => '#{lock_key} " \
|
203
209
|
"queue_ttl => #{queue_ttl} " \
|
204
210
|
"acq_id => '#{acquier_id}'"
|
205
|
-
|
211
|
+
end
|
206
212
|
end
|
207
213
|
end
|
208
214
|
|
@@ -230,13 +236,13 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
230
236
|
# Step 2.1: analyze an acquirement attempt
|
231
237
|
if ok
|
232
238
|
run_non_critical do
|
233
|
-
logger.debug
|
239
|
+
logger.debug do
|
234
240
|
"[redis_queued_locks.lock_obtained] " \
|
235
241
|
"lock_key => '#{result[:lock_key]}' " \
|
236
242
|
"queue_ttl => #{queue_ttl} " \
|
237
243
|
"acq_id => '#{acquier_id}' " \
|
238
244
|
"acq_time => #{acq_time} (ms)"
|
239
|
-
|
245
|
+
end
|
240
246
|
end
|
241
247
|
|
242
248
|
# Step X (instrumentation): lock obtained
|
@@ -315,7 +321,11 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
315
321
|
if block_given?
|
316
322
|
begin
|
317
323
|
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
318
|
-
|
324
|
+
|
325
|
+
ttl_shift = (
|
326
|
+
(yield_time - acq_process[:acq_end_time]) * 1000 - REDIS_TIMESHIFT_ERROR
|
327
|
+
).ceil(2)
|
328
|
+
|
319
329
|
yield_with_expire(
|
320
330
|
redis,
|
321
331
|
logger,
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.1.0
|
5
|
+
module RedisQueuedLocks::Acquier::ClearDeadRequests
|
6
|
+
class << self
|
7
|
+
# @param redis_client [RedisClient]
|
8
|
+
# @param scan_size [Integer]
|
9
|
+
# @param dead_ttl [Integer] In milliseconds
|
10
|
+
# @param logger [::Logger,#debug]
|
11
|
+
# @param instrumenter [#notify]
|
12
|
+
# @param instrument [NilClass,Any]
|
13
|
+
# @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
# @since 0.1.0
|
17
|
+
def clear_dead_requests(redis_client, scan_size, dead_ttl, logger, instrumenter, instrument)
|
18
|
+
dead_score = RedisQueuedLocks::Resource.acquier_dead_score(dead_ttl / 1000.0)
|
19
|
+
|
20
|
+
result = Set.new.tap do |processed_queues|
|
21
|
+
redis_client.with do |rconn|
|
22
|
+
each_lock_queue(rconn, scan_size) do |lock_queue|
|
23
|
+
rconn.call('ZREMRANGEBYSCORE', lock_queue, '-inf', dead_score)
|
24
|
+
processed_queues << lock_queue
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
RedisQueuedLocks::Data[ok: true, result: { processed_queues: result }]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param redis_client [RedisClient]
|
35
|
+
# @param scan_size [Integer]
|
36
|
+
# @yield [lock_queue]
|
37
|
+
# @yieldparam lock_queue [String]
|
38
|
+
# @yieldreturn [void]
|
39
|
+
# @return [Enumerator]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.1.0
|
43
|
+
def each_lock_queue(redis_client, scan_size, &block)
|
44
|
+
redis_client.scan(
|
45
|
+
'MATCH',
|
46
|
+
RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
|
47
|
+
count: scan_size,
|
48
|
+
&block
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -3,17 +3,36 @@
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
5
|
module RedisQueuedLocks::Acquier::ExtendLockTTL
|
6
|
+
# @return [String]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.1.0
|
10
|
+
EXTEND_LOCK_PTTL = <<~LUA_SCRIPT.strip.tr("\n", '').freeze
|
11
|
+
local new_lock_pttl = redis.call("PTTL", KEYS[1]) + ARGV[1];
|
12
|
+
return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
|
13
|
+
LUA_SCRIPT
|
14
|
+
|
6
15
|
class << self
|
7
16
|
# @param redis_client [RedisClient]
|
8
17
|
# @param lock_name [String]
|
9
18
|
# @param milliseconds [Integer]
|
10
19
|
# @param logger [::Logger,#debug]
|
11
|
-
# @return [
|
20
|
+
# @return [Hash<Symbol,Boolean|Symbol>]
|
12
21
|
#
|
13
22
|
# @api private
|
14
23
|
# @since 0.1.0
|
15
24
|
def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
|
16
|
-
|
25
|
+
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
26
|
+
|
27
|
+
# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
|
28
|
+
result = redis_client.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, milliseconds)
|
29
|
+
# TODO: upload scripts to the redis
|
30
|
+
|
31
|
+
if result == 1
|
32
|
+
RedisQueuedLocks::Data[ok: true, result: :ttl_extended]
|
33
|
+
else
|
34
|
+
RedisQueuedLocks::Data[ok: false, result: :async_expire_or_no_lock]
|
35
|
+
end
|
17
36
|
end
|
18
37
|
end
|
19
38
|
end
|
@@ -21,9 +21,9 @@ module RedisQueuedLocks::Acquier::LockInfo
|
|
21
21
|
def lock_info(redis_client, lock_name)
|
22
22
|
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
|
23
23
|
|
24
|
-
result = redis_client.
|
25
|
-
|
26
|
-
|
24
|
+
result = redis_client.pipelined do |pipeline|
|
25
|
+
pipeline.call('HGETALL', lock_key)
|
26
|
+
pipeline.call('PTTL', lock_key)
|
27
27
|
end
|
28
28
|
|
29
29
|
if result == nil
|
@@ -12,8 +12,10 @@ module RedisQueuedLocks::Acquier::Locks
|
|
12
12
|
# @api private
|
13
13
|
# @since 0.1.0
|
14
14
|
def locks(redis_client, scan_size:, with_info:)
|
15
|
-
|
16
|
-
|
15
|
+
redis_client.with do |rconn|
|
16
|
+
lock_keys = scan_locks(rconn, scan_size)
|
17
|
+
with_info ? extract_locks_info(rconn, lock_keys) : lock_keys
|
18
|
+
end
|
17
19
|
end
|
18
20
|
|
19
21
|
private
|
@@ -50,9 +52,9 @@ module RedisQueuedLocks::Acquier::Locks
|
|
50
52
|
# Step X: iterate each lock and extract their info
|
51
53
|
lock_keys.each do |lock_key|
|
52
54
|
# Step 1: extract lock info from redis
|
53
|
-
lock_info = redis_client.
|
54
|
-
|
55
|
-
|
55
|
+
lock_info = redis_client.pipelined do |pipeline|
|
56
|
+
pipeline.call('HGETALL', lock_key)
|
57
|
+
pipeline.call('PTTL', lock_key)
|
56
58
|
end.yield_self do |result| # Step 2: format the result
|
57
59
|
# Step 2.X: lock is released
|
58
60
|
if result == nil
|
@@ -12,8 +12,10 @@ module RedisQueuedLocks::Acquier::Queues
|
|
12
12
|
# @api private
|
13
13
|
# @since 0.1.0
|
14
14
|
def queues(redis_client, scan_size:, with_info:)
|
15
|
-
|
16
|
-
|
15
|
+
redis_client.with do |rconn|
|
16
|
+
lock_queues = scan_queues(rconn, scan_size)
|
17
|
+
with_info ? extract_queues_info(rconn, lock_queues) : lock_queues
|
18
|
+
end
|
17
19
|
end
|
18
20
|
|
19
21
|
private
|
@@ -15,20 +15,23 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
15
15
|
# Redis connection client.
|
16
16
|
# @param batch_size [Integer]
|
17
17
|
# The number of lock keys that should be released in a time.
|
18
|
-
# @param isntrumenter [#notify]
|
19
|
-
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
20
18
|
# @param logger [::Logger,#debug]
|
21
19
|
# - Logger object used from `configuration` layer (see config[:logger]);
|
22
20
|
# - See RedisQueuedLocks::Logging::VoidLogger for example;
|
21
|
+
# @param isntrumenter [#notify]
|
22
|
+
# See RedisQueuedLocks::Instrument::ActiveSupport for example.
|
23
|
+
# @option instrument [NilClass,Any]
|
24
|
+
# - Custom instrumentation data wich will be passed to the instrumenter's payload
|
25
|
+
# with :instrument key;
|
23
26
|
# @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
|
24
|
-
# Format: { ok: true
|
27
|
+
# Format: { ok: true, result: Hash<Symbol,Numeric> }
|
25
28
|
#
|
26
29
|
# @api private
|
27
30
|
# @since 0.1.0
|
28
|
-
def release_all_locks(redis, batch_size, instrumenter,
|
31
|
+
def release_all_locks(redis, batch_size, logger, instrumenter, instrument)
|
29
32
|
rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
30
33
|
fully_release_all_locks(redis, batch_size) => { ok:, result: }
|
31
|
-
time_at = Time.now.
|
34
|
+
time_at = Time.now.to_f
|
32
35
|
rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
33
36
|
rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
|
34
37
|
|
@@ -36,13 +39,13 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
36
39
|
instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
|
37
40
|
at: time_at,
|
38
41
|
rel_time: rel_time,
|
39
|
-
|
42
|
+
rel_key_cnt: result[:rel_key_cnt]
|
40
43
|
})
|
41
44
|
end
|
42
45
|
|
43
46
|
RedisQueuedLocks::Data[
|
44
47
|
ok: true,
|
45
|
-
result: { rel_key_cnt: result[:
|
48
|
+
result: { rel_key_cnt: result[:rel_key_cnt], rel_time: rel_time }
|
46
49
|
]
|
47
50
|
end
|
48
51
|
|
@@ -52,7 +55,8 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
52
55
|
#
|
53
56
|
# @param redis [RedisClient]
|
54
57
|
# @param batch_size [Integer]
|
55
|
-
# @return [RedisQueuedLocks::Data,Hash<Symbol,
|
58
|
+
# @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
|
59
|
+
# - Exmaple: { ok: true, result: { rel_key_cnt: 12345 } }
|
56
60
|
#
|
57
61
|
# @api private
|
58
62
|
# @since 0.1.0
|
@@ -66,8 +70,7 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
66
70
|
count: batch_size
|
67
71
|
) do |lock_queue|
|
68
72
|
# TODO: reduce unnecessary iterations
|
69
|
-
pipeline.call('
|
70
|
-
pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
|
73
|
+
pipeline.call('EXPIRE', lock_queue, '0')
|
71
74
|
end
|
72
75
|
|
73
76
|
# Step B: release all locks
|
@@ -82,9 +85,7 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
|
-
|
86
|
-
|
87
|
-
RedisQueuedLocks::Data[ok: true, result: { rel_keys: rel_keys }]
|
88
|
+
RedisQueuedLocks::Data[ok: true, result: { rel_key_cnt: result.sum }]
|
88
89
|
end
|
89
90
|
end
|
90
91
|
end
|