redlock 2.0.1 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13a2ef1c0096e1135a1996719fbeb679234b639e0debecba4fa6d72f3fcd174a
4
- data.tar.gz: e8e247b8be0ded9272f04addfbcbc6b99c1c6ad4429f344c74d61101af7e1682
3
+ metadata.gz: eb95cd103dd1856b7c75459b121bd5905d2884f520850552552bdb1c39f7502b
4
+ data.tar.gz: 165945b2aca8e081ad2704fa9eb699bd2e08e75f973fc7ae802163eed88dce8b
5
5
  SHA512:
6
- metadata.gz: 81bf9233769810cc1a3f08a6927dc5ffddfa5c31ed158071daf1ad2ffc8303060094dbd78aa4af4185148b49878e36fcf54483a41378277e3bb306dd45c483e6
7
- data.tar.gz: b0f198c408d7281c8d1d45ef57ce612a964571bf728969601ecfd448f6e482c83567cef66d94fab6bcf722909bd19ef3120ad2ae56171dc83fae815ad81111a9
6
+ metadata.gz: 79ba3e5ab3dc1704b2af22efb3f038a43b8b9fd773492ba8e6d8f8e0b162cdd4df3671e2267ac80ff3e1f1a3d0edb3b3cc29430637f6d3efa146fd1f6d4fd100
7
+ data.tar.gz: '0929d107e6ffb002498a26b5afe0d59e41eacc7265ade1402584b1c25a52509ddbedd40eb24acfacfefa718f343ffe9f055adff4f3f7b390e61971543dea4451'
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  strategy:
15
15
  matrix:
16
- ruby-version: [3.1, "3.0", "2.7", "2.6", "2.5", "ruby-head"]
16
+ ruby-version: [3.2, 3.1, "3.0", "2.7", "2.6", "2.5", "ruby-head"]
17
17
 
18
18
  steps:
19
19
  - uses: actions/checkout@v2
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
- Redlock works with Redis versions 6.0 or later.
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
 
@@ -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 = RedisClient.new(connection)
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.select { |s| s.lock resource, value, ttl, allow_new_lock }.size
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
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = '2.0.1'
2
+ VERSION = '2.0.3'
3
3
  end
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(RedisClient::CannotConnectError)
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(RedisClient::CannotConnectError)
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(/NOSCRIPT/)
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.1
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-02-14 00:00:00.000000000 Z
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: '0'
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: '0'
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.2.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: