redis_queued_locks 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f78a90d3830a3797bfd973b8ce48a879ba8400c3daec8827c0c14c25c650bb3
4
- data.tar.gz: a8e09fae141ff16d142234a4200c86f05abea787fb627e23b4187dd11fa06086
3
+ metadata.gz: e984603ea8f509f8e5f7f685c356d1f838b16bc950e1de83ff8f66e6afa83fcf
4
+ data.tar.gz: 1b2eee0f32c911caecf08214b2b404d620ffb10501ae72d7721235b64fc02362
5
5
  SHA512:
6
- metadata.gz: fe28a66ec0d028453a0e43f2e140b907a100a70b8a547af8067f45f60392339dd9d0540ef9b8638d0bc9588dd0152f4e21ac488d031170cb872679c7ed4bc34a
7
- data.tar.gz: 6608fc1f6a9b2b2000254ba0b4a39d5d16c50f9265bcf58c7b138066e08c29c32d31047e3419db09aa0bfda88c09af10111254c3bf2ba35df775248f2d732d63
6
+ metadata.gz: b7e2d574eeeff4a9a235f39ee25bde152880dcd12e7066196a8304e54098480ef05672edd89a6931ece225711b96602f23ce76304c555dbd623d30099e35ca0a
7
+ data.tar.gz: d7f5075bbf136e672ebb1632da5c6a78b2b72886bc1d5e8b0d9425cf38b14f5eef5a097fdc835c78f5f2d26885512279926a33bdbdb559151b875453d5312c9e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.2] - 2024-02-26
4
+ ### Added
5
+ - Instrumentation events:
6
+ - `"redis_queued_locks.lock_obtained"`;
7
+ - `"redis_queued_locks.lock_hold_and_release"`;
8
+ - `"redis_queued_locks.explicit_lock_release"`;
9
+ - `"redis_queued_locks.explicit_all_locks_release"`;
10
+
3
11
  ## [0.0.1] - 2024-02-26
4
12
 
5
13
  - Still the initial release version;
data/README.md CHANGED
@@ -5,13 +5,38 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
5
5
  ## Instrumentation events
6
6
 
7
7
  - `"redis_queued_locks.lock_obtained"`
8
- - the moment when the lock was obtained;
8
+ - a moment when the lock was obtained;
9
9
  - payload:
10
10
  - `ttl` - `integer`/`milliseconds` - lock ttl;
11
11
  - `acq_id` - `string` - lock acquier identifier;
12
- - `lock_key` - `string` - lock name ;
12
+ - `lock_key` - `string` - lock name;
13
13
  - `ts` - `integer`/`epoch` - the time when the lock was obtaiend;
14
14
  - `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
15
+ - `"redis_queued_locks.lock_hold_and_release"`
16
+ - an event signalizes about the "hold+and+release" process
17
+ when the lock obtained and hold by the block of logic;
18
+ - payload:
19
+ - `hold_time` - `float`/`milliseconds` - lock hold time;
20
+ - `ttl` - `integer`/`milliseconds` - lock ttl;
21
+ - `acq_id` - `string` - lock acquier identifier;
22
+ - `lock_key` - `string` - lock name;
23
+ - `ts` - `integer`/`epoch` - the time when lock was obtained;
24
+ - `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
25
+ - `rel_time` - `float`/`milliseconds` - time spent on lock "holding + releasing";
26
+ - `"redis_queued_locks.explicit_lock_release"`
27
+ - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
28
+ - payload:
29
+ - `at` - `integer`/`epoch` - the time when the lock was released;
30
+ - `rel_time` - `float`/`milliseconds` - time spent on lock releasing;
31
+ - `lock_key` - `string` - released lock (lock name);
32
+ - `lock_key_queue` - `string` - released lock queue (lock queue name);
33
+ - `"redis_queued_locks.explicit_all_locks_release"`
34
+ - an event signalizes about the explicit all locks release (invoked viaa `RedisQueuedLock#clear_locks`);
35
+ - payload:
36
+ - `rel_time` - `float`/`milliseconds` - time when the locks was released
37
+ - `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;
15
40
 
16
41
  ## Todo
17
42
 
@@ -8,32 +8,40 @@ module RedisQueuedLocks::Acquier::Release
8
8
  # @param redis [RedisClient]
9
9
  # @param lock_key [String]
10
10
  # @param lock_key_queue [String]
11
- # @return [void]
11
+ # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
12
12
  #
13
13
  # @api private
14
14
  # @since 0.1.0
15
15
  def fully_release_lock(redis, lock_key, lock_key_queue)
16
- redis.multi do |transact|
16
+ result = redis.multi do |transact|
17
17
  transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
18
18
  transact.call('EXPIRE', lock_key, '0')
19
19
  end
20
+
21
+ { ok: true, result: }
20
22
  end
21
23
 
22
24
  # Release all locks: clear all lock queus and expire all locks.
23
25
  #
24
26
  # @param redis [RedisClient]
25
27
  # @param batch_size [Integer]
26
- # @return [void]
28
+ # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
27
29
  #
28
30
  # @api private
29
31
  # @since 0.1.0
30
32
  def fully_release_all_locks(redis, batch_size)
33
+ rel_queue_cnt = 0
34
+ rel_lock_cnt = 0
35
+
31
36
  # Step A: release all queus and their related locks
32
37
  redis.scan(
33
38
  'MATCH',
34
39
  RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
35
40
  count: batch_size
36
41
  ) do |lock_queue|
42
+ rel_queue_cnt += 1
43
+ rel_lock_cnt += 1
44
+
37
45
  redis.pipelined do |pipeline|
38
46
  pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
39
47
  pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue))
@@ -42,6 +50,8 @@ module RedisQueuedLocks::Acquier::Release
42
50
 
43
51
  # Step B: release all locks
44
52
  redis.pipelined do |pipeline|
53
+ rel_lock_cnt += 1
54
+
45
55
  redis.scan(
46
56
  'MATCH',
47
57
  RedisQueuedLocks::Resource::LOCK_PATTERN,
@@ -50,5 +60,7 @@ module RedisQueuedLocks::Acquier::Release
50
60
  pipeline.call('EXPIRE', lock_key, '0')
51
61
  end
52
62
  end
63
+
64
+ { ok: true, result: { rel_queue_cnt:, rel_lock_cnt: } }
53
65
  end
54
66
  end
@@ -37,10 +37,10 @@ module RedisQueuedLocks::Acquier
37
37
  # The process that want to acquire the lock.
38
38
  # @option thread_id [Integer,String]
39
39
  # The process's thread that want to acquire the lock.
40
- # @option ttl [Integer]
41
- # Lock's time to live (in milliseconds).
40
+ # @option ttl [Integer,NilClass]
41
+ # Lock's time to live (in milliseconds). Nil means "without timeout".
42
42
  # @option queue_ttl [Integer]
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
46
  # @option retry_count [Integer]
@@ -90,7 +90,9 @@ module RedisQueuedLocks::Acquier
90
90
  tries: 0,
91
91
  acquired: false,
92
92
  result: nil,
93
- acq_time: nil # NOTE: in milliseconds
93
+ acq_time: nil, # NOTE: in milliseconds
94
+ hold_time: nil, # NOTE: in milliseconds
95
+ rel_time: nil # NOTE: in milliseconds
94
96
  }
95
97
  acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
96
98
 
@@ -118,7 +120,7 @@ module RedisQueuedLocks::Acquier
118
120
 
119
121
  # Step 2.1: analyze an acquirement attempt
120
122
  if ok
121
- # INSTRUMENT: lock obtained
123
+ # Step X (instrumentation): lock obtained
122
124
  instrumenter.notify('redis_queued_locks.lock_obtained', {
123
125
  lock_key: result[:lock_key],
124
126
  ttl: result[:ttl],
@@ -160,7 +162,25 @@ module RedisQueuedLocks::Acquier
160
162
  if acq_process[:acquired]
161
163
  # Step 3.a: acquired successfully => run logic or return the result of acquirement
162
164
  if block_given?
163
- yield_with_expire(redis, lock_key, &block) # INSTRUMENT: lock release
165
+ begin
166
+ yield_with_expire(redis, lock_key, instrumenter, &block)
167
+ ensure
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
172
+
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
+ })
183
+ end
164
184
  else
165
185
  { ok: true, result: acq_process[:lock_info] }
166
186
  end
@@ -181,16 +201,28 @@ module RedisQueuedLocks::Acquier
181
201
  #
182
202
  # @param redis [RedisClient] Redis connection client.
183
203
  # @param lock_name [String] The lock name that should be released.
204
+ # @param isntrumenter [#notify] See RedisQueuedLocks::Instrument::ActiveSupport for example.
184
205
  # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
185
206
  #
186
207
  # @api private
187
208
  # @since 0.1.0
188
- def release_lock!(redis, lock_name)
209
+ def release_lock!(redis, lock_name, instrumenter)
189
210
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
190
211
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
191
212
 
192
- # INSTRUMENT: lock release
213
+ rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
193
214
  result = fully_release_lock(redis, lock_key, lock_key_queue)
215
+ time_at = Time.now.to_i
216
+ rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
217
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
218
+
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
+ })
225
+
194
226
  { ok: true, result: result }
195
227
  end
196
228
 
@@ -200,13 +232,25 @@ module RedisQueuedLocks::Acquier
200
232
  #
201
233
  # @param redis [RedisClient] Redis connection client.
202
234
  # @param batch_size [Integer] The number of lock keys that should be released in a time.
235
+ # @param isntrumenter [#notify] See RedisQueuedLocks::Instrument::ActiveSupport for example.
203
236
  # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
204
237
  #
205
238
  # @api private
206
239
  # @since 0.1.0
207
- def release_all_locks!(redis, batch_size)
208
- # INSTRUMENT: all locks released
240
+ def release_all_locks!(redis, batch_size, instrumenter)
241
+ rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
209
242
  result = fully_release_all_locks(redis, batch_size)
243
+ time_at = Time.now.to_i
244
+ rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
245
+ rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
246
+
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
+ })
253
+
210
254
  { ok: true, result: result }
211
255
  end
212
256
 
@@ -60,9 +60,9 @@ class RedisQueuedLocks::Client
60
60
  # @option ttl [Integer]
61
61
  # Lock's time to live (in milliseconds).
62
62
  # @option queue_ttl [Integer]
63
- # ?
64
- # @option timeout [Integer]
65
- # Time period whe should try to acquire the lock (in seconds).
63
+ # Lifetime of the acuier's lock request. In seconds.
64
+ # @option timeout [Integer,NilClass]
65
+ # Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
66
66
  # @option retry_count [Integer]
67
67
  # How many times we should try to acquire a lock.
68
68
  # @option retry_delay [Integer]
@@ -147,14 +147,23 @@ class RedisQueuedLocks::Client
147
147
  # @api public
148
148
  # @since 0.1.0
149
149
  def unlock(lock_name)
150
- RedisQueuedLocks::Acquier.release_lock!(redis_client, lock_name)
150
+ RedisQueuedLocks::Acquier.release_lock!(
151
+ redis_client,
152
+ lock_name,
153
+ config[:instrumenter]
154
+ )
151
155
  end
152
156
 
157
+ # @option batch_size [Integer]
153
158
  # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol/Hash }.
154
159
  #
155
160
  # @api public
156
161
  # @since 0.1.0
157
162
  def clear_locks(batch_size: config[:lock_release_batch_size])
158
- RedisQueuedLocks::Acquier.release_all_locks!(redis_client, batch_size)
163
+ RedisQueuedLocks::Acquier.release_all_locks!(
164
+ redis_client,
165
+ batch_size,
166
+ config[:instrumenter]
167
+ )
159
168
  end
160
169
  end
@@ -4,6 +4,6 @@ module RedisQueuedLocks
4
4
  # @return [String]
5
5
  #
6
6
  # @api public
7
- # @since 0.0.1
8
- VERSION = '0.0.1'
7
+ # @since 0.0.2
8
+ VERSION = '0.0.2'
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.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov