redlock 2.0.5 → 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 +17 -2
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +11 -10
- data/spec/client_spec.rb +109 -1
- 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.
|
|
@@ -319,7 +333,8 @@ module Redlock
|
|
|
319
333
|
@servers.each { |s| s.unlock(resource, value) }
|
|
320
334
|
|
|
321
335
|
if errors.size >= @quorum
|
|
322
|
-
|
|
336
|
+
err_msgs = errors.map { |e| "#{e.class}: #{e.message}" }.join("\n")
|
|
337
|
+
raise LockAcquisitionError.new("Too many Redis errors prevented lock acquisition:\n#{err_msgs}", errors)
|
|
323
338
|
end
|
|
324
339
|
|
|
325
340
|
false
|
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
|
@@ -110,7 +110,13 @@ RSpec.describe Redlock::Client do
|
|
|
110
110
|
|
|
111
111
|
expect {
|
|
112
112
|
redlock.lock(resource_key, ttl)
|
|
113
|
-
}.to raise_error
|
|
113
|
+
}.to raise_error do |error|
|
|
114
|
+
expect(error).to be_a(Redlock::LockAcquisitionError)
|
|
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')
|
|
118
|
+
expect(error.errors.size).to eq(2)
|
|
119
|
+
end
|
|
114
120
|
end
|
|
115
121
|
end
|
|
116
122
|
|
|
@@ -371,6 +377,108 @@ RSpec.describe Redlock::Client do
|
|
|
371
377
|
end
|
|
372
378
|
end
|
|
373
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
|
+
|
|
374
482
|
describe 'block syntax' do
|
|
375
483
|
context 'when lock is available' do
|
|
376
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:
|