redlock 2.0.1 → 2.0.3
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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +40 -0
- data/README.md +2 -1
- data/lib/redlock/client.rb +50 -2
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +1 -1
- data/spec/client_spec.rb +56 -4
- metadata +15 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb95cd103dd1856b7c75459b121bd5905d2884f520850552552bdb1c39f7502b
|
4
|
+
data.tar.gz: 165945b2aca8e081ad2704fa9eb699bd2e08e75f973fc7ae802163eed88dce8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79ba3e5ab3dc1704b2af22efb3f038a43b8b9fd773492ba8e6d8f8e0b162cdd4df3671e2267ac80ff3e1f1a3d0edb3b3cc29430637f6d3efa146fd1f6d4fd100
|
7
|
+
data.tar.gz: '0929d107e6ffb002498a26b5afe0d59e41eacc7265ade1402584b1c25a52509ddbedd40eb24acfacfefa718f343ffe9f055adff4f3f7b390e61971543dea4451'
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
# Change Log
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
|
+
|
8
|
+
## [Unreleased] - yyyy-mm-dd
|
9
|
+
|
10
|
+
Here we write upgrading notes for brands. It's a team effort to make them as
|
11
|
+
straightforward as possible.
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
|
19
|
+
|
20
|
+
## [2.0.1] - 2023-02-14
|
21
|
+
|
22
|
+
### Added
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
### Fixed
|
27
|
+
|
28
|
+
* always treat redis instance as pool-like #125
|
29
|
+
|
30
|
+
## [2.0.0] - 2023-02-09
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
* support for redis >= 6.0
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
|
38
|
+
* **BREAKING**: The library now only works with `RedisClient` instance.
|
39
|
+
|
40
|
+
### Fixed
|
data/README.md
CHANGED
@@ -15,7 +15,8 @@ This is an implementation of a proposed [distributed lock algorithm with Redis](
|
|
15
15
|
|
16
16
|
## Compatibility
|
17
17
|
|
18
|
-
|
18
|
+
* It works with Redis server versions 6.0 or later.
|
19
|
+
* Redlock >= 2.0 only works with [`RedisClient`](https://github.com/redis-rb/redis-client) client instance.
|
19
20
|
|
20
21
|
## Installation
|
21
22
|
|
data/lib/redlock/client.rb
CHANGED
@@ -4,6 +4,15 @@ require 'securerandom'
|
|
4
4
|
module Redlock
|
5
5
|
include Scripts
|
6
6
|
|
7
|
+
class LockAcquisitionError < StandardError
|
8
|
+
attr_reader :errors
|
9
|
+
|
10
|
+
def initialize(message, errors)
|
11
|
+
super(message)
|
12
|
+
@errors = errors
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
class Client
|
8
17
|
DEFAULT_REDIS_HOST = ENV["DEFAULT_REDIS_HOST"] || "localhost"
|
9
18
|
DEFAULT_REDIS_PORT = ENV["DEFAULT_REDIS_PORT"] || "6379"
|
@@ -160,12 +169,35 @@ module Redlock
|
|
160
169
|
if connection.respond_to?(:client)
|
161
170
|
@redis = connection
|
162
171
|
else
|
163
|
-
@redis =
|
172
|
+
@redis = initialize_client(connection)
|
164
173
|
end
|
165
174
|
@redis.extend(ConnectionPoolLike)
|
166
175
|
end
|
167
176
|
end
|
168
177
|
|
178
|
+
def initialize_client(options)
|
179
|
+
if options.key?(:sentinels)
|
180
|
+
if url = options.delete(:url)
|
181
|
+
uri = URI.parse(url)
|
182
|
+
if !options.key?(:name) && uri.host
|
183
|
+
options[:name] = uri.host
|
184
|
+
end
|
185
|
+
|
186
|
+
if !options.key?(:password) && uri.password && !uri.password.empty?
|
187
|
+
options[:password] = uri.password
|
188
|
+
end
|
189
|
+
|
190
|
+
if !options.key?(:username) && uri.user && !uri.user.empty?
|
191
|
+
options[:username] = uri.user
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
RedisClient.sentinel(**options).new_client
|
196
|
+
else
|
197
|
+
RedisClient.config(**options).new_client
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
169
201
|
def lock(resource, val, ttl, allow_new_lock)
|
170
202
|
recover_from_script_flush do
|
171
203
|
@redis.with { |conn|
|
@@ -232,6 +264,7 @@ module Redlock
|
|
232
264
|
def try_lock_instances(resource, ttl, options)
|
233
265
|
retry_count = options[:retry_count] || @retry_count
|
234
266
|
tries = options[:extend] ? 1 : (retry_count + 1)
|
267
|
+
last_error = nil
|
235
268
|
|
236
269
|
tries.times do |attempt_number|
|
237
270
|
# Wait a random delay before retrying.
|
@@ -239,8 +272,12 @@ module Redlock
|
|
239
272
|
|
240
273
|
lock_info = lock_instances(resource, ttl, options)
|
241
274
|
return lock_info if lock_info
|
275
|
+
rescue => e
|
276
|
+
last_error = e
|
242
277
|
end
|
243
278
|
|
279
|
+
raise last_error if last_error
|
280
|
+
|
244
281
|
false
|
245
282
|
end
|
246
283
|
|
@@ -261,9 +298,15 @@ module Redlock
|
|
261
298
|
def lock_instances(resource, ttl, options)
|
262
299
|
value = (options[:extend] || { value: SecureRandom.uuid })[:value]
|
263
300
|
allow_new_lock = options[:extend_only_if_locked] ? 'no' : 'yes'
|
301
|
+
errors = []
|
264
302
|
|
265
303
|
locked, time_elapsed = timed do
|
266
|
-
@servers.
|
304
|
+
@servers.count do |s|
|
305
|
+
s.lock(resource, value, ttl, allow_new_lock)
|
306
|
+
rescue => e
|
307
|
+
errors << e
|
308
|
+
false
|
309
|
+
end
|
267
310
|
end
|
268
311
|
|
269
312
|
validity = ttl - time_elapsed - drift(ttl)
|
@@ -272,6 +315,11 @@ module Redlock
|
|
272
315
|
{ validity: validity, resource: resource, value: value }
|
273
316
|
else
|
274
317
|
@servers.each { |s| s.unlock(resource, value) }
|
318
|
+
|
319
|
+
if errors.size >= @quorum
|
320
|
+
raise LockAcquisitionError.new('Too many Redis errors prevented lock acquisition', errors)
|
321
|
+
end
|
322
|
+
|
275
323
|
false
|
276
324
|
end
|
277
325
|
end
|
data/lib/redlock/version.rb
CHANGED
data/redlock.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_dependency 'redis-client'
|
21
|
+
spec.add_dependency 'redis-client', '>= 0.14.1', '< 1.0.0'
|
22
22
|
|
23
23
|
spec.add_development_dependency 'connection_pool', '~> 2.2'
|
24
24
|
spec.add_development_dependency 'coveralls', '~> 0.8'
|
data/spec/client_spec.rb
CHANGED
@@ -59,6 +59,16 @@ RSpec.describe Redlock::Client do
|
|
59
59
|
lock_manager.unlock(lock_info)
|
60
60
|
end
|
61
61
|
|
62
|
+
it 'accepts Configuration hashes' do
|
63
|
+
config = { url: "redis://#{redis1_host}:#{redis1_port}" }
|
64
|
+
_redlock = Redlock::Client.new([config])
|
65
|
+
|
66
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
67
|
+
expect(lock_info).to be_a(Hash)
|
68
|
+
expect(resource_key).to_not be_lockable(lock_manager, ttl)
|
69
|
+
lock_manager.unlock(lock_info)
|
70
|
+
end
|
71
|
+
|
62
72
|
it 'does not load scripts' do
|
63
73
|
redis_client.call('SCRIPT', 'FLUSH')
|
64
74
|
|
@@ -76,6 +86,38 @@ RSpec.describe Redlock::Client do
|
|
76
86
|
context 'when lock is available' do
|
77
87
|
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
78
88
|
|
89
|
+
context 'when redis connection error occurs' do
|
90
|
+
let(:servers_with_quorum) {
|
91
|
+
[
|
92
|
+
"redis://#{redis1_host}:#{redis1_port}",
|
93
|
+
"redis://#{redis2_host}:#{redis2_port}",
|
94
|
+
unreachable_redis
|
95
|
+
]
|
96
|
+
}
|
97
|
+
|
98
|
+
let(:servers_without_quorum) {
|
99
|
+
[
|
100
|
+
"redis://#{redis1_host}:#{redis1_port}",
|
101
|
+
unreachable_redis,
|
102
|
+
unreachable_redis
|
103
|
+
]
|
104
|
+
}
|
105
|
+
|
106
|
+
it 'locks if majority of redis instances are available' do
|
107
|
+
redlock = Redlock::Client.new(servers_with_quorum)
|
108
|
+
|
109
|
+
expect(redlock.lock(resource_key, ttl)).to be_truthy
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'fails to acquire a lock if majority of Redis instances are not available' do
|
113
|
+
redlock = Redlock::Client.new(servers_without_quorum)
|
114
|
+
|
115
|
+
expect {
|
116
|
+
redlock.lock(resource_key, ttl)
|
117
|
+
}.to raise_error(Redlock::LockAcquisitionError)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
79
121
|
it 'locks' do
|
80
122
|
@lock_info = lock_manager.lock(resource_key, ttl)
|
81
123
|
|
@@ -266,7 +308,10 @@ RSpec.describe Redlock::Client do
|
|
266
308
|
|
267
309
|
expect {
|
268
310
|
lock_manager.lock(resource_key, ttl)
|
269
|
-
}.to raise_error(
|
311
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
312
|
+
expect(e.errors[0]).to be_a(RedisClient::CannotConnectError)
|
313
|
+
expect(e.errors.count).to eq 1
|
314
|
+
end
|
270
315
|
end
|
271
316
|
end
|
272
317
|
|
@@ -278,7 +323,10 @@ RSpec.describe Redlock::Client do
|
|
278
323
|
redis_instance.instance_variable_set(:@redis, unreachable_redis)
|
279
324
|
expect {
|
280
325
|
lock_manager.lock(resource_key, ttl)
|
281
|
-
}.to raise_error(
|
326
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
327
|
+
expect(e.errors[0]).to be_a(RedisClient::CannotConnectError)
|
328
|
+
expect(e.errors.count).to eq 1
|
329
|
+
end
|
282
330
|
redis_instance.instance_variable_set(:@redis, old_redis)
|
283
331
|
expect(lock_manager.lock(resource_key, ttl)).to be_truthy
|
284
332
|
end
|
@@ -308,11 +356,15 @@ RSpec.describe Redlock::Client do
|
|
308
356
|
# This time we do not pass it through to Redis, in order to simulate a passing
|
309
357
|
# call to LOAD SCRIPT followed by another NOSCRIPT error. Imagine someone
|
310
358
|
# repeatedly calling SCRIPT FLUSH on our Redis instance.
|
311
|
-
expect(@manipulated_instance).to receive(:load_scripts)
|
359
|
+
expect(@manipulated_instance).to receive(:load_scripts).exactly(8).times
|
312
360
|
|
313
361
|
expect {
|
314
362
|
lock_manager.lock(resource_key, ttl)
|
315
|
-
}.to raise_error(
|
363
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
364
|
+
expect(e.errors[0]).to be_a(RedisClient::CommandError)
|
365
|
+
expect(e.errors[0].message).to match(/NOSCRIPT/)
|
366
|
+
expect(e.errors.count).to eq 1
|
367
|
+
end
|
316
368
|
end
|
317
369
|
end
|
318
370
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redlock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leandro Moreira
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -16,14 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.14.1
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
29
|
+
version: 0.14.1
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: connection_pool
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +128,7 @@ files:
|
|
122
128
|
- ".github/workflows/ci.yml"
|
123
129
|
- ".gitignore"
|
124
130
|
- ".rspec"
|
131
|
+
- CHANGELOG.md
|
125
132
|
- CONTRIBUTORS
|
126
133
|
- Gemfile
|
127
134
|
- LICENSE
|
@@ -143,7 +150,7 @@ homepage: https://github.com/leandromoreira/redlock-rb
|
|
143
150
|
licenses:
|
144
151
|
- BSD-2-Clause
|
145
152
|
metadata: {}
|
146
|
-
post_install_message:
|
153
|
+
post_install_message:
|
147
154
|
rdoc_options: []
|
148
155
|
require_paths:
|
149
156
|
- lib
|
@@ -158,8 +165,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
165
|
- !ruby/object:Gem::Version
|
159
166
|
version: '0'
|
160
167
|
requirements: []
|
161
|
-
rubygems_version: 3.
|
162
|
-
signing_key:
|
168
|
+
rubygems_version: 3.4.10
|
169
|
+
signing_key:
|
163
170
|
specification_version: 4
|
164
171
|
summary: Distributed lock using Redis written in Ruby.
|
165
172
|
test_files:
|