redis_queued_locks 0.0.38 → 0.0.40
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 +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
|