redlock 2.0.6 → 2.1.0
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/Makefile +3 -3
- data/lib/redlock/client.rb +15 -1
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +11 -10
- data/spec/client_spec.rb +105 -7
- metadata +7 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 229c8a5a2a6c074810df969cd50e20bac41f8bf47e3d5714fe21975888702fb0
|
|
4
|
+
data.tar.gz: d9e1079662c2ef7ddd23d3e03696c48d6b9ae7fe37c0b65760c3a90be07bc239
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a3436508b649c79682b310ff57094aa06a532ebde60b193d720dcc88116166a9bacc5e60701515528ef9cf8b7946e246c2bc8341a75eb948cc23ecd1a7be8ff
|
|
7
|
+
data.tar.gz: d2fc1c15c559a684f87b08ccd1e7e3adc9988323111803c1ca66f2df5381890c09802218dc86477e1de2099bd7aafca1e5e368c9f15fd85ea9f5d433846c0928
|
data/.github/workflows/ci.yml
CHANGED
data/Makefile
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
default: test
|
|
2
2
|
test:
|
|
3
|
-
docker
|
|
3
|
+
docker compose run --rm test
|
|
4
4
|
|
|
5
5
|
build:
|
|
6
|
-
docker
|
|
6
|
+
docker compose run --rm test gem build redlock.gemspec
|
|
7
7
|
|
|
8
8
|
publish:
|
|
9
|
-
docker
|
|
9
|
+
docker compose run --rm test gem push `ls -lt *gem | head -n 1 | awk '{ print $$9 }'`
|
data/lib/redlock/client.rb
CHANGED
|
@@ -244,11 +244,25 @@ module Redlock
|
|
|
244
244
|
end
|
|
245
245
|
end
|
|
246
246
|
|
|
247
|
+
# Exception classes that may be raised for NOSCRIPT errors.
|
|
248
|
+
# RedisClient::CommandError is raised when using redis-client directly.
|
|
249
|
+
# Redis::CommandError (or its subclass Redis::NoScriptError) is raised
|
|
250
|
+
# when using the redis gem wrapper.
|
|
251
|
+
# See: https://github.com/leandromoreira/redlock-rb/issues/124
|
|
252
|
+
# See: https://github.com/leandromoreira/redlock-rb/issues/148
|
|
253
|
+
def script_error_classes
|
|
254
|
+
@script_error_classes ||= begin
|
|
255
|
+
classes = [RedisClient::CommandError]
|
|
256
|
+
classes << Redis::CommandError if defined?(Redis::CommandError)
|
|
257
|
+
classes
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
247
261
|
def recover_from_script_flush
|
|
248
262
|
retry_on_noscript = true
|
|
249
263
|
begin
|
|
250
264
|
yield
|
|
251
|
-
rescue
|
|
265
|
+
rescue *script_error_classes => e
|
|
252
266
|
# When somebody has flushed the Redis instance's script cache, we might
|
|
253
267
|
# want to reload our scripts. Only attempt this once, though, to avoid
|
|
254
268
|
# going into an infinite loop.
|
data/lib/redlock/version.rb
CHANGED
data/redlock.gemspec
CHANGED
|
@@ -5,14 +5,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
5
5
|
require 'redlock/version'
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
|
-
spec.name
|
|
9
|
-
spec.version
|
|
10
|
-
spec.authors
|
|
11
|
-
spec.email
|
|
12
|
-
spec.summary
|
|
13
|
-
spec.description
|
|
14
|
-
spec.homepage
|
|
15
|
-
spec.license
|
|
8
|
+
spec.name = 'redlock'
|
|
9
|
+
spec.version = Redlock::VERSION
|
|
10
|
+
spec.authors = ['Leandro Moreira']
|
|
11
|
+
spec.email = ['leandro.ribeiro.moreira@gmail.com']
|
|
12
|
+
spec.summary = 'Distributed lock using Redis written in Ruby.'
|
|
13
|
+
spec.description = 'Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.'
|
|
14
|
+
spec.homepage = 'https://github.com/leandromoreira/redlock-rb'
|
|
15
|
+
spec.license = 'BSD-2-Clause'
|
|
16
|
+
spec.required_ruby_version = '>= 2.7.0'
|
|
16
17
|
|
|
17
18
|
spec.files = `git ls-files -z`.split("\x0")
|
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
@@ -21,8 +22,8 @@ Gem::Specification.new do |spec|
|
|
|
21
22
|
spec.add_dependency 'redis-client', '>= 0.14.1', '< 1.0.0'
|
|
22
23
|
|
|
23
24
|
spec.add_development_dependency 'connection_pool', '~> 2.2'
|
|
24
|
-
spec.add_development_dependency '
|
|
25
|
-
spec.add_development_dependency 'json', '>= 2.3.0'
|
|
25
|
+
spec.add_development_dependency 'coveralls_reborn', '~> 0.29'
|
|
26
|
+
spec.add_development_dependency 'json', '>= 2.3.0'
|
|
26
27
|
spec.add_development_dependency 'rake', '>= 11.1.2', '~> 13.0'
|
|
27
28
|
spec.add_development_dependency 'rspec', '~> 3', '>= 3.0.0'
|
|
28
29
|
end
|
data/spec/client_spec.rb
CHANGED
|
@@ -108,17 +108,13 @@ RSpec.describe Redlock::Client do
|
|
|
108
108
|
it 'fails to acquire a lock if majority of Redis instances are not available' do
|
|
109
109
|
redlock = Redlock::Client.new(servers_without_quorum)
|
|
110
110
|
|
|
111
|
-
expected_msg = <<~MSG
|
|
112
|
-
failed to acquire lock on 'Too many Redis errors prevented lock acquisition:
|
|
113
|
-
RedisClient::CannotConnectError: Connection refused - connect(2) for 127.0.0.1:46864
|
|
114
|
-
RedisClient::CannotConnectError: Connection refused - connect(2) for 127.0.0.1:46864'
|
|
115
|
-
MSG
|
|
116
|
-
|
|
117
111
|
expect {
|
|
118
112
|
redlock.lock(resource_key, ttl)
|
|
119
113
|
}.to raise_error do |error|
|
|
120
114
|
expect(error).to be_a(Redlock::LockAcquisitionError)
|
|
121
|
-
expect(error.message).to
|
|
115
|
+
expect(error.message).to include('Too many Redis errors prevented lock acquisition')
|
|
116
|
+
expect(error.message).to include('RedisClient::CannotConnectError')
|
|
117
|
+
expect(error.message).to include('Connection refused')
|
|
122
118
|
expect(error.errors.size).to eq(2)
|
|
123
119
|
end
|
|
124
120
|
end
|
|
@@ -381,6 +377,108 @@ RSpec.describe Redlock::Client do
|
|
|
381
377
|
end
|
|
382
378
|
end
|
|
383
379
|
|
|
380
|
+
context 'when using the redis gem (Redis::CommandError)' do
|
|
381
|
+
# The redis gem raises Redis::CommandError (or Redis::NoScriptError) instead of
|
|
382
|
+
# RedisClient::CommandError. This tests that both error hierarchies are handled.
|
|
383
|
+
# See: https://github.com/leandromoreira/redlock-rb/issues/124
|
|
384
|
+
# See: https://github.com/leandromoreira/redlock-rb/issues/148
|
|
385
|
+
|
|
386
|
+
before(:all) do
|
|
387
|
+
# Define Redis::CommandError if not already defined (simulates redis gem being loaded)
|
|
388
|
+
unless defined?(Redis::CommandError)
|
|
389
|
+
module Redis
|
|
390
|
+
class CommandError < StandardError; end
|
|
391
|
+
end
|
|
392
|
+
@redis_command_error_defined_by_test = true
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
after(:all) do
|
|
397
|
+
# Clean up the mock class if we defined it
|
|
398
|
+
if @redis_command_error_defined_by_test
|
|
399
|
+
Redis.send(:remove_const, :CommandError)
|
|
400
|
+
Object.send(:remove_const, :Redis) if Redis.constants.empty?
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
describe '#script_error_classes' do
|
|
405
|
+
it 'includes RedisClient::CommandError' do
|
|
406
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
|
407
|
+
expect(redis_instance.send(:script_error_classes)).to include(RedisClient::CommandError)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
it 'includes Redis::CommandError when defined' do
|
|
411
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
|
412
|
+
# Clear memoization to pick up the newly defined class
|
|
413
|
+
redis_instance.instance_variable_set(:@script_error_classes, nil)
|
|
414
|
+
expect(redis_instance.send(:script_error_classes)).to include(Redis::CommandError)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
context 'when Redis::CommandError NOSCRIPT is raised' do
|
|
419
|
+
it 'recovers by reloading scripts' do
|
|
420
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
|
421
|
+
# Clear memoization to ensure Redis::CommandError is included
|
|
422
|
+
redis_instance.instance_variable_set(:@script_error_classes, nil)
|
|
423
|
+
|
|
424
|
+
call_count = 0
|
|
425
|
+
allow(redis_instance).to receive(:synchronize).and_wrap_original do |original_method, &block|
|
|
426
|
+
call_count += 1
|
|
427
|
+
if call_count == 1
|
|
428
|
+
# First call raises Redis::CommandError with NOSCRIPT
|
|
429
|
+
raise Redis::CommandError, 'NOSCRIPT No matching script'
|
|
430
|
+
else
|
|
431
|
+
original_method.call(&block)
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
expect(redis_instance).to receive(:load_scripts).and_call_original
|
|
436
|
+
|
|
437
|
+
# Should recover and successfully lock
|
|
438
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
|
439
|
+
expect(lock_info).to be_truthy
|
|
440
|
+
lock_manager.unlock(lock_info) if lock_info
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
it 'does not retry more than once per operation' do
|
|
444
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
|
445
|
+
redis_instance.instance_variable_set(:@script_error_classes, nil)
|
|
446
|
+
|
|
447
|
+
# Always raise Redis::CommandError NOSCRIPT
|
|
448
|
+
allow(redis_instance).to receive(:synchronize).and_raise(
|
|
449
|
+
Redis::CommandError, 'NOSCRIPT No matching script'
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
load_scripts_count = 0
|
|
453
|
+
allow(redis_instance).to receive(:load_scripts) { load_scripts_count += 1 }
|
|
454
|
+
|
|
455
|
+
expect {
|
|
456
|
+
lock_manager.lock(resource_key, ttl)
|
|
457
|
+
}.to raise_error(Redlock::LockAcquisitionError)
|
|
458
|
+
|
|
459
|
+
# Should have called load_scripts once per retry attempt (8 times total:
|
|
460
|
+
# lock + unlock for each of retry_count+1 attempts, but unlock swallows errors)
|
|
461
|
+
expect(load_scripts_count).to eq(8)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
it 're-raises non-NOSCRIPT Redis::CommandError' do
|
|
465
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
|
466
|
+
redis_instance.instance_variable_set(:@script_error_classes, nil)
|
|
467
|
+
|
|
468
|
+
allow(redis_instance).to receive(:synchronize).and_raise(
|
|
469
|
+
Redis::CommandError, 'ERR some other error'
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
expect {
|
|
473
|
+
lock_manager.lock(resource_key, ttl)
|
|
474
|
+
}.to raise_error(Redlock::LockAcquisitionError) do |e|
|
|
475
|
+
expect(e.errors[0]).to be_a(Redis::CommandError)
|
|
476
|
+
expect(e.errors[0].message).to eq('ERR some other error')
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
384
482
|
describe 'block syntax' do
|
|
385
483
|
context 'when lock is available' do
|
|
386
484
|
it 'locks' do
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redlock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leandro Moreira
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: redis-client
|
|
@@ -45,19 +44,19 @@ dependencies:
|
|
|
45
44
|
- !ruby/object:Gem::Version
|
|
46
45
|
version: '2.2'
|
|
47
46
|
- !ruby/object:Gem::Dependency
|
|
48
|
-
name:
|
|
47
|
+
name: coveralls_reborn
|
|
49
48
|
requirement: !ruby/object:Gem::Requirement
|
|
50
49
|
requirements:
|
|
51
50
|
- - "~>"
|
|
52
51
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0.
|
|
52
|
+
version: '0.29'
|
|
54
53
|
type: :development
|
|
55
54
|
prerelease: false
|
|
56
55
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
56
|
requirements:
|
|
58
57
|
- - "~>"
|
|
59
58
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
59
|
+
version: '0.29'
|
|
61
60
|
- !ruby/object:Gem::Dependency
|
|
62
61
|
name: json
|
|
63
62
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,9 +64,6 @@ dependencies:
|
|
|
65
64
|
- - ">="
|
|
66
65
|
- !ruby/object:Gem::Version
|
|
67
66
|
version: 2.3.0
|
|
68
|
-
- - "~>"
|
|
69
|
-
- !ruby/object:Gem::Version
|
|
70
|
-
version: 2.3.1
|
|
71
67
|
type: :development
|
|
72
68
|
prerelease: false
|
|
73
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -75,9 +71,6 @@ dependencies:
|
|
|
75
71
|
- - ">="
|
|
76
72
|
- !ruby/object:Gem::Version
|
|
77
73
|
version: 2.3.0
|
|
78
|
-
- - "~>"
|
|
79
|
-
- !ruby/object:Gem::Version
|
|
80
|
-
version: 2.3.1
|
|
81
74
|
- !ruby/object:Gem::Dependency
|
|
82
75
|
name: rake
|
|
83
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -150,7 +143,6 @@ homepage: https://github.com/leandromoreira/redlock-rb
|
|
|
150
143
|
licenses:
|
|
151
144
|
- BSD-2-Clause
|
|
152
145
|
metadata: {}
|
|
153
|
-
post_install_message:
|
|
154
146
|
rdoc_options: []
|
|
155
147
|
require_paths:
|
|
156
148
|
- lib
|
|
@@ -158,15 +150,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
158
150
|
requirements:
|
|
159
151
|
- - ">="
|
|
160
152
|
- !ruby/object:Gem::Version
|
|
161
|
-
version:
|
|
153
|
+
version: 2.7.0
|
|
162
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
155
|
requirements:
|
|
164
156
|
- - ">="
|
|
165
157
|
- !ruby/object:Gem::Version
|
|
166
158
|
version: '0'
|
|
167
159
|
requirements: []
|
|
168
|
-
rubygems_version:
|
|
169
|
-
signing_key:
|
|
160
|
+
rubygems_version: 4.0.3
|
|
170
161
|
specification_version: 4
|
|
171
162
|
summary: Distributed lock using Redis written in Ruby.
|
|
172
163
|
test_files:
|