redis_queued_locks 0.0.18 → 0.0.20

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: '0993079d1a7cca97094c741d65c4ef1e8a5694184212b6597960e0b3c91d83cb'
4
- data.tar.gz: 22535e254fa22db6bfb0225f391e3d4606ad680bb401034fcbdfcd8e93a1fc5c
3
+ metadata.gz: 281c476c0b1318a3f061e0d22031de92983f4ebd2277bc7e024b2baf998ae559
4
+ data.tar.gz: 5547ace054290da08fb1eb26e965e2d0ff68dfc9b5c3df0adb8a77069543b8ef
5
5
  SHA512:
6
- metadata.gz: 7db77251aefc59a83a9727d756774f5eb6b30a156fd058d69ec5170d252cc8fb875b2f213da2e15a4bdf9f5565565aa5ca699545ac0a83dcbc333fbed8c7aec1
7
- data.tar.gz: 659c47a6aaa421ade9c7ffaea000a7133c1ba72f5ab0a2e66bc56f5ad72f84f4d9525fc86024e5d52b8196a6dfc7b0a714100462915a91405c97c3b3d77d3e26
6
+ metadata.gz: a971c732be6ef918ae6e9fc70e4456a076b36a50622cda4e0f2b4d5d3f0cb9479e768852e391b02a6420d3de682ccca7de374cf1410b25be37a5d8cf43a4865c
7
+ data.tar.gz: ef66b70b7ef28be2b9b2422a94dc89e2fff350ad5d8ce58806f28c7742fa4a3caa5032f64d4817abd52dc4bc2ae77327dcfee79b28f462241c63fb2ddaf992eb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.20] - 2024-03-14
4
+ ### Added
5
+ - An ability to provide custom metadata to `lock` and `lock!` methods that will be passed
6
+ to the instrumentation level inside the `payload` parameter with `:meta` key;
7
+
8
+ ## [0.0.19] - 2024-03-12
9
+ ### Added
10
+ - An ability to set the invocation time period to the block of code invoked under
11
+ the obtained lock;
12
+
3
13
  ## [0.0.18] - 2024-03-04
4
14
  ### Changed
5
15
  - Semantic results for methods returning `{ ok: true/false, result: Any }` hash objects.
data/README.md CHANGED
@@ -36,7 +36,9 @@ Each lock request is put into the request queue and processed in order of their
36
36
 
37
37
  ### Algorithm
38
38
 
39
- - soon
39
+ > Each lock request is put into the request queue and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.
40
+
41
+ **Soon**: detailed explanation.
40
42
 
41
43
  ---
42
44
 
@@ -167,12 +169,14 @@ def lock(
167
169
  ttl: config[:default_lock_ttl],
168
170
  queue_ttl: config[:default_queue_ttl],
169
171
  timeout: config[:try_to_lock_timeout],
172
+ timed: false,
170
173
  retry_count: config[:retry_count],
171
174
  retry_delay: config[:retry_delay],
172
175
  retry_jitter: config[:retry_jitter],
173
176
  raise_errors: false,
174
177
  fail_fast: false,
175
178
  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
179
+ metadata: nil,
176
180
  &block
177
181
  )
178
182
  ```
@@ -185,6 +189,8 @@ def lock(
185
189
  - Lifetime of the acuier's lock request. In seconds.
186
190
  - `timeout` - `[Integer,NilClass]`
187
191
  - Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
192
+ - `timed` - `[Boolean]`
193
+ - Limit the invocation time period of the passed block of code by the lock's TTL.
188
194
  - `retry_count` - `[Integer,NilClass]`
189
195
  - How many times we should try to acquire a lock. Nil means "infinite retries".
190
196
  - `retry_delay` - `[Integer]`
@@ -206,6 +212,8 @@ def lock(
206
212
  pods or/and nodes of your application;
207
213
  - It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
208
214
  ivar (accessed via `uniq_dentity` accessor method);
215
+ - `metadata` - `[NilClass,Any]`
216
+ - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
209
217
  - `block` - `[Block]`
210
218
  - A block of code that should be executed after the successfully acquired lock.
211
219
  - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
@@ -259,6 +267,7 @@ def lock!(
259
267
  retry_jitter: config[:retry_jitter],
260
268
  identity: uniq_identity,
261
269
  fail_fast: false,
270
+ metadata: nil,
262
271
  &block
263
272
  )
264
273
  ```
@@ -533,6 +542,7 @@ Detalized event semantics and payload structure:
533
542
  - `:lock_key` - `string` - lock name;
534
543
  - `:ts` - `integer`/`epoch` - the time when the lock was obtaiend;
535
544
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
545
+ - `:meta` - `nil`/`Any` - custom metadata passed to the `lock`/`lock!` method;
536
546
  - `"redis_queued_locks.lock_hold_and_release"`
537
547
  - an event signalizes about the "hold+and+release" process
538
548
  when the lock obtained and hold by the block of logic;
@@ -543,6 +553,7 @@ Detalized event semantics and payload structure:
543
553
  - `:lock_key` - `string` - lock name;
544
554
  - `:ts` - `integer`/`epoch` - the time when lock was obtained;
545
555
  - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
556
+ - `:meta` - `nil`/`Any` - custom metadata passed to the `lock`/`lock!` method;
546
557
  - `"redis_queued_locks.explicit_lock_release"`
547
558
  - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
548
559
  - payload:
@@ -568,6 +579,7 @@ Detalized event semantics and payload structure:
568
579
  the acquired lock for long-running blocks of code (that invoked "under" the lock
569
580
  whose ttl may expire before the block execution completes);
570
581
  - an ability to add custom metadata to the lock and an ability to read this data;
582
+ - lock prioritization;
571
583
  - **Minor**
572
584
  - GitHub Actions CI;
573
585
  - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module RedisQueuedLocks::Acquier::YieldExpire
6
+ # @param redis [RedisClient] Redis connection manager.
7
+ # @param lock_key [String] Lock key to be expired.
8
+ # @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
9
+ # @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
10
+ # @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
11
+ # @param block [Block] Custom logic that should be invoked unter the obtained lock.
12
+ # @return [Any,NilClass] nil is returned no block parametr is provided.
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ def yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
17
+ if block_given?
18
+ if timed && ttl != nil
19
+ timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
20
+ yield_with_timeout(timeout, lock_key, ttl, &block)
21
+ else
22
+ yield
23
+ end
24
+ end
25
+ ensure
26
+ redis.call('EXPIRE', lock_key, '0')
27
+ end
28
+
29
+ private
30
+
31
+ # @param timeout [Float]
32
+ # @parma lock_key [String]
33
+ # @param lock_ttl [Integer,NilClass]
34
+ # @param block [Blcok]
35
+ # @return [Any]
36
+ #
37
+ # @api private
38
+ # @since 0.1.0
39
+ def yield_with_timeout(timeout, lock_key, lock_ttl, &block)
40
+ ::Timeout.timeout(timeout, &block)
41
+ rescue ::Timeout::Error
42
+ raise(RedisQueuedLocks::TimedLockTimeoutError, <<~ERROR_MESSAGE)
43
+ Passed <timed> block of code exceeded the lock TTL (lock: "#{lock_key}", ttl: #{lock_ttl})
44
+ ERROR_MESSAGE
45
+ end
46
+ end
@@ -6,7 +6,7 @@
6
6
  module RedisQueuedLocks::Acquier
7
7
  require_relative 'acquier/try'
8
8
  require_relative 'acquier/delay'
9
- require_relative 'acquier/expire'
9
+ require_relative 'acquier/yield_expire'
10
10
  require_relative 'acquier/release'
11
11
 
12
12
  # @since 0.1.0
@@ -14,7 +14,7 @@ module RedisQueuedLocks::Acquier
14
14
  # @since 0.1.0
15
15
  extend Delay
16
16
  # @since 0.1.0
17
- extend Expire
17
+ extend YieldExpire
18
18
  # @since 0.1.0
19
19
  extend Release
20
20
 
@@ -44,6 +44,8 @@ module RedisQueuedLocks::Acquier
44
44
  # Lifetime of the acuier's lock request. In seconds.
45
45
  # @option timeout [Integer]
46
46
  # Time period whe should try to acquire the lock (in seconds).
47
+ # @option timed [Boolean]
48
+ # Limit the invocation time period of the passed block of code by the lock's TTL.
47
49
  # @option retry_count [Integer,NilClass]
48
50
  # How many times we should try to acquire a lock. Nil means "infinite retries".
49
51
  # @option retry_delay [Integer]
@@ -61,6 +63,8 @@ module RedisQueuedLocks::Acquier
61
63
  # @option fail_fast [Boolean]
62
64
  # Should the required lock to be checked before the try and exit immidetly if lock is
63
65
  # already obtained.
66
+ # @option metadata [NilClass,Any]
67
+ # - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
64
68
  # @param [Block]
65
69
  # A block of code that should be executed after the successfully acquired lock.
66
70
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
@@ -80,6 +84,7 @@ module RedisQueuedLocks::Acquier
80
84
  ttl:,
81
85
  queue_ttl:,
82
86
  timeout:,
87
+ timed:,
83
88
  retry_count:,
84
89
  retry_delay:,
85
90
  retry_jitter:,
@@ -87,6 +92,7 @@ module RedisQueuedLocks::Acquier
87
92
  instrumenter:,
88
93
  identity:,
89
94
  fail_fast:,
95
+ metadata:,
90
96
  &block
91
97
  )
