redis_queued_locks 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e984603ea8f509f8e5f7f685c356d1f838b16bc950e1de83ff8f66e6afa83fcf
4
- data.tar.gz: 1b2eee0f32c911caecf08214b2b404d620ffb10501ae72d7721235b64fc02362
3
+ metadata.gz: 5dbd11cefb9d18a816eb4c63fe1178f87325c30d75efc2bc806d29f41994fc2c
4
+ data.tar.gz: 9697f4bf452aaaa7cade45320c50d16b510c657074ba97513c5f8de9c51f6c49
5
5
  SHA512:
6
- metadata.gz: b7e2d574eeeff4a9a235f39ee25bde152880dcd12e7066196a8304e54098480ef05672edd89a6931ece225711b96602f23ce76304c555dbd623d30099e35ca0a
7
- data.tar.gz: d7f5075bbf136e672ebb1632da5c6a78b2b72886bc1d5e8b0d9425cf38b14f5eef5a097fdc835c78f5f2d26885512279926a33bdbdb559151b875453d5312c9e
6
+ metadata.gz: b28ef92375c7eef4d6c43fccb5e44105d653fb00e052edcf67316f8da13578ad7b39086b3799782592b38bf046760f8faa1238b92de293b83eb1e4a7c441413e
7
+ data.tar.gz: dfd3891ae8d0303b88ac95268056addc3b4e03f862a5200166de9553b412eee78b77f42193f133f9dd340793b67928806e2e05451517585c2282fb7e4c871d1f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.3] - 2024-02-26
4
+ ### Changed
5
+ - Instrumentation events:
6
+ - `"redis_queued_locks.explicit_all_locks_release"`
7
+ - re-factored with fully pipelined invocation;
8
+ - removed `rel_queue_cnt` and `rel_lock_cnt` because of the pipelined invocation
9
+ misses the concrete results and now we can receive only "released redis keys count";
10
+ - adde `rel_keys` payload data (released redis keys);
11
+
3
12
  ## [0.0.2] - 2024-02-26
4
13
  ### Added
5
14
  - Instrumentation events:
data/README.md CHANGED
@@ -22,7 +22,6 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
22
22
  - `lock_key` - `string` - lock name;
23
23
  - `ts` - `integer`/`epoch` - the time when lock was obtained;
24
24
  - `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
25
- - `rel_time` - `float`/`milliseconds` - time spent on lock "holding + releasing";
26
25
  - `"redis_queued_locks.explicit_lock_release"`
27
26
  - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
28
27
  - payload:
@@ -31,13 +30,8 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
31
30
  - `lock_key` - `string` - released lock (lock name);
32
31
  - `lock_key_queue` - `string` - released lock queue (lock queue name);
33
32
  - `"redis_queued_locks.explicit_all_locks_release"`
34
- - an event signalizes about the explicit all locks release (invoked viaa `RedisQueuedLock#clear_locks`);
33
+ - an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
35
34
  - payload:
36
- - `rel_time` - `float`/`milliseconds` - time when the locks was released
35
+ - `rel_time` - `float`/`milliseconds` - time spent on the lock release;
37
36
  - `at` - `integer`/`epoch` - the time when the operation has ended;
38
- - `rel_lock_cnt` - `integer` - released lock count;
39
- - `rel_queue_cnt` - `integer` - released queue count;
40
-
41
- ## Todo
42
-
43
- - CI (github actions);
37
+ - `rel_keys` - `integer` - released redis keys count (`released queu keys` + `released lock keys`);
@@ -3,6 +3,8 @@
3
3
  # @api private
4
4
  # @since 0.1.0
5
5
  module RedisQueuedLocks::Acquier::Delay
6
+ # Sleep with random time-shifting (it is necessary for empty-time-slot lock acquirement).
7
+ #
6
8
  # @param retry_delay [Integer] In milliseconds
7
9
  # @param retry_jitter [Integer] In milliseconds
8
10
  # @return [void]
@@ -3,10 +3,10 @@
3
3
  # @api private
4
4
  # @since 0.1.0
5
5
  module RedisQueuedLocks::Acquier::Expire
6
- # @param redis [RedisClient]
7
- # @param lock_key [String]
8
- # @param block [Block]
9
- # @return [void]
6
+ # @param redis [RedisClient] Redis connection manager.
7
+ # @param lock_key [String] Lock key to be expired.
8
+ # @param block [Block] Custom logic that should be invoked unter the obtained lock.
9
+ # @return [Any,NilClass] nil is returned no block parametr is provided.
10
10
  #
11
11
  # @api private
12
12
  # @since 0.1.0
@@ -30,37 +30,31 @@ module RedisQueuedLocks::Acquier::Release
30
30
  # @api private
31
31
  # @since 0.1.0
32
32
  def fully_release_all_locks(redis, batch_size)
33
- rel_queue_cnt = 0
34
- rel_lock_cnt = 0
35
-
36
- # Step A: release all queus and their related locks
37
- redis.scan(
38
- 'MATCH',
39
- RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
40
- count: batch_size
41
- ) do |lock_queue|
42
- rel_queue_cnt += 1
43
- rel_lock_cnt += 1
44
-
45
- redis.pipelined do |pipeline|
33
+ result = redis.pipelined do |pipeline|
34
+ # Step A: release all queus and their related locks
35
+ redis.scan(
36
+ 'MATCH',
37
+ RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
38
+ count: batch_size
39
+ ) do |lock_queue|
40
+ puts "RELEASE (lock_queue): #{lock_queue}"
46
41
  pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
47
- pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue))
42
+ pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), "0")
48
43
  end
49
- end
50
-
51
- # Step B: release all locks
52
- redis.pipelined do |pipeline|
53
- rel_lock_cnt += 1
54
44
 
45
+ # Step B: release all locks
55
46
  redis.scan(
56
47
  'MATCH',
57
48
  RedisQueuedLocks::Resource::LOCK_PATTERN,
58
49
  count: batch_size
59
50
  ) do |lock_key|
51
+ puts "RELEASE (lock_key): #{lock_key}"
60
52
  pipeline.call('EXPIRE', lock_key, '0')
61
53
  end
62
54
  end
63
55
 
64
- { ok: true, result: { rel_queue_cnt:, rel_lock_cnt: } }
56
+ rel_keys = result.count { |red_res| red_res == 0 }
57
+
58
+ { ok: true, result: { rel_keys: rel_keys } }
65
59
  end
66
60
  end
@@ -43,8 +43,8 @@ module RedisQueuedLocks::Acquier
43
43
  # Lifetime of the acuier's lock request. In seconds.
44
44
  # @option timeout [Integer]
45
45
  # Time period whe should try to acquire the lock (in seconds).
46
- # @option retry_count [Integer]
47
- # How many times we should try to acquire a lock.
46
+ # @option retry_count [Integer,NilClass]
47
+ # How many times we should try to acquire a lock. Nil means "infinite retries".
48
48
  # @option retry_delay [Integer]
49
49
  # A time-interval between the each retry (in milliseconds).
50
50
  # @option retry_jitter [Integer]
@@ -113,7 +113,7 @@ module RedisQueuedLocks::Acquier
113
113
  ) => { ok:, result: }
114
114
 
115
115
  acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
116
- acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil
116
+ acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil(2)
117
117
 
118
118
  # Step X: save the intermediate results to the result observer
119
119
  acq_process[:result] = result
@@ -139,20 +139,20 @@ module RedisQueuedLocks::Acquier
139
139
  acq_process[:acquired] = true
140
140
  acq_process[:should_try] = false
141
141
  acq_process[:acq_time] = acq_time
142
+ acq_process[:acq_end_time] = acq_end_time
142
143
  else
143
144
  # Step 2.1.b: failed acquirement => retry
144
145
  acq_process[:tries] += 1
145
146
 
146
- if acq_process[:tries] >= retry_count
147
+ if retry_count != nil && acq_process[:tries] >= retry_count
147
148
  # NOTE: reached the retry limit => quit from the loop
148
149
  acq_process[:should_try] = false
149
150
  # NOTE: reached the retry limit => dequeue from the lock queue
150
151
  acq_dequeue.call
151
- else
152
+ elsif delay_execution(retry_delay, retry_jitter)
152
153
  # NOTE:
153
154
  # delay the exceution in order to prevent chaotic attempts
154
155
  # and to allow other processes and threads to obtain the lock too.
155
- delay_execution(retry_delay, retry_jitter)
156
156
  end
157
157
  end
158
158
  end
@@ -163,23 +163,24 @@ module RedisQueuedLocks::Acquier
163
163
  # Step 3.a: acquired successfully => run logic or return the result of acquirement
164
164
  if block_given?
165
165
  begin
166
- yield_with_expire(redis, lock_key, instrumenter, &block)
166
+ yield_with_expire(redis, lock_key, &block)
167
167
  ensure
168
168
  acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
169
- acq_process[:hold_time] = ((
170
- acq_process[:rel_time] - acq_process[:acq_time]
171
- ) * 1000).ceil
169
+ acq_process[:hold_time] = (
170
+ (acq_process[:rel_time] - acq_process[:acq_end_time]) * 1000
171
+ ).ceil(2)
172
172
 
173
173
  # Step X (instrumentation): lock_hold_and_release
174
- instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
175
- hold_time: acq_process[:hold_time],
176
- rel_time: acq_process[:rel_time],
177
- ttl: acq_process[:lock_info][:ttl],
178
- acq_id: acq_process[:lock_info][:acq_id],
179
- ts: acq_process[:lock_info][:ts],
180
- lock_key: acq_process[:lock_info][:lock_key],
181
- acq_time: acq_process[:acq_time]
182
- })
174
+ run_non_critical do
175
+ instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
176
+ hold_time: acq_process[:hold_time],
177
+ ttl: acq_process[:lock_info][:ttl],
178
+ acq_id: acq_process[:lock_info][:acq_id],
179
+ ts: acq_process[:lock_info][:ts],
180
+ lock_key: acq_process[:lock_info][:lock_key],
181
+ acq_time: acq_process[:acq_time]
182
+ })
183
+ end
183
184
  end
184
185
  else
185
186
  { ok: true, result: acq_process[:lock_info] }
@@ -211,17 +212,19 @@ module RedisQueuedLocks::Acquier
211
212
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
212
213
 
213
214
  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
214
- result = fully_release_lock(redis, lock_key, lock_key_queue)
215
+ fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
215
216
  time_at = Time.now.to_i
216
217
  rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
217
- rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
218
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
218
219
 
219
- instrumenter.notify('redis_queued_locks.explicit_lock_release', {
220
- lock_key: lock_key,
221
- lock_key_queue: lock_key_queue,
222
- rel_time: rel_time,
223
- at: time_at
224
- })
220
+ run_non_critical do
221
+ instrumenter.notify('redis_queued_locks.explicit_lock_release', {
222
+ lock_key: lock_key,
223
+ lock_key_queue: lock_key_queue,
224
+ rel_time: rel_time,
225
+ at: time_at
226
+ })
227
+ end
225
228
 
226
229
  { ok: true, result: result }
227
230
  end
@@ -239,17 +242,18 @@ module RedisQueuedLocks::Acquier
239
242
  # @since 0.1.0
240
243
  def release_all_locks!(redis, batch_size, instrumenter)
241
244
  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
242
- result = fully_release_all_locks(redis, batch_size)
245
+ fully_release_all_locks(redis, batch_size) => { ok:, result: }
243
246
  time_at = Time.now.to_i
244
247
  rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
245
- rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
248
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
246
249
 
247
- instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
248
- at: time_at,
249
- rel_time: rel_time,
250
- rel_lock_cnt: result[:rel_lock_cnt],
251
- rel_queue_cnt: result[:rel_queue_cnt]
252
- })
250
+ run_non_critical do
251
+ instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
252
+ at: time_at,
253
+ rel_time: rel_time,
254
+ rel_keys: result[:rel_keys]
255
+ })
256
+ end
253
257
 
254
258
  { ok: true, result: result }
255
259
  end
@@ -279,6 +283,15 @@ module RedisQueuedLocks::Acquier
279
283
  ERROR_MESSAGE
280
284
  end
281
285
  end
286
+
287
+ # @param block [Block]
288
+ # @return [Any]
289
+ #
290
+ # @api private
291
+ # @since 0.1.0
292
+ def run_non_critical(&block)
293
+ yield rescue nil
294
+ end
282
295
  end
283
296
  # rubocop:enable Metrics/ClassLength
284
297
  end
@@ -12,15 +12,15 @@ class RedisQueuedLocks::Client
12
12
  setting :retry_jitter, 50 # NOTE: milliseconds
13
13
  setting :default_timeout, 10 # NOTE: seconds
14
14
  setting :exp_precision, 1 # NOTE: milliseconds
15
- setting :default_lock_ttl, 10_000 # NOTE: milliseconds
16
- setting :default_queue_ttl, 5 # NOTE: seconds
15
+ setting :default_lock_ttl, 5_000 # NOTE: milliseconds
16
+ setting :default_queue_ttl, 30 # NOTE: seconds
17
17
  setting :lock_release_batch_size, 100
18
18
  setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
19
19
 
20
20
  # TODO: setting :logger, Logger.new(IO::NULL)
21
21
  # TODO: setting :debug, true/false
22
22
 
23
- validate('retry_count', :integer)
23
+ validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
24
24
  validate('retry_delay', :integer)
25
25
  validate('retry_jitter', :integer)
26
26
  validate('default_timeout', :integer)
@@ -28,7 +28,7 @@ class RedisQueuedLocks::Client
28
28
  validate('default_lock_tt', :integer)
29
29
  validate('default_queue_ttl', :integer)
30
30
  validate('lock_release_batch_size', :integer)
31
- validate('instrumenter') { |instr| RedisQueuedLocks::Instrument.valid_interface?(instr) }
31
+ validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
32
32
  end
33
33
 
34
34
  # @return [RedisClient]
@@ -63,8 +63,8 @@ class RedisQueuedLocks::Client
63
63
  # Lifetime of the acuier's lock request. In seconds.
64
64
  # @option timeout [Integer,NilClass]
65
65
  # Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
66
- # @option retry_count [Integer]
67
- # How many times we should try to acquire a lock.
66
+ # @option retry_count [Integer,NilClass]
67
+ # How many times we should try to acquire a lock. Nil means "infinite retries".
68
68
  # @option retry_delay [Integer]
69
69
  # A time-interval between the each retry (in milliseconds).
70
70
  # @option retry_jitter [Integer]
@@ -4,6 +4,6 @@ module RedisQueuedLocks
4
4
  # @return [String]
5
5
  #
6
6
  # @api public
7
- # @since 0.0.2
8
- VERSION = '0.0.2'
7
+ # @since 0.0.3
8
+ VERSION = '0.0.3'
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov