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