redis_queued_locks 0.0.4 → 0.0.5

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: 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