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.
@@ -23,8 +23,8 @@ module RedisQueuedLocks::Acquier::ReleaseLock
23
23
  # @param logger [::Logger,#debug]
24
24
  # - Logger object used from `configuration` layer (see config[:logger]);
25
25
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
26
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
27
- # Format: { ok: true/false, result: Hash<Symbil,Numeric|String> }
26
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean<Hash<Symbol,Numeric|String|Symbol>>]
27
+ # Format: { ok: true/false, result: Hash<Symbol,Numeric|String|Symbol> }
28
28
  #
29
29
  # @api private
30
30
  # @since 0.1.0
@@ -34,7 +34,7 @@ module RedisQueuedLocks::Acquier::ReleaseLock
34
34
 
35
35
  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
36
36
  fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
37
- time_at = Time.now.to_i
37
+ time_at = Time.now.to_f
38
38
  rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
39
39
  rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
40
40
 
@@ -49,7 +49,13 @@ module RedisQueuedLocks::Acquier::ReleaseLock
49
49
 
50
50
  RedisQueuedLocks::Data[
51
51
  ok: true,
52
- result: { rel_time: rel_time, rel_key: lock_key, rel_queue: lock_key_queue }
52
+ result: {
53
+ rel_time: rel_time,
54
+ rel_key: lock_key,
55
+ rel_queue: lock_key_queue,
56
+ queue_res: result[:queue],
57
+ lock_res: result[:lock]
58
+ }
53
59
  ]
54
60
  end
55
61
 
@@ -60,7 +66,14 @@ module RedisQueuedLocks::Acquier::ReleaseLock
60
66
  # @param redis [RedisClient]
61
67
  # @param lock_key [String]
62
68
  # @param lock_key_queue [String]
63
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
69
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Symbol>>]
70
+ # Format: {
71
+ # ok: true/false,
72
+ # result: {
73
+ # queue: :released/:nothing_to_release,
74
+ # lock: :released/:nothing_to_release
75
+ # }
76
+ # }
64
77
  #
65
78
  # @api private
66
79
  # @since 0.1.0
@@ -72,7 +85,13 @@ module RedisQueuedLocks::Acquier::ReleaseLock
72
85
  end
73
86
  end
74
87
 
75
- RedisQueuedLocks::Data[ok: true, result:]
88
+ RedisQueuedLocks::Data[
89
+ ok: true,
90
+ result: {
91
+ queue: (result[0] != 0) ? :released : :nothing_to_release,
92
+ lock: (result[1] != 0) ? :released : :nothing_to_release
93
+ }
94
+ ]
76
95
  end
77
96
  end
78
97
  end
@@ -14,4 +14,5 @@ module RedisQueuedLocks::Acquier
14
14
  require_relative 'acquier/queues'
15
15
  require_relative 'acquier/keys'
16
16
  require_relative 'acquier/extend_lock_ttl'
17
+ require_relative 'acquier/clear_dead_requests'
17
18
  end
@@ -20,6 +20,8 @@ class RedisQueuedLocks::Client
20
20
  setting :uniq_identifier, -> { RedisQueuedLocks::Resource.calc_uniq_identity }
21
21
  setting :logger, RedisQueuedLocks::Logging::VoidLogger
22
22
  setting :log_lock_try, false
23
+ setting :dead_request_ttl, (1 * 24 * 60 * 60 * 1000) # NOTE: 1 day in milliseconds
24
+ setting :is_timed_by_default, false
23
25
 
24
26
  validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
25
27
  validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
@@ -32,6 +34,8 @@ class RedisQueuedLocks::Client
32
34
  validate('uniq_identifier', :proc)
33
35
  validate('logger') { |val| RedisQueuedLocks::Logging.valid_interface?(val) }
34
36
  validate('log_lock_try', :boolean)
37
+ validate('dead_request_ttl') { |val| val.is_a?(::Integer) && val > 0 }
38
+ validate('is_timed_by_default', :boolean)
35
39
  end
36
40
 
37
41
  # @return [RedisClient]
@@ -40,15 +44,14 @@ class RedisQueuedLocks::Client
40
44
  # @since 0.1.0
41
45
  attr_reader :redis_client
42
46
 
47
+ # NOTE: attr_access here is chosen intentionally in order to have an ability to change
48
+ # uniq_identity value for debug purposes in runtime;
43
49
  # @return [String]
44
50
  #
45
51
  # @api private
46
52
  # @since 0.1.0
47
53
  attr_accessor :uniq_identity
48
54
 
49
- # NOTE: attr_access is chosen intentionally in order to have an ability to change
50
- # uniq_identity values for debug purposes in runtime;
51
-
52
55
  # @param redis_client [RedisClient]
53
56
  # Redis connection manager, which will be used for the lock acquierment and distribution.
54
57
  # It should be an instance of RedisClient.
@@ -119,7 +122,7 @@ class RedisQueuedLocks::Client
119
122
  ttl: config[:default_lock_ttl],
120
123
  queue_ttl: config[:default_queue_ttl],
121
124
  timeout: config[:try_to_lock_timeout],
122
- timed: false,
125
+ timed: config[:is_timed_by_default],
123
126
  retry_count: config[:retry_count],
124
127
  retry_delay: config[:retry_delay],
125
128
  retry_jitter: config[:retry_jitter],
@@ -167,7 +170,7 @@ class RedisQueuedLocks::Client
167
170
  ttl: config[:default_lock_ttl],
168
171
  queue_ttl: config[:default_queue_ttl],
169
172
  timeout: config[:try_to_lock_timeout],
170
- timed: false,
173
+ timed: config[:is_timed_by_default],
171
174
  retry_count: config[:retry_count],
172
175
  retry_delay: config[:retry_delay],
173
176
  retry_jitter: config[:retry_jitter],
@@ -191,20 +194,38 @@ class RedisQueuedLocks::Client
191
194
  raise_errors: true,
192
195
  identity:,
193
196
  fail_fast:,
197
+ logger:,
198
+ log_lock_try:,
194
199
  meta:,
195
200
  instrument:,
196
201
  &block
197
202
  )
198
203
  end
199
204
 
200
- # @param lock_name [String]
201
- # The lock name that should be released.
205
+ # @param lock_name [String] The lock name that should be released.
206
+ # @option logger [::Logger,#debug]
207
+ # @option instrumenter [#notify]
208
+ # @option instrument [NilClass,Any]
202
209
  # @return [RedisQueuedLocks::Data, Hash<Symbol,Any>]
203
- # Format: { ok: true/false, result: Symbol/Hash }.
210
+ # Format: {
211
+ # ok: true/false,
212
+ # result: {
213
+ # rel_time: Integer, # <millisecnds>
214
+ # rel_key: String, # lock key
215
+ # rel_queue: String, # lock queue
216
+ # queue_res: Symbol, # :released or :nothing_to_release
217
+ # lock_res: Symbol # :released or :nothing_to_release
218
+ # }
219
+ # }
204
220
  #
205
221
  # @api public
206
222
  # @since 0.1.0
207
- def unlock(lock_name)
223
+ def unlock(
224
+ lock_name,
225
+ logger: config[:logger],
226
+ instrumenter: config[:instrumenter],
227
+ instrument: nil
228
+ )
208
229
  RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
209
230
  redis_client,
210
231
  lock_name,
@@ -249,33 +270,60 @@ class RedisQueuedLocks::Client
249
270
  RedisQueuedLocks::Acquier::QueueInfo.queue_info(redis_client, lock_name)
250
271
  end
251
272
 
273
+ # This method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension.
274
+ # So the methid is spliited into the two commands:
275
+ # (1) read current pttl
276
+ # (2) set new ttl that is calculated as "current pttl + additional milliseconds"
277
+ # What can happen during these steps
278
+ # - lock is expired between commands or before the first command;
279
+ # - lock is expired before the second command;
280
+ # - lock is expired AND newly acquired by another process (so you will extend the
281
+ # totally new lock with fresh PTTL);
282
+ # Use it at your own risk and consider async nature when calling this method.
283
+ #
252
284
  # @param lock_name [String]
253
285
  # @param milliseconds [Integer] How many milliseconds should be added.
254
- # @return [?]
286
+ # @return [Hash<Symbol,Boolean|Symbol>]
287
+ # - { ok: true, result: :ttl_extended }
288
+ # - { ok: false, result: :async_expire_or_no_lock }
255
289
  #
256
290
  # @api public
257
291
  # @since 0.1.0
258
- def extend_lock_ttl(lock_name, milliseconds)
292
+ def extend_lock_ttl(lock_name, milliseconds, logger: config[:logger])
259
293
  RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
260
294
  redis_client,
261
295
  lock_name,
262
296
  milliseconds,
263
- config[:logger]
297
+ logger
264
298
  )
265
299
  end
266
300
 
301
+ # Releases all queues and locks.
302
+ # Returns:
303
+ # - :rel_time - (milliseconds) - time spent to release all locks and queues;
304
+ # - :rel_key_cnt - (integer) - the number of released redis keys (queus+locks);
305
+ #
267
306
  # @option batch_size [Integer]
268
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
269
- # Format: { ok: true/false, result: Symbol/Hash }.
307
+ # @option logger [::Logger,#debug]
308
+ # @option instrumenter [#notify]
309
+ # @option instrument [NilClass,Any]
310
+ # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Numeric>>]
311
+ # Example: { ok: true, result { rel_key_cnt: 100, rel_time: 0.01 } }
270
312
  #
271
313
  # @api public
272
314
  # @since 0.1.0
273
- def clear_locks(batch_size: config[:lock_release_batch_size])
315
+ def clear_locks(
316
+ batch_size: config[:lock_release_batch_size],
317
+ logger: config[:logger],
318
+ instrumenter: config[:instrumenter],
319
+ instrument: nil
320
+ )
274
321
  RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
275
322
  redis_client,
276
323
  batch_size,
277
- config[:instrumenter],
278
- config[:logger]
324
+ logger,
325
+ instrumenter,
326
+ instrument
279
327
  )
280
328
  end
281
329
 
@@ -352,5 +400,36 @@ class RedisQueuedLocks::Client
352
400
  def keys(scan_size: config[:key_extraction_batch_size])
353
401
  RedisQueuedLocks::Acquier::Keys.keys(redis_client, scan_size:)
354
402
  end
403
+
404
+ # @option dead_ttl [Integer]
405
+ # - the time period (in millsiecnds) after whcih the lock request is
406
+ # considered as dead;
407
+ # - `config[:dead_request_ttl]` is used by default;
408
+ # @option scan_size [Integer]
409
+ # The batch of scanned keys for Redis'es SCAN command.
410
+ # @option logger [::Logger,#debug]
411
+ # @option instrumenter [#notify]
412
+ # @option instrument [NilClass,Any]
413
+ # @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
414
+ # Format: { ok: true, result: { processed_queus: Set<String> } }
415
+ #
416
+ # @api public
417
+ # @since 0.1.0
418
+ def clear_dead_requests(
419
+ dead_ttl: config[:dead_request_ttl],
420
+ scan_size: config[:key_extraction_batch_size],
421
+ logger: config[:logger],
422
+ instrumenter: config[:instrumenter],
423
+ instrument: nil
424
+ )
425
+ RedisQueuedLocks::Acquier::ClearDeadRequests.clear_dead_requests(
426
+ redis_client,
427
+ scan_size,
428
+ dead_ttl,
429
+ logger,
430
+ instrumenter,
431
+ instrument
432
+ )
433
+ end
355
434
  end
356
435
  # rubocop:enable Metrics/ClassLength
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # NOTE: wiill be rewritten with Ruby's 3.2 "Data" class;
4
3
  class RedisQueuedLocks::Data < Hash
5
4
  end
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.38
9
- VERSION = '0.0.38'
8
+ # @version 0.0.40
9
+ VERSION = '0.0.40'
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.38
4
+ version: 0.0.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-28 00:00:00.000000000 Z
11
+ date: 2024-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -61,6 +61,7 @@ files:
61
61
  - lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb
62
62
  - lib/redis_queued_locks/acquier/acquire_lock/with_acq_timeout.rb
63
63
  - lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb
64
+ - lib/redis_queued_locks/acquier/clear_dead_requests.rb
64
65
  - lib/redis_queued_locks/acquier/extend_lock_ttl.rb
65
66
  - lib/redis_queued_locks/acquier/is_locked.rb
66
67
  - lib/redis_queued_locks/acquier/is_queued.rb
@@ -107,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
108
  - !ruby/object:Gem::Version
108
109
  version: '0'
109
110
  requirements: []
110
- rubygems_version: 3.5.1
111
+ rubygems_version: 3.3.7
111
112
  signing_key:
112
113
  specification_version: 4
113
114
  summary: Queued distributed locks based on Redis.