92
98
  # Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
@@ -119,7 +125,7 @@ module RedisQueuedLocks::Acquier
119
125
  acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
120
126
 
121
127
  # Step 2: try to lock with timeout
122
- with_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
128
+ with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
123
129
  acq_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
124
130
 
125
131
  # Step 2.1: caclically try to obtain the lock
@@ -140,6 +146,7 @@ module RedisQueuedLocks::Acquier
140
146
 
141
147
  # Step X: save the intermediate results to the result observer
142
148
  acq_process[:result] = result
149
+ acq_process[:acq_end_time] = acq_end_time
143
150
 
144
151
  # Step 2.1: analyze an acquirement attempt
145
152
  if ok
@@ -149,7 +156,8 @@ module RedisQueuedLocks::Acquier
149
156
  ttl: result[:ttl],
150
157
  acq_id: result[:acq_id],
151
158
  ts: result[:ts],
152
- acq_time: acq_time
159
+ acq_time: acq_time,
160
+ meta: metadata
153
161
  })
154
162
 
155
163
  # Step 2.1.a: successfully acquired => build the result
@@ -212,7 +220,9 @@ module RedisQueuedLocks::Acquier
212
220
  # Step 3.a: acquired successfully => run logic or return the result of acquirement
213
221
  if block_given?
214
222
  begin
215
- yield_with_expire(redis, lock_key, &block)
223
+ yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
224
+ ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
225
+ yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
216
226
  ensure
217
227
  acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
218
228
  acq_process[:hold_time] = (
@@ -227,7 +237,8 @@ module RedisQueuedLocks::Acquier
227
237
  acq_id: acq_process[:lock_info][:acq_id],
228
238
  ts: acq_process[:lock_info][:ts],
229
239
  lock_key: acq_process[:lock_info][:lock_key],
230
- acq_time: acq_process[:acq_time]
240
+ acq_time: acq_process[:acq_time],
241
+ meta: metadata
231
242
  })
232
243
  end
233
244
  end
@@ -526,7 +537,7 @@ module RedisQueuedLocks::Acquier
526
537
  #
527
538
  # @api private
528
539
  # @since 0.1.0
529
- def with_timeout(timeout, lock_key, raise_errors, on_timeout: nil, &block)
540
+ def with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: nil, &block)
530
541
  ::Timeout.timeout(timeout, &block)
531
542
  rescue ::Timeout::Error
532
543
  on_timeout.call unless on_timeout == nil
@@ -71,6 +71,8 @@ class RedisQueuedLocks::Client
71
71
  # Lifetime of the acuier's lock request. In seconds.
72
72
  # @option timeout [Integer,NilClass]
73
73
  # Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
74
+ # @option timed [Boolean]
75
+ # Limit the invocation time period of the passed block of code by the lock's TTL.
74
76
  # @option retry_count [Integer,NilClass]
75
77
  # How many times we should try to acquire a lock. Nil means "infinite retries".
76
78
  # @option retry_delay [Integer]
@@ -90,6 +92,8 @@ class RedisQueuedLocks::Client
90
92
  # already obtained;
91
93
  # - Should the logic exit immidietly after the first try if the lock was obtained
