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.
@@ -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.