redis_queued_locks 0.0.3 → 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: 5dbd11cefb9d18a816eb4c63fe1178f87325c30d75efc2bc806d29f41994fc2c
4
- data.tar.gz: 9697f4bf452aaaa7cade45320c50d16b510c657074ba97513c5f8de9c51f6c49
3
+ metadata.gz: 1044ea4b8236b241132017ea9b9f2f8ac280c584be1158a9e7b52b15aa58d4ef
4
+ data.tar.gz: 5e6dc2141b05055f3641d88c9c181521ed6ad94100f6007573628e6aef7cee17
5
5
  SHA512:
6
- metadata.gz: b28ef92375c7eef4d6c43fccb5e44105d653fb00e052edcf67316f8da13578ad7b39086b3799782592b38bf046760f8faa1238b92de293b83eb1e4a7c441413e
7
- data.tar.gz: dfd3891ae8d0303b88ac95268056addc3b4e03f862a5200166de9553b412eee78b77f42193f133f9dd340793b67928806e2e05451517585c2282fb7e4c871d1f
6
+ metadata.gz: 8d8c0ac83545d663f0d558dc258364348a100e34c9636bf9663d8c021154c38c660ee626019f08bc5607c4ce01fc731a49c1fae3f5dc6e28d3a5d2ea41292d02
7
+ data.tar.gz: 1bbc2cfc740ff6eb3fa6e458dc5ed9dd4509885427eaf7e5eb8c2e74a605652dc9c39586d7282442befb1f199d0b84ff3ac573dfa43bfcc86bcb117f165e70ca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.5] - 2024-02-26
4
+ - Minor update with documentation and configuration updates inside.
5
+
6
+ ## [0.0.4] - 2024-02-26
7
+ ### Changed
8
+ - changed default configuration values of `RedisQueuedLocks::Client` config;
9
+
3
10
  ## [0.0.3] - 2024-02-26
4
11
  ### Changed
5
12
  - Instrumentation events:
data/README.md CHANGED
@@ -1,8 +1,288 @@
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
- ## Instrumentation events
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
73
+
74
+ ```ruby
75
+ redis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance
76
+
77
+ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
78
+ # (default: 3) (supports nil)
79
+ # - nil means "infinite retries" and you are only limited by the "try_to_lock_timeout" config;
80
+ config.retry_count = 3
81
+
82
+ # (milliseconds) (default: 200)
83
+ config.retry_delay = 200
84
+
85
+ # (milliseconds) (default: 50)
86
+ config.retry_jitter = 50
87
+
88
+ # (seconds) (supports nil)
89
+ # - nil means "no timeout" and you are only limited by "retry_count" config;
90
+ config.try_to_lock_timeout = 10
91
+
92
+ # (milliseconds) (default: 1)
93
+ # - expiration precision. affects the ttl (ttl + precision);
94
+ config.exp_precision = 1
95
+
96
+ # (milliseconds) (default: 5_000)
97
+ # - lock's time to live
98
+ config.default_lock_ttl = 5_000
99
+
100
+ # (seconds) (default: 15)
101
+ # - lock request timeout. after this timeout your lock request in queue will be requeued;
102
+ config.default_queue_ttl = 15
103
+
104
+ # (default: 100)
105
+ # - how many items will be released at a time in RedisQueuedLocks::Client#clear_locks logic;
106
+ # - affects the performancs capabilites (redis, rubyvm);
107
+ config.lock_release_batch_size = 100
108
+
109
+ # (default: RedisQueuedLocks::Instrument::VoidNotifier)
110
+ # - instrumentation layer;
111
+ # - you can provde your own instrumenter with `#notify(event, payload = {})`
112
+ # - event: <string> requried;
113
+ # - payload: <hash> requried;
114
+ # - disabled by default via VoidNotifier;
115
+ config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport
116
+ end
117
+ ```
118
+
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
131
+
132
+ ```ruby
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])
247
+ ```
248
+
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
258
+
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:
6
286
 
7
287
  - `"redis_queued_locks.lock_obtained"`
8
288
  - a moment when the lock was obtained;
@@ -32,6 +312,32 @@ Distributed lock implementation with "lock acquisition queue" capabilities based
32
312
  - `"redis_queued_locks.explicit_all_locks_release"`
33
313
  - an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
34
314
  - payload:
35
- - `rel_time` - `float`/`milliseconds` - time spent on the lock release;
315
+ - `rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
36
316
  - `at` - `integer`/`epoch` - the time when the operation has ended;
37
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
@@ -10,10 +10,10 @@ class RedisQueuedLocks::Client
10
10
  setting :retry_count, 3
11
11
  setting :retry_delay, 200 # NOTE: milliseconds
12
12
  setting :retry_jitter, 50 # NOTE: milliseconds
13
- setting :default_timeout, 10 # NOTE: seconds
13
+ setting :try_to_lock_timeout, 10 # NOTE: seconds
14
14
  setting :exp_precision, 1 # NOTE: milliseconds
15
15
  setting :default_lock_ttl, 5_000 # NOTE: milliseconds
16
- setting :default_queue_ttl, 30 # NOTE: seconds
16
+ setting :default_queue_ttl, 15 # NOTE: seconds
17
17
  setting :lock_release_batch_size, 100
18
18
  setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
19
19
 
@@ -23,7 +23,7 @@ class RedisQueuedLocks::Client
23
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
- validate('default_timeout', :integer)
26
+ validate('try_to_lock_timeout') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
27
27
  validate('exp_precision', :integer)
28
28
  validate('default_lock_tt', :integer)
29
29
  validate('default_queue_ttl', :integer)
@@ -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 }.
@@ -86,7 +86,7 @@ class RedisQueuedLocks::Client
86
86
  thread_id: RedisQueuedLocks::Resource.get_thread_id,
87
87
  ttl: config[:default_lock_ttl],
88
88
  queue_ttl: config[:default_queue_ttl],
89
- timeout: config[:default_timeout],
89
+ timeout: config[:try_to_lock_timeout],
90
90
  retry_count: config[:retry_count],
91
91
  retry_delay: config[:retry_delay],
92
92
  retry_jitter: config[:retry_jitter],
@@ -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
@@ -4,6 +4,7 @@ module RedisQueuedLocks
4
4
  # @return [String]
5
5
  #
6
6
  # @api public
7
- # @since 0.0.3
8
- VERSION = '0.0.3'
7
+ # @since 0.0.1
8
+ # @version 0.0.5
9
+ VERSION = '0.0.5'
9
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.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov