redis_queued_locks 0.0.4 → 0.0.5

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: 6e1e1a54762132f92451d595938cb467b7b644dffc0817abe99209e1f92160f8
4
- data.tar.gz: 297eec3d7fcee35d12e7f3509e8e3dfd4b8b3c52889c7570a79cd6a30f45eda4
3
+ metadata.gz: 1044ea4b8236b241132017ea9b9f2f8ac280c584be1158a9e7b52b15aa58d4ef
4
+ data.tar.gz: 5e6dc2141b05055f3641d88c9c181521ed6ad94100f6007573628e6aef7cee17
5
5
  SHA512:
6
- metadata.gz: 6d2c64622bd3b8465a749b5f6a8050d5fd1d5b623990f91421f91004c444baab12114964ded28d5ac0fa43fed56f3e893798feb31b00ab173d536e57fcfdfaa4
7
- data.tar.gz: 02ee9d8742ffd3a7b16b8c23b8d4c4f58a8cb1b9db4d215ece4bf34de0c79830bd3417186b2d75dbbd48f6845d736361af7503f641e7faa16d139cbe193263d3
6
+ metadata.gz: 8d8c0ac83545d663f0d558dc258364348a100e34c9636bf9663d8c021154c38c660ee626019f08bc5607c4ce01fc731a49c1fae3f5dc6e28d3a5d2ea41292d02
7
+ data.tar.gz: 1bbc2cfc740ff6eb3fa6e458dc5ed9dd4509885427eaf7e5eb8c2e74a605652dc9c39586d7282442befb1f199d0b84ff3ac573dfa43bfcc86bcb117f165e70ca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.5] - 2024-02-26
4
+ - Minor update with documentation and configuration updates inside.
5
+
3
6
  ## [0.0.4] - 2024-02-26
4
7
  ### Changed
5
8
  - changed default configuration values of `RedisQueuedLocks::Client` config;
data/README.md CHANGED
@@ -1,8 +1,75 @@
1
1
  # RedisQueuedLocks
2
2
 
3
3
  Distributed lock implementation with "lock acquisition queue" capabilities based on the Redis Database.
4
+ Each lock request is put into a request queue and processed in order of their priority (FIFO).
4
5
 
5
- ## Configuration
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ - [Algorithm](#algorithm)
11
+ - [Installation](#installation)
12
+ - [Setup](#setup)
13
+ - [Configuration](#configuration)
14
+ - [Usage](#usage)
15
+ - [lock](#lock---obtain-a-lock)
16
+ - [lock!](#lock---exeptional-lock-obtaining)
17
+ - [unlock](#unlock---release-a-lock)
18
+ - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
19
+ - [Instrumentation](#instrumentation)
20
+ - [Instrumentation Events](#instrumentation-events)
21
+ - [TODO](#todo)
22
+ - [Contributing](#contributing)
23
+ - [License](#license)
24
+ - [Authors](#authors)
25
+
26
+ ---
27
+
28
+ ### Algorithm
29
+
30
+ - soon;
31
+
32
+ ---
33
+
34
+ ### Installation
35
+
36
+ ```ruby
37
+ gem 'redis_queued_locks'
38
+ ```
39
+
40
+ ```shell
41
+ bundle install
42
+ # --- or ---
43
+ gem install redis_queued_locks
44
+ ```
45
+
46
+ ```ruby
47
+ require 'redis_queued_locks'
48
+ ```
49
+
50
+ ---
51
+
52
+ ### Setup
53
+
54
+ ```ruby
55
+ require 'redis_queued_locks'
56
+
57
+ # Step 1: initialize RedisClient instance
58
+ redis_clinet = RedisClient.config.new_pool # NOTE: provide your ounw RedisClient instance
59
+
60
+ # Step 2: initialize RedisQueuedLock::Client instance
61
+ rq_lock_client = RedisQueuedLocks::Client.new(redis_client) do |config|
62
+ # NOTE:
63
+ # - some your application-related configs;
64
+ # - for documentation see <Configuration> section in readme;
65
+ end
66
+
67
+ # Step 3: start to work with locks :)
68
+ ```
69
+
70
+ ---
71
+
72
+ ### Configuration
6
73
 
7
74
  ```ruby
8
75
  redis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance
@@ -49,21 +116,173 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
49
116
  end
50
117
  ```
51
118
 
52
- ## Usage
119
+ ---
120
+
121
+ ### Usage
122
+
123
+ - [lock](#lock---obtain-a-lock)
124
+ - [lock!](#lock---exeptional-lock-obtaining)
125
+ - [unlock](#unlock---release-a-lock)
126
+ - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)
127
+
128
+ ---
129
+
130
+ #### lock - obtain a lock
53
131
 
54
132
  ```ruby
55
- redis_clinet = RedisClient.config.new_pool # NOTE: provide your ounw RedisClient instance
56
- rq_lock = RedisQueuedLocks::Client.new(redis_client) do |config|
57
- # NOTE: some your application-related configs
58
- end
133
+ def lock(
134
+ lock_name,
135
+ process_id: RedisQueuedLocks::Resource.get_process_id,
136
+ thread_id: RedisQueuedLocks::Resource.get_thread_id,
137
+ ttl: config[:default_lock_ttl],
138
+ queue_ttl: config[:default_queue_ttl],
139
+ timeout: config[:try_to_lock_timeout],
140
+ retry_count: config[:retry_count],
141
+ retry_delay: config[:retry_delay],
142
+ retry_jitter: config[:retry_jitter],
143
+ raise_errors: false,
144
+ &block
145
+ )
146
+ ```
147
+
148
+ - `lock_name` - `[String]`
149
+ - Lock name to be obtained.
150
+ - `process_id` - `[Integer,String]`
151
+ - The process that want to acquire the lock.
152
+ - `thread_id` - `[Integer,String]`
153
+ - The process's thread that want to acquire the lock.
154
+ - `ttl` [Integer]
155
+ - Lock's time to live (in milliseconds).
156
+ - `queue_ttl` - `[Integer]`
157
+ - Lifetime of the acuier's lock request. In seconds.
158
+ - `timeout` - `[Integer,NilClass]`
159
+ - Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
160
+ - `retry_count` - `[Integer,NilClass]`
161
+ - How many times we should try to acquire a lock. Nil means "infinite retries".
162
+ - `retry_delay` - `[Integer]`
163
+ - A time-interval between the each retry (in milliseconds).
164
+ - `retry_jitter` - `[Integer]`
165
+ - Time-shift range for retry-delay (in milliseconds).
166
+ - `instrumenter` - `[#notify]`
167
+ - See RedisQueuedLocks::Instrument::ActiveSupport for example.
168
+ - `raise_errors` - `[Boolean]`
169
+ - Raise errors on library-related limits such as timeout or failed lock obtain.
170
+ - `block` - `[Block]`
171
+ - A block of code that should be executed after the successfully acquired lock.
172
+
173
+ Return value:
174
+ - `[Hash<Symbol,Any>]` Format: `{ ok: true/false, result: Symbol/Hash }`;
175
+ - for successful lock obtaining:
176
+ ```ruby
177
+ {
178
+ ok: true,
179
+ result: {
180
+ lock_key: String, # acquierd lock key ("rql:lock:your_lock_name")
181
+ acq_id: String, # acquier identifier ("your_process_id/your_thread_id")
182
+ ts: Integer, # time (epoch) when lock was obtained (integer)
183
+ ttl: Integer # lock's time to live in milliseconds (integer)
184
+ }
185
+ }
186
+ ```
187
+ - for failed lock obtaining:
188
+ ```ruby
189
+ { ok: false, result: :acquier_is_not_first_in_queue }
190
+ { ok: false, result: :lock_is_still_acquired }
191
+ { ok: false, result: :lock_is_acquired_during_acquire_race }
192
+ { ok: false, result: :unknown }
193
+ ```
194
+
195
+
196
+ ---
197
+
198
+ #### #lock! - exceptional lock obtaining
199
+
200
+ - fails when:
201
+ - `timeout` limit reached before lock is obtained;
202
+ - `retry_count` limit reached before lock is obtained;
203
+
204
+ ```ruby
205
+ def lock!(
206
+ lock_name,
207
+ process_id: RedisQueuedLocks::Resource.get_process_id,
208
+ thread_id: RedisQueuedLocks::Resource.get_thread_id,
209
+ ttl: config[:default_lock_ttl],
210
+ queue_ttl: config[:default_queue_ttl],
211
+ timeout: config[:default_timeout],
212
+ retry_count: config[:retry_count],
213
+ retry_delay: config[:retry_delay],
214
+ retry_jitter: config[:retry_jitter],
215
+ &block
216
+ )
217
+ ```
218
+
219
+ See `#lock` method [documentation](#lock---obtain-a-lock).
220
+
221
+ ---
222
+
223
+ #### #unlock - release a lock
224
+
225
+ - release the concrete lock with lock request queue;
226
+ - queue will be relased first;
227
+
228
+ ```ruby
229
+ def unlock(lock_name)
230
+ ```
231
+
232
+ - `lock_name` - `[String]`
233
+ - the lock name that should be released.
234
+
235
+ Return:
236
+ - `[Hash<Symbol,Any>]` - Format: `{ ok: true/false, result: Symbol/Hash }`;
237
+
238
+ ---
239
+
240
+ #### #clear_locks - release all locks and lock queues
241
+
242
+ - release all obtained locks and related lock request queues;
243
+ - queues will be released first;
244
+
245
+ ```ruby
246
+ def clear_locks(batch_size: config[:lock_release_batch_size])
59
247
  ```
60
248
 
61
- - `#lock`
62
- - `#lock!`
63
- - `#unlock`
64
- - `#clear_locks`
249
+ - `batch_size` - `[Integer]`
250
+ - batch of cleared locks and lock queus unde the one pipelined redis command;
251
+
252
+ Return:
253
+ - `[Hash<Symbol,Any>]` - Format: `{ ok: true/false, result: Symbol/Hash }`;
254
+
255
+ ---
256
+
257
+ ## Instrumentation
65
258
 
66
- ## Instrumentation events
259
+ An instrumentation layer is incapsulated in `instrumenter` object stored in configurations.
260
+
261
+ Instrumenter object should provide `notify(event, payload)` method with following signarue:
262
+
263
+ - `event` - `string`;
264
+ - `payload` - `hash<Symbol,Any>`;
265
+
266
+ `redis_queued_locks` provides two instrumenters:
267
+
268
+ - `RedisQueuedLocks::Instrument::ActiveSupport` - `ActiveSupport::Notifications` instrumenter
269
+ that instrument events via `ActiveSupport::Notifications` api;
270
+ - `RedisQueuedLocks::Instrument::VoidNotifier` - instrumenter that does nothing;
271
+
272
+ By default `RedisQueuedLocks::Client` is configured with the void notifier (which means "instrumentation is disabled").
273
+
274
+ ---
275
+
276
+ ### Instrumentation Events
277
+
278
+ List of instrumentation events
279
+
280
+ - **redis_queued_locks.lock_obtained**
281
+ - **redis_queued_locks.lock_hold_and_release**
282
+ - **redis_queued_locks.explicit_lock_release**
283
+ - **redis_queued_locks.explicit_all_locks_release**
284
+
285
+ Detalized event semantics and payload structure:
67
286
 
68
287
  - `"redis_queued_locks.lock_obtained"`
69
288
  - a moment when the lock was obtained;
@@ -96,3 +315,29 @@ end
96
315
  - `rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
97
316
  - `at` - `integer`/`epoch` - the time when the operation has ended;
98
317
  - `rel_keys` - `integer` - released redis keys count (`released queu keys` + `released lock keys`);
318
+
319
+ ---
320
+
321
+ ## TODO
322
+
323
+ - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
324
+ - `100%` test coverage;
325
+ - better code stylization and interesting refactorings :)
326
+
327
+ ---
328
+
329
+ ## Contributing
330
+
331
+ - Fork it ( https://github.com/0exp/redis_queued_locks )
332
+ - Create your feature branch (`git checkout -b feature/my-new-feature`)
333
+ - Commit your changes (`git commit -am '[feature_context] Add some feature'`)
334
+ - Push to the branch (`git push origin feature/my-new-feature`)
335
+ - Create new Pull Request
336
+
337
+ ## License
338
+
339
+ Released under MIT License.
340
+
341
+ ## Authors
342
+
343
+ [Rustam Ibragimov](https://github.com/0exp)
@@ -37,7 +37,6 @@ module RedisQueuedLocks::Acquier::Release
37
37
  RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
38
38
  count: batch_size
39
39
  ) do |lock_queue|
40
- puts "RELEASE (lock_queue): #{lock_queue}"
41
40
  pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
42
41
  pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), "0")
43
42
  end
@@ -48,7 +47,6 @@ module RedisQueuedLocks::Acquier::Release
48
47
  RedisQueuedLocks::Resource::LOCK_PATTERN,
49
48
  count: batch_size
50
49
  ) do |lock_key|
51
- puts "RELEASE (lock_key): #{lock_key}"
52
50
  pipeline.call('EXPIRE', lock_key, '0')
53
51
  end
54
52
  end
@@ -149,6 +149,11 @@ module RedisQueuedLocks::Acquier
149
149
  acq_process[:should_try] = false
150
150
  # NOTE: reached the retry limit => dequeue from the lock queue
151
151
  acq_dequeue.call
152
+ # NOTE: check and raise an error
153
+ raise(LockAcquiermentRetryLimitError, <<~ERROR_MESSAGE.strip) if raise_errors
154
+ Failed to acquire the lock "#{lock_key}"
155
+ for the given retry_count limit (#{retry_count} times).
156
+ ERROR_MESSAGE
152
157
  elsif delay_execution(retry_delay, retry_jitter)
153
158
  # NOTE:
154
159
  # delay the exceution in order to prevent chaotic attempts
@@ -52,7 +52,7 @@ class RedisQueuedLocks::Client
52
52
  end
53
53
 
54
54
  # @param lock_name [String]
55
- # Lock name to be acquier.
55
+ # Lock name to be obtained.
56
56
  # @option process_id [Integer,String]
57
57
  # The process that want to acquire the lock.
58
58
  # @option thread_id [Integer,String]
@@ -73,7 +73,7 @@ class RedisQueuedLocks::Client
73
73
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
74
74
  # @option raise_errors [Boolean]
75
75
  # Raise errors on library-related limits such as timeout or failed lock obtain.
76
- # @param [Block]
76
+ # @param block [Block]
77
77
  # A block of code that should be executed after the successfully acquired lock.
78
78
  # @return [Hash<Symbol,Any>]
79
79
  # Format: { ok: true/false, result: Symbol/Hash }.
@@ -15,5 +15,5 @@ module RedisQueuedLocks
15
15
 
16
16
  # @api public
17
17
  # @since 0.1.0
18
- LockAcquiermentLimitError = Class.new(Error)
18
+ LockAcquiermentRetryLimitError = Class.new(Error)
19
19
  end
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.4
9
- VERSION = '0.0.4'
8
+ # @version 0.0.5
9
+ VERSION = '0.0.5'
10
10
  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.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov