redis_queued_locks 0.0.8 → 0.0.9

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: 4846c7a27c9ce2108903a9b9b9250a38aa18ae34be6da85b4055bfe9c20371bb
4
- data.tar.gz: 0346d8a0c4d43490ea0fcf79c7048e70c480285b18f26f84b06ad24fe42dc9fc
3
+ metadata.gz: 463b0613174c7a3806aef19ce962bbe5ed91d342796bec84ea47b2aa79cf61fb
4
+ data.tar.gz: 4d483db40896d77049e3e6d324a206606225c9c8e8b592072b1f501563fb7ec8
5
5
  SHA512:
6
- metadata.gz: cbf5bd49d6c3b912ff8e3b8061cae4e2b1eaea00f913c8f217058168e7008f3c4daaeef0888ca22bce22d318742db1890c8524293e20734b4dea506fc3eeae10
7
- data.tar.gz: 744519a43e3f1eb98527cbcb5495700b16a6ea94de95732baf8027debc14df7e5fa0394f804a9360383eb9168d3975794b46c4a0a6c45d1b769fd0c94e1700b5
6
+ metadata.gz: f829f8afc264f8a44d0edd1469c5ada76c7257f15ef6945c3f154fe2908b29d5ace4ce0c11365a30af1cdbb6d3b4aeec30227b84500da65b720f22247ed96053
7
+ data.tar.gz: 1eee0e4c2a665b57ee6d2c2c2d6488c065bbcdd586cfe06f2890a6b412a3a009675dd26e39383f2fad54434288d4c3c7660fba2faaa3013da98ab27c484b08b0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.9] - 2024-02-27
4
+ ### Changed
5
+ - The lock acquier identifier (`acq_id`) now includes the fiber id, the ractor id and an unique per-process
6
+ 10 byte string. It is added in order to prevent collisions between different processes/pods
7
+ that will have the same procjet id / thread id identifiers (cuz it is an object_id integers) that can lead
8
+ to the same position with the same `acq_id` for different processes/pods in the lock request queue.
9
+
3
10
  ## [0.0.8] - 2024-02-27
4
11
  ### Added
5
12
  - `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_clinet = 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,13 @@ 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
+ config.uniq_identifier = -> { RedisQueuedLocks::Resource.calc_uniq_identity }
122
129
  end
123
130
  ```
124
131
 
@@ -142,8 +149,6 @@ end
142
149
  ```ruby
143
150
  def lock(
144
151
  lock_name,
145
- process_id: RedisQueuedLocks::Resource.get_process_id,
146
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
147
152
  ttl: config[:default_lock_ttl],
148
153
  queue_ttl: config[:default_queue_ttl],
149
154
  timeout: config[:try_to_lock_timeout],
@@ -151,16 +156,13 @@ def lock(
151
156
  retry_delay: config[:retry_delay],
152
157
  retry_jitter: config[:retry_jitter],
153
158
  raise_errors: false,
159
+ identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;
154
160
  &block
155
161
  )
156
162
  ```
157
163
 
158
164
  - `lock_name` - `[String]`
159
165
  - 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
166
  - `ttl` [Integer]
165
167
  - Lock's time to live (in milliseconds).
166
168
  - `queue_ttl` - `[Integer]`
@@ -177,6 +179,12 @@ def lock(
177
179
  - See RedisQueuedLocks::Instrument::ActiveSupport for example.
178
180
  - `raise_errors` - `[Boolean]`
179
181
  - Raise errors on library-related limits such as timeout or retry count limit.
182
+ - `identity` - `[String]`
183
+ - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
184
+ collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
185
+ pods or/and nodes of your application;
186
+ - It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`
187
+ ivar (accessed via `uniq_dentity` accessor method);
180
188
  - `block` - `[Block]`
181
189
  - A block of code that should be executed after the successfully acquired lock.
182
190
  - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
@@ -214,14 +222,13 @@ Return value:
214
222
  ```ruby
215
223
  def lock!(
216
224
  lock_name,
217
- process_id: RedisQueuedLocks::Resource.get_process_id,
218
- thread_id: RedisQueuedLocks::Resource.get_thread_id,
219
225
  ttl: config[:default_lock_ttl],
220
226
  queue_ttl: config[:default_queue_ttl],
221
227
  timeout: config[:default_timeout],
222
228
  retry_count: config[:retry_count],
223
229
  retry_delay: config[:retry_delay],
224
230
  retry_jitter: config[:retry_jitter],
231
+ identity: uniq_identity,
225
232
  &block
226
233
  )
227
234
  ```
@@ -236,7 +243,7 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
236
243
  - returns `nil` if lock does not exist;
237
244
  - lock data (`Hash<Symbol,String|Integer>`):
238
245
  - `lock_key` - `string` - lock key in redis;
239
- - `acq_id` - `string` - acquier identifier (process_id/thread_id by default);
246
+ - `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
240
247
  - `ts` - `integer`/`epoch` - the time lock was obtained;
241
248
  - `init_ttl` - `integer` - (milliseconds) initial lock key ttl;
242
249
  - `rem_ttl` - `integer` - (milliseconds) remaining lock key ttl;
@@ -267,7 +274,7 @@ rql.lock_info("your_lock_name")
267
274
  - lock queue data (`Hash<Symbol,String|Array<Hash<Symbol,String|Numeric>>`):
268
275
  - `lock_queue` - `string` - lock queue key in redis;
269
276
  - `queue` - `array` - an array of lock requests (array of hashes):
270
- - `acq_id` - `string` - acquier identifier (process_id/thread_id by default);
277
+ - `acq_id` - `string` - acquier identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);
271
278
  - `score` - `float`/`epoch` - time when the lock request was made (epoch);
272
279
 
273
280
  ```ruby
@@ -277,9 +284,9 @@ rql.queue_info("your_lock_name")
277
284
  {
278
285
  lock_queue: "rql:lock_queue:your_lock_name",
279
286
  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},
287
+ { acq_id: "rql:acq:123/456/567/678/fa76df9cc2", score: 1},
288
+ { acq_id: "rql:acq:123/567/456/679/c7bfcaf4f9", score: 2},
289
+ { acq_id: "rql:acq:555/329/523/127/7329553b11", score: 3},
283
290
  # ...etc
284
291
  ]
285
292
  }
@@ -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)
@@ -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]
@@ -197,11 +202,7 @@ class RedisQueuedLocks::Client
197
202
  # @api public
198
203
  # @since 0.1.0
199
204
  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
- )
205
+ RedisQueuedLocks::Acquier.release_all_locks!(redis_client, batch_size, config[:instrumenter])
205
206
  end
206
207
  end
207
208
  # 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.9
9
+ VERSION = '0.0.9'
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.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov