redis_queued_locks 0.0.3 → 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: 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