92
94
  # by another process while the lock request queue was initially empty;
95
+ # @option metadata [NilClass,Any]
96
+ # - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
93
97
  # @param block [Block]
94
98
  # A block of code that should be executed after the successfully acquired lock.
95
99
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
@@ -103,12 +107,14 @@ class RedisQueuedLocks::Client
103
107
  ttl: config[:default_lock_ttl],
104
108
  queue_ttl: config[:default_queue_ttl],
105
109
  timeout: config[:try_to_lock_timeout],
110
+ timed: false,
106
111
  retry_count: config[:retry_count],
107
112
  retry_delay: config[:retry_delay],
108
113
  retry_jitter: config[:retry_jitter],
109
114
  raise_errors: false,
110
115
  fail_fast: false,
111
116
  identity: uniq_identity,
117
+ metadata: nil,
112
118
  &block
113
119
  )
114
120
  RedisQueuedLocks::Acquier.acquire_lock!(
@@ -121,6 +127,7 @@ class RedisQueuedLocks::Client
121
127
  ttl:,
122
128
  queue_ttl:,
123
129
  timeout:,
130
+ timed:,
124
131
  retry_count:,
125
132
  retry_delay:,
126
133
  retry_jitter:,
@@ -128,6 +135,7 @@ class RedisQueuedLocks::Client
128
135
  instrumenter: config[:instrumenter],
129
136
  identity:,
130
137
  fail_fast:,
138
+ metadata:,
131
139
  &block
132
140
  )
133
141
  end
@@ -141,11 +149,13 @@ class RedisQueuedLocks::Client
141
149
  ttl: config[:default_lock_ttl],
142
150
  queue_ttl: config[:default_queue_ttl],
143
151
  timeout: config[:try_to_lock_timeout],
152
+ timed: false,
144
153
  retry_count: config[:retry_count],
145
154
  retry_delay: config[:retry_delay],
146
155
  retry_jitter: config[:retry_jitter],
147
156
  fail_fast: false,
148
157
  identity: uniq_identity,
158
+ metadata: nil,
149
159
  &block
150
160
  )
151
161
  lock(
@@ -153,12 +163,14 @@ class RedisQueuedLocks::Client
153
163
  ttl:,
154
164
  queue_ttl:,
155
165
  timeout:,
166
+ timed:,
156
167
  retry_count:,
157
168
  retry_delay:,
158
169
  retry_jitter:,
159
170
  raise_errors: true,
160
171
  identity:,
161
172
  fail_fast:,
173
+ metadata:,
162
174
  &block
163
175
  )
164
176
  end
@@ -20,4 +20,8 @@ module RedisQueuedLocks
20
20
  # @api public
21
21
  # @since 0.1.0
22
22
  LockAcquiermentRetryLimitError = Class.new(Error)
23
+
24
+ # @api pulic
25
+ # @since 0.1.0
26
+ TimedLockTimeoutError = Class.new(Error)
23
27
  end
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.18
9
- VERSION = '0.0.18'
8
+ # @version 0.0.20
9
+ VERSION = '0.0.20'
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.18
4
+ version: 0.0.20
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-04 00:00:00.000000000 Z
11
+ date: 2024-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -57,9 +57,9 @@ files:
57
57
  - lib/redis_queued_locks.rb
58
58
  - lib/redis_queued_locks/acquier.rb
59
59
  - lib/redis_queued_locks/acquier/delay.rb
60
- - lib/redis_queued_locks/acquier/expire.rb
61
60
  - lib/redis_queued_locks/acquier/release.rb
62
61
  - lib/redis_queued_locks/acquier/try.rb
62
+ - lib/redis_queued_locks/acquier/yield_expire.rb
63
63
  - lib/redis_queued_locks/client.rb
64
64
  - lib/redis_queued_locks/data.rb
65
65
  - lib/redis_queued_locks/debugger.rb
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- # @since 0.1.0
5
- module RedisQueuedLocks::Acquier::Expire
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
- #
11
- # @api private
12
- # @since 0.1.0
13
- def yield_with_expire(redis, lock_key, &block)
14
- yield if block_given?
15
- ensure
16
- redis.call('EXPIRE', lock_key, '0')
17
- end
18
- end