redis_queued_locks 0.0.8 → 0.0.10

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: 4846c7a27c9ce2108903a9b9b9250a38aa18ae34be6da85b4055bfe9c20371bb
4
- data.tar.gz: 0346d8a0c4d43490ea0fcf79c7048e70c480285b18f26f84b06ad24fe42dc9fc
3
+ metadata.gz: 28714dd149659f7c58634868219b3e020df6304253db120fda241304a82ce781
4
+ data.tar.gz: 52e9193aa4945598f7da31d202cfc445f2b5b63009f5f09e260a732b02e3ac64
5
5
  SHA512:
6
- metadata.gz: cbf5bd49d6c3b912ff8e3b8061cae4e2b1eaea00f913c8f217058168e7008f3c4daaeef0888ca22bce22d318742db1890c8524293e20734b4dea506fc3eeae10
7
- data.tar.gz: 744519a43e3f1eb98527cbcb5495700b16a6ea94de95732baf8027debc14df7e5fa0394f804a9360383eb9168d3975794b46c4a0a6c45d1b769fd0c94e1700b5
6
+ metadata.gz: b0f02df991559f21667288ebf690695909d65766cb2c9dd809d18d62a34e722d35d898e227ef38cf12d5acf96d271b7c0a078b1aa8ce47b93645fdd27b36d043
7
+ data.tar.gz: 5f60948e47cebdc9ff2de6a594aa68155c39ee262fd0662afbe293b672ce811c74a5182f8793be7d3860a8361560213108f25fd00d8a1891cd72a353d3103b25
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.10] - 2024-02-27
4
+ ### Changed
5
+ - Minor documentation updates;
6
+
7
+ ## [0.0.9] - 2024-02-27
8
+ ### Changed
9
+ - The lock acquier identifier (`acq_id`) now includes the fiber id, the ractor id and an unique per-process
10
+ 10 byte string. It is added in order to prevent collisions between different processes/pods
11
+ that will have the same procjet id / thread id identifiers (cuz it is an object_id integers) that can lead
12
+ to the same position with the same `acq_id` for different processes/pods in the lock request queue.
13
+
3
14
  ## [0.0.8] - 2024-02-27
4
15
  ### Added
5
16
  - `RedisQueuedLock::Client#locked?`
data/README.md CHANGED
@@ -60,7 +60,7 @@ require 'redis_queued_locks'
60
60
  require 'redis_queued_locks'
61
61
 
62
62
  # Step 1: initialize RedisClient instance
63
- redis_clinet = RedisClient.config.new_pool # NOTE: provide your ounw RedisClient instance
63
+ redis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance
64
64
 
65
65
  # Step 2: initialize RedisQueuedLock::Client instance
66
66
  rq_lock_client = RedisQueuedLocks::Client.new(redis_client) do |config|
@@ -119,6 +119,14 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
119
119
  # - payload: <hash> requried;
120
120
  # - disabled by default via VoidNotifier;
121
121
  config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport
122
+
123
+ # (default: -> { RedisQueuedLocks::Resource.calc_uniq_identity })
124
+ # - uniqude idenfitier that is uniq per process/pod;
125
+ # - prevents potential lock-acquirement collisions bettween different process/pods
126
+ # that have identical process_id/thread_id/fiber_id/ractor_id (identivcal acquier ids);
127
+ # - it is calculated once per `RedisQueudLocks::Client` instance;
128
+ # - expects the proc object;
129
+ config.uniq_identifier = -> { RedisQueuedLocks::Resource.calc_uniq_identity }
122
130
  end
123
131
  ```
124
132
 
@@ -142,8 +150,6 @@ end
142
150
  ```ruby
143
151
  def lock(
144
152
  lock_name,
145
- process_id: RedisQueuedLocks::Resource.get_process_id,
146
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
147
153
  ttl: config[:default_lock_ttl],
148
154
  queue_ttl: config[:default_queue_ttl],
149
155
  timeout: config[:try_to_lock_timeout],
@@ -151,16 +157,13 @@ def lock(
151
157
  retry_delay: config[:retry_delay],
152
158
  retry_jitter: config[:retry_jitter],
153
159
  raise_errors: false,
160
+ identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
154
161
  &block
155
162
  )
156
163
  ```
157
164
 
158
165
  - `lock_name` - `[String]`
159
166
  - Lock name to be obtained.
160
- - `process_id` - `[Integer,String]`
161
- - The process that want to acquire the lock.
162
- - `thread_id` - `[Integer,String]`
163
- - The process's thread that want to acquire the lock.
164
167
  - `ttl` [Integer]
165
168
  - Lock's time to live (in milliseconds).
166
169
  - `queue_ttl` - `[Integer]`
@@ -177,6 +180,12 @@ def lock(
177
180
  - See RedisQueuedLocks::Instrument::ActiveSupport for example.
178
181
  - `raise_errors` - `[Boolean]`
179
182
  - Raise errors on library-related limits such as timeout or retry count limit.
183
+ - `identity` - `[String]`
184
+ - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
185
+ collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
186
+ pods or/and nodes of your application;
187
+ - It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
188
+ ivar (accessed via `uniq_dentity` accessor method);
180
189
  - `block` - `[Block]`
181
190
  - A block of code that should be executed after the successfully acquired lock.
182
191
  - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
@@ -190,7 +199,7 @@ Return value:
190
199
  ok: true,
191
200
  result: {
192
201
  lock_key: String, # acquierd lock key ("rql:lock:your_lock_name")
193
- acq_id: String, # acquier identifier ("your_process_id/your_thread_id")
202
+ acq_id: String, # acquier identifier ("process_id/thread_id/fiber_id/ractor_id/identity")
194
203
  ts: Integer, # time (epoch) when lock was obtained (integer)
195
204
  ttl: Integer # lock's time to live in milliseconds (integer)
196
205
  }
@@ -214,14 +223,13 @@ Return value:
214
223
  ```ruby
215
224
  def lock!(
216
225
  lock_name,
217
- process_id: RedisQueuedLocks::Resource.get_process_id,
218
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
219
226
  ttl: config[:default_lock_ttl],
220
227
  queue_ttl: config[:default_queue_ttl],
221
228
  timeout: config[:default_timeout],
222
229
  retry_count: config[:retry_count],
223
230
  retry_delay: config[:retry_delay],
224
231
  retry_jitter: config[:retry_jitter],
232
+ identity: uniq_identity,
225
233
  &block
226
234
  )
227
235
  ```
@@ -236,7 +244,7 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
236
244
  - returns `nil` if lock does not exist;
237
245
  - lock data (`Hash<Symbol,String|Integer>`):
238
246
  - `lock_key` - `string` - lock key in redis;
239
- - `acq_id` - `string` - acquier identifier (process_id/thread_id by default);
247
+ - `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity);
240
248
  - `ts` - `integer`/`epoch` - the time lock was obtained;
241
249
  - `init_ttl` - `integer` - (milliseconds) initial lock key ttl;
242
250
  - `rem_ttl` - `integer` - (milliseconds) remaining lock key ttl;
@@ -247,7 +255,7 @@ rql.lock_info("your_lock_name")
247
255
  # =>
248
256
  {
249
257
  lock_key: "rql:lock:your_lock_name",
250
- acq_id: "rql:acq:123/456",
258
+ acq_id: "rql:acq:123/456/567/678/374dd74324",
251
259
  ts: 123456789,
252
260
  ini_ttl: 123456789,
253
261
  rem_ttl: 123456789
@@ -267,7 +275,7 @@ rql.lock_info("your_lock_name")
267
275
  - lock queue data (`Hash<Symbol,String|Array<Hash<Symbol,String|Numeric>>`):
268
276
  - `lock_queue` - `string` - lock queue key in redis;
269
277
  - `queue` - `array` - an array of lock requests (array of hashes):
270
- - `acq_id` - `string` - acquier identifier (process_id/thread_id by default);
278
+ - `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
271
279
  - `score` - `float`/`epoch` - time when the lock request was made (epoch);
272
280
 
273
281
  ```ruby
@@ -277,9 +285,9 @@ rql.queue_info("your_lock_name")
277
285
  {
278
286
  lock_queue: "rql:lock_queue:your_lock_name",
279
287
  queue: [
280
- { acq_id: "rql:acq:123/456", score: 1},
281
- { acq_id: "rql:acq:123/567", score: 2},
282
- { acq_id: "rql:acq:555/329", score: 3},
288
+ { acq_id: "rql:acq:123/456/567/678/fa76df9cc2", score: 1},
289
+ { acq_id: "rql:acq:123/567/456/679/c7bfcaf4f9", score: 2},
290
+ { acq_id: "rql:acq:555/329/523/127/7329553b11", score: 3},
283
291
  # ...etc
284
292
  ]
285
293
  }
@@ -422,7 +430,7 @@ Detalized event semantics and payload structure:
422
430
  - payload:
423
431
  - `rel_time` - `float`/`milliseconds` - time spent on "realese all locks" operation;
424
432
  - `at` - `integer`/`epoch` - the time when the operation has ended;
425
- - `rel_keys` - `integer` - released redis keys count (`released queu keys` + `released lock keys`);
433
+ - `rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);
426
434
 
427
435
  ---
428
436
 
@@ -431,6 +439,7 @@ Detalized event semantics and payload structure:
431
439
  - **Major**
432
440
  - Semantic Error objects for unexpected Redis errors;
433
441
  - `100%` test coverage;
442
+ - sidecar `Ractor` object and `in progress queue` in RedisDB that will extend an acquired lock for long-running blocks of code (that invoked "under" the lock);
434
443
  - **Minor**
435
444
  - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
436
445
  - better code stylization and interesting refactorings;
@@ -34,9 +34,13 @@ module RedisQueuedLocks::Acquier
34
34
  # @param lock_name [String]
35
35
  # Lock name to be acquier.
36
36
  # @option process_id [Integer,String]
37
- # The process that want to acquire the lock.
37
+ # The process that want to acquire a lock.
38
38
  # @option thread_id [Integer,String]
39
- # The process's thread that want to acquire the lock.
39
+ # The process's thread that want to acquire a lock.
40
+ # @option fiber_id [Integer,String]
41
+ # A current fiber that want to acquire a lock.
42
+ # @option ractor_id [Integer,String]
43
+ # The current ractor that want to acquire a lock.
40
44
  # @option ttl [Integer,NilClass]
41
45
  # Lock's time to live (in milliseconds). Nil means "without timeout".
42
46
  # @option queue_ttl [Integer]
@@ -53,6 +57,10 @@ module RedisQueuedLocks::Acquier
53
57
  # Raise errors on exceptional cases.
54
58
  # @option instrumenter [#notify]
55
59
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
60
+ # @option identity [String]
61
+ # Unique acquire identifier that is also should be unique between processes and pods
62
+ # on different machines. By default the uniq identity string is
63
+ # represented as 10 bytes hexstr.
56
64
  # @param [Block]
57
65
  # A block of code that should be executed after the successfully acquired lock.
58
66
  # @return [Hash<Symbol,Any>]
@@ -66,6 +74,8 @@ module RedisQueuedLocks::Acquier
66
74
  lock_name,
67
75
  process_id:,
68
76
  thread_id:,
77
+ fiber_id:,
78
+ ractor_id:,
69
79
  ttl:,
70
80
  queue_ttl:,
71
81
  timeout:,
@@ -74,10 +84,17 @@ module RedisQueuedLocks::Acquier
74
84
  retry_jitter:,
75
85
  raise_errors:,
76
86
  instrumenter:,
87
+ identity:,
77
88
  &block
78
89
  )
79
90
  # Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
80
- acquier_id = RedisQueuedLocks::Resource.acquier_identifier(process_id, thread_id)
91
+ acquier_id = RedisQueuedLocks::Resource.acquier_identifier(
92
+ process_id,
93
+ thread_id,
94
+ fiber_id,
95
+ ractor_id,
96
+ identity
97
+ )
81
98
  lock_ttl = ttl + REDIS_EXPIRATION_DEVIATION
82
99
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
83
100
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
@@ -386,6 +403,17 @@ module RedisQueuedLocks::Acquier
386
403
  end
387
404
  end
388
405
 
406
+ # @param redis_client [RedisClient]
407
+ # @param lock_name [String]
408
+ # @param milliseconds [Integer]
409
+ # @return [?]
410
+ #
411
+ # @api private
412
+ # @since 0.1.0
413
+ def extend_lock_ttl(redis_client, lock_name, milliseconds)
414
+
415
+ end
416
+
389
417
  private
390
418
 
391
419
  # @param timeout [NilClass,Integer]
@@ -17,6 +17,7 @@ class RedisQueuedLocks::Client
17
17
  setting :default_queue_ttl, 15 # NOTE: seconds
18
18
  setting :lock_release_batch_size, 100
19
19
  setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
20
+ setting :uniq_identifier, -> { RedisQueuedLocks::Resource.calc_uniq_identity }
20
21
 
21
22
  # TODO: setting :logger, Logger.new(IO::NULL)
22
23
  # TODO: setting :debug, true/false
@@ -30,6 +31,7 @@ class RedisQueuedLocks::Client
30
31
  validate('default_queue_ttl', :integer)
31
32
  validate('lock_release_batch_size', :integer)
32
33
  validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
34
+ validate('uniq_identifier', :proc)
33
35
  end
34
36
 
35
37
  # @return [RedisClient]
@@ -38,6 +40,12 @@ class RedisQueuedLocks::Client
38
40
  # @since 0.1.0
39
41
  attr_reader :redis_client
40
42
 
43
+ # @return [String]
44
+ #
45
+ # @api private
46
+ # @since 0.1.0
47
+ attr_accessor :uniq_identity
48
+
41
49
  # @param redis_client [RedisClient]
42
50
  # Redis connection manager, which will be used for the lock acquierment and distribution.
43
51
  # It should be an instance of RedisClient.
@@ -49,15 +57,12 @@ class RedisQueuedLocks::Client
49
57
  # @since 0.1.0
50
58
  def initialize(redis_client, &configs)
51
59
  configure(&configs)
60
+ @uniq_identity = config[:uniq_identifier].call
52
61
  @redis_client = redis_client
53
62
  end
54
63
 
55
64
  # @param lock_name [String]
56
65
  # Lock name to be obtained.
57
- # @option process_id [Integer,String]
58
- # The process that want to acquire the lock.
59
- # @option thread_id [Integer,String]
60
- # The process's thread that want to acquire the lock.
61
66
  # @option ttl [Integer]
62
67
  # Lock's time to live (in milliseconds).
63
68
  # @option queue_ttl [Integer]
@@ -74,17 +79,19 @@ class RedisQueuedLocks::Client
74
79
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
75
80
  # @option raise_errors [Boolean]
76
81
  # Raise errors on library-related limits such as timeout or failed lock obtain.
82
+ # @option identity [String]
83
+ # Unique acquire identifier that is also should be unique between processes and pods
84
+ # on different machines. By default the uniq identity string is
85
+ # represented as 10 bytes hexstr.
77
86
  # @param block [Block]
78
87
  # A block of code that should be executed after the successfully acquired lock.
79
- # @return [Hash<Symbol,Any>]
88
+ # @return [Hash<Symbol,Any>,yield]
80
89
  # Format: { ok: true/false, result: Symbol/Hash }.
81
90
  #
82
91
  # @api public
83
92
  # @since 0.1.0
84
93
  def lock(
85
94
  lock_name,
86
- process_id: RedisQueuedLocks::Resource.get_process_id,
87
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
88
95
  ttl: config[:default_lock_ttl],
89
96
  queue_ttl: config[:default_queue_ttl],
90
97
  timeout: config[:try_to_lock_timeout],
@@ -92,13 +99,16 @@ class RedisQueuedLocks::Client
92
99
  retry_delay: config[:retry_delay],
93
100
  retry_jitter: config[:retry_jitter],
94
101
  raise_errors: false,
102
+ identity: uniq_identity,
95
103
  &block
96
104
  )
97
105
  RedisQueuedLocks::Acquier.acquire_lock!(
98
106
  redis_client,
99
107
  lock_name,
100
- process_id:,
101
- thread_id:,
108
+ process_id: RedisQueuedLocks::Resource.get_process_id,
109
+ thread_id: RedisQueuedLocks::Resource.get_thread_id,
110
+ fiber_id: RedisQueuedLocks::Resource.get_fiber_id,
111
+ ractor_id: RedisQueuedLocks::Resource.get_ractor_id,
102
112
  ttl:,
103
113
  queue_ttl:,
104
114
  timeout:,
@@ -107,6 +117,7 @@ class RedisQueuedLocks::Client
107
117
  retry_jitter:,
108
118
  raise_errors:,
109
119
  instrumenter: config[:instrumenter],
120
+ identity:,
110
121
  &block
111
122
  )
112
123
  end
@@ -117,20 +128,17 @@ class RedisQueuedLocks::Client
117
128
  # @since 0.1.0
118
129
  def lock!(
119
130
  lock_name,
120
- process_id: RedisQueuedLocks::Resource.get_process_id,
121
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
122
131
  ttl: config[:default_lock_ttl],
123
132
  queue_ttl: config[:default_queue_ttl],
124
133
  timeout: config[:default_timeout],
125
134
  retry_count: config[:retry_count],
126
135
  retry_delay: config[:retry_delay],
127
136
  retry_jitter: config[:retry_jitter],
137
+ identity: uniq_identity,
128
138
  &block
129
139
  )
130
140
  lock(
131
141
  lock_name,
132
- process_id:,
133
- thread_id:,
134
142
  ttl:,
135
143
  queue_ttl:,
136
144
  timeout:,
@@ -138,6 +146,7 @@ class RedisQueuedLocks::Client
138
146
  retry_delay:,
139
147
  retry_jitter:,
140
148
  raise_errors: true,
149
+ identity:,
141
150
  &block
142
151
  )
143
152
  end
@@ -148,11 +157,7 @@ class RedisQueuedLocks::Client
148
157
  # @api public
149
158
  # @since 0.1.0
150
159
  def unlock(lock_name)
151
- RedisQueuedLocks::Acquier.release_lock!(
152
- redis_client,
153
- lock_name,
154
- config[:instrumenter]
155
- )
160
+ RedisQueuedLocks::Acquier.release_lock!(redis_client, lock_name, config[:instrumenter])
156
161
  end
157
162
 
158
163
  # @param lock_name [String]
@@ -191,17 +196,23 @@ class RedisQueuedLocks::Client
191
196
  RedisQueuedLocks::Acquier.queue_info(redis_client, lock_name)
192
197
  end
193
198
 
199
+ # @param lock_name [String]
200
+ # @param milliseconds [Integer] How many milliseconds should be added.
201
+ # @return [?]
202
+ #
203
+ # @api public
204
+ # @since 0.1.0
205
+ def extend_lock_ttl(lock_name, milliseconds)
206
+ RedisQueuedLocks::Acquier.extend_lock_ttl(redis_client, lock_name)
207
+ end
208
+
194
209
  # @option batch_size [Integer]
195
210
  # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol/Hash }.
196
211
  #
197
212
  # @api public
198
213
  # @since 0.1.0
199
214
  def clear_locks(batch_size: config[:lock_release_batch_size])
200
- RedisQueuedLocks::Acquier.release_all_locks!(
201
- redis_client,
202
- batch_size,
203
- config[:instrumenter]
204
- )
215
+ RedisQueuedLocks::Acquier.release_all_locks!(redis_client, batch_size, config[:instrumenter])
205
216
  end
206
217
  end
207
218
  # rubocop:enable Metrics/ClassLength
@@ -16,14 +16,29 @@ module RedisQueuedLocks::Resource
16
16
  LOCK_QUEUE_PATTERN = 'rql:lock_queue:*'
17
17
 
18
18
  class << self
19
+ # Returns 10-byte unique identifier. It is used for uniquely
20
+ # identify current process between different nodes/pods of your application
21
+ # during the lock obtaining and self-identifying in the lock queue.
22
+ #
23
+ # @return [String]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ def calc_uniq_identity
28
+ SecureRandom.hex(5)
29
+ end
30
+
19
31
  # @param process_id [Integer,String]
20
32
  # @param thread_id [Integer,String]
33
+ # @param fiber_id [Integer,String]
34
+ # @param ractor_id [Integer,String]
35
+ # @param identity [String]
21
36
  # @return [String]
22
37
  #
23
38
  # @api private
24
39
  # @since 0.1.0
25
- def acquier_identifier(process_id = get_process_id, thread_id = get_thread_id)
26
- "rql:acq:#{process_id}/#{thread_id}"
40
+ def acquier_identifier(process_id, thread_id, fiber_id, ractor_id, identity)
41
+ "rql:acq:#{process_id}/#{thread_id}/#{fiber_id}/#{ractor_id}/#{identity}"
27
42
  end
28
43
 
29
44
  # @param lock_name [String]
@@ -79,6 +94,22 @@ module RedisQueuedLocks::Resource
79
94
  ::Thread.current.object_id
80
95
  end
81
96
 
97
+ # @return [Integer]
98
+ #
99
+ # @api private
100
+ # @since 0.1.0
101
+ def get_fiber_id
102
+ ::Fiber.current.object_id
103
+ end
104
+
105
+ # @return [Integer]
106
+ #
107
+ # @api private
108
+ # @since 0.1.0
109
+ def get_ractor_id
110
+ ::Ractor.current.object_id
111
+ end
112
+
82
113
  # @return [Integer]
83
114
  #
84
115
  # @api private
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.8
9
- VERSION = '0.0.8'
8
+ # @version 0.0.10
9
+ VERSION = '0.0.10'
10
10
  end
@@ -3,6 +3,7 @@
3
3
  require 'redis-client'
4
4
  require 'qonfig'
5
5
  require 'timeout'
6
+ require 'securerandom'
6
7
 
7
8
  # @api public
8
9
  # @since 0.1.0
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.8
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov