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 +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