redis_queued_locks 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +21 -14
- data/lib/redis_queued_locks/acquier.rb +20 -3
- data/lib/redis_queued_locks/client.rb +24 -23
- data/lib/redis_queued_locks/resource.rb +33 -2
- data/lib/redis_queued_locks/version.rb +2 -2
- data/lib/redis_queued_locks.rb +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 463b0613174c7a3806aef19ce962bbe5ed91d342796bec84ea47b2aa79cf61fb
|
4
|
+
data.tar.gz: 4d483db40896d77049e3e6d324a206606225c9c8e8b592072b1f501563fb7ec8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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(
|
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
|
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
|
data/lib/redis_queued_locks.rb
CHANGED