redlock 2.0.1 → 2.0.2
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 +40 -0
- data/README.md +2 -1
- data/lib/redlock/client.rb +26 -1
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +1 -1
- data/spec/client_spec.rb +46 -4
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bdda4faebf87c2d1fb0026a3142497d5ae14092340bce6e35e46f51635e5c6a
|
4
|
+
data.tar.gz: a9f5636898a2a3ece98b95a286bd88f417e68d119b1f1ad60bd75b5d1da94baf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b10ec10f00ce9282d604b1d0c8a2e845d8d95a6684a5a235d6d39b598efa6324725c4d088e8016b11d473b0195a1e29a451e79fad115f04f6b217493be297cf
|
7
|
+
data.tar.gz: e1d54bb8bb8a108e0989d1c11baa44cbfc4c971029fb23819d000758b42739698342ef803a826990757c888d113fb8a49663df1f3812603965c5498361596f86
|
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"
|
@@ -232,6 +241,7 @@ module Redlock
|
|
232
241
|
def try_lock_instances(resource, ttl, options)
|
233
242
|
retry_count = options[:retry_count] || @retry_count
|
234
243
|
tries = options[:extend] ? 1 : (retry_count + 1)
|
244
|
+
last_error = nil
|
235
245
|
|
236
246
|
tries.times do |attempt_number|
|
237
247
|
# Wait a random delay before retrying.
|
@@ -239,8 +249,12 @@ module Redlock
|
|
239
249
|
|
240
250
|
lock_info = lock_instances(resource, ttl, options)
|
241
251
|
return lock_info if lock_info
|
252
|
+
rescue => e
|
253
|
+
last_error = e
|
242
254
|
end
|
243
255
|
|
256
|
+
raise last_error if last_error
|
257
|
+
|
244
258
|
false
|
245
259
|
end
|
246
260
|
|
@@ -261,9 +275,15 @@ module Redlock
|
|
261
275
|
def lock_instances(resource, ttl, options)
|
262
276
|
value = (options[:extend] || { value: SecureRandom.uuid })[:value]
|
263
277
|
allow_new_lock = options[:extend_only_if_locked] ? 'no' : 'yes'
|
278
|
+
errors = []
|
264
279
|
|
265
280
|
locked, time_elapsed = timed do
|
266
|
-
@servers.
|
281
|
+
@servers.count do |s|
|
282
|
+
s.lock(resource, value, ttl, allow_new_lock)
|
283
|
+
rescue => e
|
284
|
+
errors << e
|
285
|
+
false
|
286
|
+
end
|
267
287
|
end
|
268
288
|
|
269
289
|
validity = ttl - time_elapsed - drift(ttl)
|
@@ -272,6 +292,11 @@ module Redlock
|
|
272
292
|
{ validity: validity, resource: resource, value: value }
|
273
293
|
else
|
274
294
|
@servers.each { |s| s.unlock(resource, value) }
|
295
|
+
|
296
|
+
if errors.size >= @quorum
|
297
|
+
raise LockAcquisitionError.new('Too many Redis errors prevented lock acquisition', errors)
|
298
|
+
end
|
299
|
+
|
275
300
|
false
|
276
301
|
end
|
277
302
|
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'
|
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
@@ -76,6 +76,38 @@ RSpec.describe Redlock::Client do
|
|
76
76
|
context 'when lock is available' do
|
77
77
|
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
78
78
|
|
79
|
+
context 'when redis connection error occurs' do
|
80
|
+
let(:servers_with_quorum) {
|
81
|
+
[
|
82
|
+
"redis://#{redis1_host}:#{redis1_port}",
|
83
|
+
"redis://#{redis2_host}:#{redis2_port}",
|
84
|
+
unreachable_redis
|
85
|
+
]
|
86
|
+
}
|
87
|
+
|
88
|
+
let(:servers_without_quorum) {
|
89
|
+
[
|
90
|
+
"redis://#{redis1_host}:#{redis1_port}",
|
91
|
+
unreachable_redis,
|
92
|
+
unreachable_redis
|
93
|
+
]
|
94
|
+
}
|
95
|
+
|
96
|
+
it 'locks if majority of redis instances are available' do
|
97
|
+
redlock = Redlock::Client.new(servers_with_quorum)
|
98
|
+
|
99
|
+
expect(redlock.lock(resource_key, ttl)).to be_truthy
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'fails to acquire a lock if majority of Redis instances are not available' do
|
103
|
+
redlock = Redlock::Client.new(servers_without_quorum)
|
104
|
+
|
105
|
+
expect {
|
106
|
+
redlock.lock(resource_key, ttl)
|
107
|
+
}.to raise_error(Redlock::LockAcquisitionError)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
79
111
|
it 'locks' do
|
80
112
|
@lock_info = lock_manager.lock(resource_key, ttl)
|
81
113
|
|
@@ -266,7 +298,10 @@ RSpec.describe Redlock::Client do
|
|
266
298
|
|
267
299
|
expect {
|
268
300
|
lock_manager.lock(resource_key, ttl)
|
269
|
-
}.to raise_error(
|
301
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
302
|
+
expect(e.errors[0]).to be_a(RedisClient::CannotConnectError)
|
303
|
+
expect(e.errors.count).to eq 1
|
304
|
+
end
|
270
305
|
end
|
271
306
|
end
|
272
307
|
|
@@ -278,7 +313,10 @@ RSpec.describe Redlock::Client do
|
|
278
313
|
redis_instance.instance_variable_set(:@redis, unreachable_redis)
|
279
314
|
expect {
|
280
315
|
lock_manager.lock(resource_key, ttl)
|
281
|
-
}.to raise_error(
|
316
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
317
|
+
expect(e.errors[0]).to be_a(RedisClient::CannotConnectError)
|
318
|
+
expect(e.errors.count).to eq 1
|
319
|
+
end
|
282
320
|
redis_instance.instance_variable_set(:@redis, old_redis)
|
283
321
|
expect(lock_manager.lock(resource_key, ttl)).to be_truthy
|
284
322
|
end
|
@@ -308,11 +346,15 @@ RSpec.describe Redlock::Client do
|
|
308
346
|
# This time we do not pass it through to Redis, in order to simulate a passing
|
309
347
|
# call to LOAD SCRIPT followed by another NOSCRIPT error. Imagine someone
|
310
348
|
# repeatedly calling SCRIPT FLUSH on our Redis instance.
|
311
|
-
expect(@manipulated_instance).to receive(:load_scripts)
|
349
|
+
expect(@manipulated_instance).to receive(:load_scripts).exactly(8).times
|
312
350
|
|
313
351
|
expect {
|
314
352
|
lock_manager.lock(resource_key, ttl)
|
315
|
-
}.to raise_error(
|
353
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
354
|
+
expect(e.errors[0]).to be_a(RedisClient::CommandError)
|
355
|
+
expect(e.errors[0].message).to match(/NOSCRIPT/)
|
356
|
+
expect(e.errors.count).to eq 1
|
357
|
+
end
|
316
358
|
end
|
317
359
|
end
|
318
360
|
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leandro Moreira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.14.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.14.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: connection_pool
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- ".github/workflows/ci.yml"
|
123
123
|
- ".gitignore"
|
124
124
|
- ".rspec"
|
125
|
+
- CHANGELOG.md
|
125
126
|
- CONTRIBUTORS
|
126
127
|
- Gemfile
|
127
128
|
- LICENSE
|
@@ -158,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
159
|
- !ruby/object:Gem::Version
|
159
160
|
version: '0'
|
160
161
|
requirements: []
|
161
|
-
rubygems_version: 3.
|
162
|
+
rubygems_version: 3.3.7
|
162
163
|
signing_key:
|
163
164
|
specification_version: 4
|
164
165
|
summary: Distributed lock using Redis written in Ruby.
|