redlock 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.gitignore +1 -0
- data/Gemfile.lock +3 -7
- data/README.md +17 -4
- data/lib/redlock/client.rb +17 -39
- data/lib/redlock/testing.rb +3 -3
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +2 -3
- data/spec/client_spec.rb +18 -38
- data/spec/spec_helper.rb +1 -1
- metadata +28 -48
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NzQwMDRhZWQ2ZjUyM2FjMTgxMzJiY2Q1ZmY5YzI3ODk1ZWY4NTNmMA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDliZjZlMmM3OGNmZGMyYzhlNWI3YjBlMmUyNGRmNGY0Yjk0N2FkYw==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZTZjYWUyNmI2NTliZjliODZkM2ZhYzczZDAyM2RlM2FiY2Y5MTkxMDk4YjVk
|
10
|
+
YzVhMGJjMjBiMTM4NDYzY2ZkNWNiYzRmODk5YWFlZTJlMzczMTZlMzI4ZmRh
|
11
|
+
YTNkZDNhNjgxNzcxOTlhNGE2YTdjNzgyZTgwNTY0YWY4OTUwMjc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDllNDgxMmQ0ODk1NTdlNTNkMjczN2EyYjQyMjE2MWU5YmJmMDVlMmJkY2Q1
|
14
|
+
YTZjNTE5NjFjNTI3Mjc3NzJiZDBlMDIwYzUwM2NlMGM1YjE0NDVkNDIzODFm
|
15
|
+
MjVhNjU3OTI4NzY0YzY3OTE1ZDhiNGRhNmRlMzM5OGE3YjczMTI=
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
redlock (0.1.
|
5
|
-
redis (~> 3, >= 3.
|
4
|
+
redlock (0.1.8)
|
5
|
+
redis (~> 3, >= 3.0.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
@@ -45,11 +45,7 @@ PLATFORMS
|
|
45
45
|
ruby
|
46
46
|
|
47
47
|
DEPENDENCIES
|
48
|
-
bundler (~> 1.12, >= 1.12.3)
|
49
48
|
coveralls (~> 0.8.13)
|
50
49
|
rake (~> 11.1, >= 11.1.2)
|
51
50
|
redlock!
|
52
|
-
rspec (~> 3
|
53
|
-
|
54
|
-
BUNDLED WITH
|
55
|
-
1.12.3
|
51
|
+
rspec (~> 3, >= 3.0.0)
|
data/README.md
CHANGED
@@ -95,21 +95,34 @@ rescue Redlock::LockError
|
|
95
95
|
end
|
96
96
|
```
|
97
97
|
|
98
|
-
To extend the life of the lock
|
98
|
+
To extend the life of the lock:
|
99
99
|
|
100
100
|
```ruby
|
101
101
|
begin
|
102
102
|
block_result = lock_manager.lock!("resource_key", 2000) do |lock_info|
|
103
103
|
# critical code
|
104
|
-
lock_manager.
|
104
|
+
lock_manager.lock("resource key", 3000, extend: lock_info)
|
105
105
|
# more critical code
|
106
106
|
end
|
107
107
|
rescue Redlock::LockError
|
108
108
|
# error handling
|
109
109
|
end
|
110
110
|
```
|
111
|
-
|
112
|
-
|
111
|
+
|
112
|
+
The above code will also acquire the lock if the previous lock has expired and the lock is currently free. Keep in mind that this means the lock could have been acquired by someone else in the meantime. To only extend the life of the lock if currently locked by yourself, use `extend_life` parameter:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
begin
|
116
|
+
block_result = lock_manager.lock!("resource_key", 2000) do |lock_info|
|
117
|
+
# critical code
|
118
|
+
lock_manager.lock("resource key", 3000, extend: lock_info, extend_life: true)
|
119
|
+
# more critical code, only if lock was still hold
|
120
|
+
end
|
121
|
+
rescue Redlock::LockError
|
122
|
+
# error handling
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
113
126
|
|
114
127
|
## Run tests
|
115
128
|
|
data/lib/redlock/client.rb
CHANGED
@@ -37,8 +37,8 @@ module Redlock
|
|
37
37
|
# +extend+: A lock ("lock_info") to extend.
|
38
38
|
# +block+:: an optional block to be executed; after its execution, the lock (if successfully
|
39
39
|
# acquired) is automatically unlocked.
|
40
|
-
def lock(resource, ttl,
|
41
|
-
lock_info = try_lock_instances(resource, ttl,
|
40
|
+
def lock(resource, ttl, options = {}, &block)
|
41
|
+
lock_info = try_lock_instances(resource, ttl, options)
|
42
42
|
|
43
43
|
if block_given?
|
44
44
|
begin
|
@@ -52,30 +52,6 @@ module Redlock
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def extend_life(to_extend, ttl)
|
56
|
-
value = to_extend.fetch(:value)
|
57
|
-
resource = to_extend.fetch(:resource)
|
58
|
-
|
59
|
-
|
60
|
-
extended, time_elapsed = timed do
|
61
|
-
@servers.all? { |s| s.extend_life(resource, value, ttl) }
|
62
|
-
end
|
63
|
-
|
64
|
-
validity = ttl - time_elapsed - drift(ttl)
|
65
|
-
|
66
|
-
if extended
|
67
|
-
{ validity: validity, resource: resource, value: value }
|
68
|
-
else
|
69
|
-
@servers.each { |s| s.unlock(resource, value) }
|
70
|
-
false
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def extend_life!(to_extend, ttl)
|
75
|
-
new_lock_info = self.extend_life(to_extend, ttl)
|
76
|
-
raise LockError, 'failed to extend lock' unless new_lock_info
|
77
|
-
end
|
78
|
-
|
79
55
|
# Unlocks a resource.
|
80
56
|
# Params:
|
81
57
|
# +lock_info+:: the lock that has been acquired when you locked the resource.
|
@@ -86,10 +62,10 @@ module Redlock
|
|
86
62
|
# Locks a resource, executing the received block only after successfully acquiring the lock,
|
87
63
|
# and returning its return value as a result.
|
88
64
|
# See Redlock::Client#lock for parameters.
|
89
|
-
def lock!(*args
|
65
|
+
def lock!(*args)
|
90
66
|
fail 'No block passed' unless block_given?
|
91
67
|
|
92
|
-
lock(*args
|
68
|
+
lock(*args) do |lock_info|
|
93
69
|
raise LockError, 'failed to acquire lock' unless lock_info
|
94
70
|
return yield
|
95
71
|
end
|
@@ -115,7 +91,7 @@ module Redlock
|
|
115
91
|
end
|
116
92
|
eos
|
117
93
|
|
118
|
-
|
94
|
+
EXTEND_LIFE_SCRIPT = <<-eos
|
119
95
|
if redis.call("get", KEYS[1]) == ARGV[1] then
|
120
96
|
redis.call("expire", KEYS[1], ARGV[2])
|
121
97
|
return 0
|
@@ -124,7 +100,6 @@ module Redlock
|
|
124
100
|
end
|
125
101
|
eos
|
126
102
|
|
127
|
-
|
128
103
|
def initialize(connection)
|
129
104
|
if connection.respond_to?(:client)
|
130
105
|
@redis = connection
|
@@ -141,9 +116,9 @@ module Redlock
|
|
141
116
|
end
|
142
117
|
end
|
143
118
|
|
144
|
-
def
|
119
|
+
def extend(resource, val, ttl)
|
145
120
|
recover_from_script_flush do
|
146
|
-
rc = @redis.evalsha @
|
121
|
+
rc = @redis.evalsha @extend_life_script_sha, keys: [resource], argv: [val, ttl]
|
147
122
|
rc == 0
|
148
123
|
end
|
149
124
|
end
|
@@ -161,7 +136,7 @@ module Redlock
|
|
161
136
|
def load_scripts
|
162
137
|
@unlock_script_sha = @redis.script(:load, UNLOCK_SCRIPT)
|
163
138
|
@lock_script_sha = @redis.script(:load, LOCK_SCRIPT)
|
164
|
-
@
|
139
|
+
@extend_life_script_sha = @redis.script(:load, EXTEND_LIFE_SCRIPT)
|
165
140
|
end
|
166
141
|
|
167
142
|
def recover_from_script_flush
|
@@ -183,9 +158,11 @@ module Redlock
|
|
183
158
|
end
|
184
159
|
end
|
185
160
|
|
186
|
-
def try_lock_instances(resource, ttl,
|
187
|
-
@retry_count
|
188
|
-
|
161
|
+
def try_lock_instances(resource, ttl, options)
|
162
|
+
tries = options[:extend] ? 1 : @retry_count
|
163
|
+
|
164
|
+
tries.times do
|
165
|
+
lock_info = lock_instances(resource, ttl, options)
|
189
166
|
return lock_info if lock_info
|
190
167
|
|
191
168
|
# Wait a random delay before retrying
|
@@ -195,11 +172,12 @@ module Redlock
|
|
195
172
|
false
|
196
173
|
end
|
197
174
|
|
198
|
-
def lock_instances(resource, ttl,
|
199
|
-
value
|
175
|
+
def lock_instances(resource, ttl, options)
|
176
|
+
value = options[:extend] ? options[:extend].fetch(:value) : SecureRandom.uuid
|
177
|
+
method = options[:extend_life] ? :extend : :lock
|
200
178
|
|
201
179
|
locked, time_elapsed = timed do
|
202
|
-
@servers.select { |s| s.
|
180
|
+
@servers.select { |s| s.send(method, resource, value, ttl) }.size
|
203
181
|
end
|
204
182
|
|
205
183
|
validity = ttl - time_elapsed - drift(ttl)
|
data/lib/redlock/testing.rb
CHANGED
@@ -4,17 +4,17 @@ module Redlock
|
|
4
4
|
|
5
5
|
alias_method :try_lock_instances_without_testing, :try_lock_instances
|
6
6
|
|
7
|
-
def try_lock_instances(resource, ttl,
|
7
|
+
def try_lock_instances(resource, ttl, options)
|
8
8
|
if @testing_mode == :bypass
|
9
9
|
{
|
10
10
|
validity: ttl,
|
11
11
|
resource: resource,
|
12
|
-
value: extend ? extend.fetch(:value) : SecureRandom.uuid
|
12
|
+
value: options[:extend] ? options[:extend].fetch(:value) : SecureRandom.uuid
|
13
13
|
}
|
14
14
|
elsif @testing_mode == :fail
|
15
15
|
false
|
16
16
|
else
|
17
|
-
try_lock_instances_without_testing resource, ttl,
|
17
|
+
try_lock_instances_without_testing resource, ttl, options
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
data/lib/redlock/version.rb
CHANGED
data/redlock.gemspec
CHANGED
@@ -18,10 +18,9 @@ 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', '~> 3', '>= 3.
|
21
|
+
spec.add_dependency 'redis', '~> 3', '>= 3.0.0'
|
22
22
|
|
23
|
-
spec.add_development_dependency 'bundler', '~> 1.12', '>= 1.12.3'
|
24
23
|
spec.add_development_dependency "coveralls", "~> 0.8.13"
|
25
24
|
spec.add_development_dependency 'rake', '~> 11.1', '>= 11.1.2'
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3', '>= 3.0.0'
|
27
26
|
end
|
data/spec/client_spec.rb
CHANGED
@@ -43,10 +43,19 @@ RSpec.describe Redlock::Client do
|
|
43
43
|
expect(@lock_info[:value]).to eq(my_lock_info[:value])
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
context 'when extend_life flag is given' do
|
47
|
+
it 'does not extend a non-existent lock' do
|
48
|
+
@lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_life: true)
|
49
|
+
expect(@lock_info).to eq(false)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when extend_life flag is not given' do
|
54
|
+
it "sets the given value when trying to extend a non-existent lock" do
|
55
|
+
@lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_life: false)
|
56
|
+
expect(@lock_info).to be_lock_info_for(resource_key)
|
57
|
+
expect(@lock_info[:value]).to eq('hello world') # really we should test what's in redis
|
58
|
+
end
|
50
59
|
end
|
51
60
|
|
52
61
|
it "doesn't extend somebody else's lock" do
|
@@ -170,40 +179,6 @@ RSpec.describe Redlock::Client do
|
|
170
179
|
end
|
171
180
|
end
|
172
181
|
|
173
|
-
describe "extend" do
|
174
|
-
context 'when lock is available' do
|
175
|
-
before { @lock_info = lock_manager.lock(resource_key, ttl) }
|
176
|
-
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
177
|
-
|
178
|
-
it 'can extend its own lock' do
|
179
|
-
lock_info = lock_manager.extend_life(@lock_info, ttl)
|
180
|
-
expect(lock_info).to be_lock_info_for(resource_key)
|
181
|
-
end
|
182
|
-
|
183
|
-
it "can't extend a nonexistent lock" do
|
184
|
-
lock_manager.unlock(@lock_info)
|
185
|
-
lock_info = lock_manager.extend_life(@lock_info, ttl)
|
186
|
-
expect(lock_info).to eq(false)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
describe "extend!" do
|
192
|
-
context 'when lock is available' do
|
193
|
-
before { @lock_info = lock_manager.lock(resource_key, ttl) }
|
194
|
-
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
195
|
-
|
196
|
-
it 'can extend its own lock' do
|
197
|
-
expect{ lock_manager.extend_life!(@lock_info, ttl) }.to_not raise_error
|
198
|
-
end
|
199
|
-
|
200
|
-
it "can't extend a nonexistent lock" do
|
201
|
-
lock_manager.unlock(@lock_info)
|
202
|
-
expect{ lock_manager.extend_life!(@lock_info, ttl) }.to raise_error(Redlock::LockError)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
182
|
describe 'lock!' do
|
208
183
|
context 'when lock is available' do
|
209
184
|
it 'locks' do
|
@@ -226,6 +201,11 @@ RSpec.describe Redlock::Client do
|
|
226
201
|
lock_manager.lock!(resource_key, ttl) { fail } rescue nil
|
227
202
|
expect(resource_key).to be_lockable(lock_manager, ttl)
|
228
203
|
end
|
204
|
+
|
205
|
+
it 'passes the extension parameter' do
|
206
|
+
my_lock_info = lock_manager.lock(resource_key, ttl)
|
207
|
+
expect{ lock_manager.lock!(resource_key, ttl, extend: my_lock_info){} }.to_not raise_error
|
208
|
+
end
|
229
209
|
end
|
230
210
|
|
231
211
|
context 'when lock is not available' do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,109 +1,89 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redlock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leandro Moreira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '3'
|
20
|
-
- -
|
20
|
+
- - ! '>='
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 3.
|
22
|
+
version: 3.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '3'
|
30
|
-
- -
|
30
|
+
- - ! '>='
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 3.
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: bundler
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '1.12'
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: 1.12.3
|
43
|
-
type: :development
|
44
|
-
prerelease: false
|
45
|
-
version_requirements: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '1.12'
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: 1.12.3
|
32
|
+
version: 3.0.0
|
53
33
|
- !ruby/object:Gem::Dependency
|
54
34
|
name: coveralls
|
55
35
|
requirement: !ruby/object:Gem::Requirement
|
56
36
|
requirements:
|
57
|
-
- -
|
37
|
+
- - ~>
|
58
38
|
- !ruby/object:Gem::Version
|
59
39
|
version: 0.8.13
|
60
40
|
type: :development
|
61
41
|
prerelease: false
|
62
42
|
version_requirements: !ruby/object:Gem::Requirement
|
63
43
|
requirements:
|
64
|
-
- -
|
44
|
+
- - ~>
|
65
45
|
- !ruby/object:Gem::Version
|
66
46
|
version: 0.8.13
|
67
47
|
- !ruby/object:Gem::Dependency
|
68
48
|
name: rake
|
69
49
|
requirement: !ruby/object:Gem::Requirement
|
70
50
|
requirements:
|
71
|
-
- -
|
51
|
+
- - ~>
|
72
52
|
- !ruby/object:Gem::Version
|
73
53
|
version: '11.1'
|
74
|
-
- -
|
54
|
+
- - ! '>='
|
75
55
|
- !ruby/object:Gem::Version
|
76
56
|
version: 11.1.2
|
77
57
|
type: :development
|
78
58
|
prerelease: false
|
79
59
|
version_requirements: !ruby/object:Gem::Requirement
|
80
60
|
requirements:
|
81
|
-
- -
|
61
|
+
- - ~>
|
82
62
|
- !ruby/object:Gem::Version
|
83
63
|
version: '11.1'
|
84
|
-
- -
|
64
|
+
- - ! '>='
|
85
65
|
- !ruby/object:Gem::Version
|
86
66
|
version: 11.1.2
|
87
67
|
- !ruby/object:Gem::Dependency
|
88
68
|
name: rspec
|
89
69
|
requirement: !ruby/object:Gem::Requirement
|
90
70
|
requirements:
|
91
|
-
- -
|
71
|
+
- - ~>
|
92
72
|
- !ruby/object:Gem::Version
|
93
|
-
version: '3
|
94
|
-
- -
|
73
|
+
version: '3'
|
74
|
+
- - ! '>='
|
95
75
|
- !ruby/object:Gem::Version
|
96
|
-
version: 3.
|
76
|
+
version: 3.0.0
|
97
77
|
type: :development
|
98
78
|
prerelease: false
|
99
79
|
version_requirements: !ruby/object:Gem::Requirement
|
100
80
|
requirements:
|
101
|
-
- -
|
81
|
+
- - ~>
|
102
82
|
- !ruby/object:Gem::Version
|
103
|
-
version: '3
|
104
|
-
- -
|
83
|
+
version: '3'
|
84
|
+
- - ! '>='
|
105
85
|
- !ruby/object:Gem::Version
|
106
|
-
version: 3.
|
86
|
+
version: 3.0.0
|
107
87
|
description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
|
108
88
|
email:
|
109
89
|
- leandro.ribeiro.moreira@gmail.com
|
@@ -111,9 +91,9 @@ executables: []
|
|
111
91
|
extensions: []
|
112
92
|
extra_rdoc_files: []
|
113
93
|
files:
|
114
|
-
-
|
115
|
-
-
|
116
|
-
-
|
94
|
+
- .gitignore
|
95
|
+
- .rspec
|
96
|
+
- .travis.yml
|
117
97
|
- CONTRIBUTORS
|
118
98
|
- Gemfile
|
119
99
|
- Gemfile.lock
|
@@ -138,17 +118,17 @@ require_paths:
|
|
138
118
|
- lib
|
139
119
|
required_ruby_version: !ruby/object:Gem::Requirement
|
140
120
|
requirements:
|
141
|
-
- -
|
121
|
+
- - ! '>='
|
142
122
|
- !ruby/object:Gem::Version
|
143
123
|
version: '0'
|
144
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
125
|
requirements:
|
146
|
-
- -
|
126
|
+
- - ! '>='
|
147
127
|
- !ruby/object:Gem::Version
|
148
128
|
version: '0'
|
149
129
|
requirements: []
|
150
130
|
rubyforge_project:
|
151
|
-
rubygems_version: 2.6
|
131
|
+
rubygems_version: 2.4.6
|
152
132
|
signing_key:
|
153
133
|
specification_version: 4
|
154
134
|
summary: Distributed lock using Redis written in Ruby.
|