redlock 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/Gemfile.lock +34 -37
- data/Makefile +4 -0
- data/README.md +50 -5
- data/clean_docker.sh +26 -0
- data/docker-compose.yml +27 -0
- data/lib/redlock/client.rb +31 -21
- data/lib/redlock/testing.rb +3 -3
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +4 -5
- data/spec/client_spec.rb +55 -8
- data/spec/spec_helper.rb +1 -1
- metadata +43 -42
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZmQ4YmY4ODEzZDZlOWVmNmZlYzBmMTFiZDIzNjg2NDk1NWYyYmEzZg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8e34b717846f5f8faa005ab9a8d5cac3c3ea865e
|
4
|
+
data.tar.gz: f7a615768b2fc2746d16128122a69259c252b78f
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NDI1YTRmYTkyYjc4ZDY1OTc3ZmFjODI5Y2UwYzVjYzI1MTcyYmQ0Y2M2MGY3
|
11
|
-
MTAxMDRlMWFmMjcyMjI2ZDExMDU3MTI0ODU0N2UyNjQyMjIwNGM=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZWI3MzI4NjQzZjEyZGIyMTU0OTA3YTIxMzIxMTFiZjExYzAzNTMwMTgzZDdm
|
14
|
-
OWY5MzFiMWIwOWRhNmUwNzVjY2M5MzI5M2JiMmUyMzUwNjM0MzI0YWYxZWQy
|
15
|
-
Y2ZkZTJkMzdhOWU1OTE0MTFlNTcxZjNmNWMzN2QyZDgzZmZlNmQ=
|
6
|
+
metadata.gz: ced9a5040b0fe2b0d2ea947732495c06a647b8df04cb5cda89b3f82731d76f79446afda7dd86d21b6705c67f6ccfc42f5c44fa16b753d09abd242d590172fe63
|
7
|
+
data.tar.gz: 0ce3c67e5f9b81d2f8381daf9708c728f8ceaa6a3367e3153586c4621748ffcef717595333b184e7da08932be7e023dfab549b78040d064b079d762a173e800b
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,57 +1,54 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
redlock (0.1
|
5
|
-
redis (
|
4
|
+
redlock (0.2.1)
|
5
|
+
redis (>= 3.0.0, < 5.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
coveralls (0.
|
11
|
-
|
12
|
-
|
13
|
-
simplecov (~> 0.9.1)
|
10
|
+
coveralls (0.8.19)
|
11
|
+
json (>= 1.8, < 3)
|
12
|
+
simplecov (~> 0.12.0)
|
14
13
|
term-ansicolor (~> 1.3)
|
15
14
|
thor (~> 0.19.1)
|
16
|
-
|
15
|
+
tins (~> 1.6)
|
16
|
+
diff-lcs (1.3)
|
17
17
|
docile (1.1.5)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
rspec-expectations (~> 3.2.0)
|
29
|
-
rspec-mocks (~> 3.2.0)
|
30
|
-
rspec-core (3.2.2)
|
31
|
-
rspec-support (~> 3.2.0)
|
32
|
-
rspec-expectations (3.2.0)
|
18
|
+
json (2.0.3)
|
19
|
+
rake (11.3.0)
|
20
|
+
redis (4.0.1)
|
21
|
+
rspec (3.5.0)
|
22
|
+
rspec-core (~> 3.5.0)
|
23
|
+
rspec-expectations (~> 3.5.0)
|
24
|
+
rspec-mocks (~> 3.5.0)
|
25
|
+
rspec-core (3.5.4)
|
26
|
+
rspec-support (~> 3.5.0)
|
27
|
+
rspec-expectations (3.5.0)
|
33
28
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
-
rspec-support (~> 3.
|
35
|
-
rspec-mocks (3.
|
29
|
+
rspec-support (~> 3.5.0)
|
30
|
+
rspec-mocks (3.5.0)
|
36
31
|
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
-
rspec-support (~> 3.
|
38
|
-
rspec-support (3.
|
39
|
-
simplecov (0.
|
32
|
+
rspec-support (~> 3.5.0)
|
33
|
+
rspec-support (3.5.0)
|
34
|
+
simplecov (0.12.0)
|
40
35
|
docile (~> 1.1.0)
|
41
|
-
|
42
|
-
simplecov-html (~> 0.
|
43
|
-
simplecov-html (0.
|
44
|
-
term-ansicolor (1.
|
36
|
+
json (>= 1.8, < 3)
|
37
|
+
simplecov-html (~> 0.10.0)
|
38
|
+
simplecov-html (0.10.0)
|
39
|
+
term-ansicolor (1.4.0)
|
45
40
|
tins (~> 1.0)
|
46
|
-
thor (0.19.
|
47
|
-
tins (1.
|
41
|
+
thor (0.19.4)
|
42
|
+
tins (1.13.0)
|
48
43
|
|
49
44
|
PLATFORMS
|
50
45
|
ruby
|
51
46
|
|
52
47
|
DEPENDENCIES
|
53
|
-
|
54
|
-
|
55
|
-
rake (~> 10.0)
|
48
|
+
coveralls (~> 0.8.13)
|
49
|
+
rake (~> 11.1, >= 11.1.2)
|
56
50
|
redlock!
|
57
|
-
rspec (~> 3.
|
51
|
+
rspec (~> 3, >= 3.0.0)
|
52
|
+
|
53
|
+
BUNDLED WITH
|
54
|
+
1.16.0
|
data/Makefile
ADDED
data/README.md
CHANGED
@@ -8,8 +8,6 @@
|
|
8
8
|
[![Inline docs](http://inch-ci.org/github/leandromoreira/redlock-rb.svg?branch=master)](http://inch-ci.org/github/leandromoreira/redlock-rb)
|
9
9
|
[![Join the chat at https://gitter.im/leandromoreira/redlock-rb](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/leandromoreira/redlock-rb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
10
10
|
|
11
|
-
[![Codeship](https://codeship.com/projects/901ff180-c1ad-0132-1a88-3eb2295b72b3/status?branch=master)](https://codeship.com/projects/901ff180-c1ad-0132-1a88-3eb2295b72b3/status?branch=master)
|
12
|
-
|
13
11
|
|
14
12
|
# Redlock - A ruby distributed lock using redis.
|
15
13
|
|
@@ -19,6 +17,10 @@
|
|
19
17
|
|
20
18
|
This is an implementation of a proposed [distributed lock algorithm with Redis](http://redis.io/topics/distlock). It started as a fork from [antirez implementation.](https://github.com/antirez/redlock-rb)
|
21
19
|
|
20
|
+
## Compatibility
|
21
|
+
|
22
|
+
Redlock works with Redis versions 2.6 or later.
|
23
|
+
|
22
24
|
## Installation
|
23
25
|
|
24
26
|
Add this line to your application's Gemfile:
|
@@ -91,15 +93,58 @@ rescue Redlock::LockError
|
|
91
93
|
end
|
92
94
|
```
|
93
95
|
|
96
|
+
To extend the life of the lock:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
begin
|
100
|
+
block_result = lock_manager.lock!("resource_key", 2000) do |lock_info|
|
101
|
+
# critical code
|
102
|
+
lock_manager.lock("resource key", 3000, extend: lock_info)
|
103
|
+
# more critical code
|
104
|
+
end
|
105
|
+
rescue Redlock::LockError
|
106
|
+
# error handling
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
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:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
begin
|
114
|
+
block_result = lock_manager.lock!("resource_key", 2000) do |lock_info|
|
115
|
+
# critical code
|
116
|
+
lock_manager.lock("resource key", 3000, extend: lock_info, extend_life: true)
|
117
|
+
# more critical code, only if lock was still hold
|
118
|
+
end
|
119
|
+
rescue Redlock::LockError
|
120
|
+
# error handling
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
It's possible to customize the retry logic providing the following options:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
lock_manager = Redlock::Client.new(
|
128
|
+
servers, {
|
129
|
+
retry_count: 3,
|
130
|
+
retry_delay: 200, # milliseconds
|
131
|
+
retry_jitter: 50, # milliseconds
|
132
|
+
redis_timeout: 0.1 # seconds
|
133
|
+
})
|
134
|
+
```
|
135
|
+
|
136
|
+
For more information you can check [documentation](http://www.rubydoc.info/gems/redlock/Redlock%2FClient:initialize).
|
137
|
+
|
138
|
+
|
94
139
|
## Run tests
|
95
140
|
|
96
|
-
Make sure you have
|
141
|
+
Make sure you have [docker installed](https://docs.docker.com/engine/installation/).
|
97
142
|
|
98
|
-
$
|
143
|
+
$ make
|
99
144
|
|
100
145
|
## Disclaimer
|
101
146
|
|
102
|
-
This code implements an algorithm which is currently a proposal, it was not formally analyzed. Make sure to understand how it works before using it in your production environments. You can see discussion about this approach at [reddit](http://www.reddit.com/r/programming/comments/2nt0nq/distributed_lock_using_redis_implemented_in_ruby/).
|
147
|
+
This code implements an algorithm which is currently a proposal, it was not formally analyzed. Make sure to understand how it works before using it in your production environments. You can see discussion about this approach at [reddit](http://www.reddit.com/r/programming/comments/2nt0nq/distributed_lock_using_redis_implemented_in_ruby/) and also the [Antirez answers](http://antirez.com/news/101) for some critics.
|
103
148
|
|
104
149
|
## Contributing
|
105
150
|
|
data/clean_docker.sh
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
EXITED=$(docker ps -q -f status=exited)
|
2
|
+
DANGLING=$(docker images -q -f "dangling=true")
|
3
|
+
DANGLING_VOLUME=$(docker volume ls -qf "dangling=true")
|
4
|
+
|
5
|
+
if [ "$1" == "--dry-run" ]; then
|
6
|
+
echo "==> Would stop containers:"
|
7
|
+
echo $EXITED
|
8
|
+
echo "==> And images:"
|
9
|
+
echo $DANGLING
|
10
|
+
else
|
11
|
+
if [ -n "$EXITED" ]; then
|
12
|
+
docker rm $EXITED
|
13
|
+
else
|
14
|
+
echo "No containers to remove."
|
15
|
+
fi
|
16
|
+
if [ -n "$DANGLING" ]; then
|
17
|
+
docker rmi $DANGLING
|
18
|
+
else
|
19
|
+
echo "No images to remove."
|
20
|
+
fi
|
21
|
+
if [ -n "$DANGLING_VOLUME" ]; then
|
22
|
+
docker volume rm $DANGLING_VOLUME
|
23
|
+
else
|
24
|
+
echo "No volumes to remove."
|
25
|
+
fi
|
26
|
+
fi
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
version: '2'
|
2
|
+
services:
|
3
|
+
test:
|
4
|
+
image: ruby
|
5
|
+
volumes:
|
6
|
+
- .:/redlock
|
7
|
+
working_dir: /redlock
|
8
|
+
command: bash -c "bundle install && rspec"
|
9
|
+
environment:
|
10
|
+
- REDIS1_HOST=redis1.local.com
|
11
|
+
- REDIS1_PORT=6379
|
12
|
+
- REDIS2_HOST=redis2.local.com
|
13
|
+
- REDIS2_PORT=6379
|
14
|
+
- DEFAULT_REDIS_HOST=redis1.local.com
|
15
|
+
- DEFAULT_REDIS_PORT=6379
|
16
|
+
links:
|
17
|
+
- redis1:redis1.local.com
|
18
|
+
- redis2:redis2.local.com
|
19
|
+
depends_on:
|
20
|
+
- redis1
|
21
|
+
- redis2
|
22
|
+
|
23
|
+
redis1:
|
24
|
+
image: redis
|
25
|
+
redis2:
|
26
|
+
image: redis
|
27
|
+
|
data/lib/redlock/client.rb
CHANGED
@@ -3,18 +3,22 @@ require 'securerandom'
|
|
3
3
|
|
4
4
|
module Redlock
|
5
5
|
class Client
|
6
|
-
|
6
|
+
DEFAULT_REDIS_HOST = ENV["DEFAULT_REDIS_HOST"] || "localhost"
|
7
|
+
DEFAULT_REDIS_PORT = ENV["DEFAULT_REDIS_PORT"] || "6379"
|
8
|
+
DEFAULT_REDIS_URLS = ["redis://#{DEFAULT_REDIS_HOST}:#{DEFAULT_REDIS_PORT}"]
|
7
9
|
DEFAULT_REDIS_TIMEOUT = 0.1
|
8
10
|
DEFAULT_RETRY_COUNT = 3
|
9
11
|
DEFAULT_RETRY_DELAY = 200
|
12
|
+
DEFAULT_RETRY_JITTER = 50
|
10
13
|
CLOCK_DRIFT_FACTOR = 0.01
|
11
14
|
|
12
15
|
# Create a distributed lock manager implementing redlock algorithm.
|
13
16
|
# Params:
|
14
17
|
# +servers+:: The array of redis connection URLs or Redis connection instances. Or a mix of both.
|
15
|
-
# +options+:: You can override the default value for `retry_count` and `
|
18
|
+
# +options+:: You can override the default value for `retry_count`, `retry_delay` and `retry_gitter`.
|
16
19
|
# * `retry_count` being how many times it'll try to lock a resource (default: 3)
|
17
20
|
# * `retry_delay` being how many ms to sleep before try to lock again (default: 200)
|
21
|
+
# * `retry_jitter` being how many ms to jitter retry delay (default: 50)
|
18
22
|
# * `redis_timeout` being how the Redis timeout will be set in seconds (default: 0.1)
|
19
23
|
def initialize(servers = DEFAULT_REDIS_URLS, options = {})
|
20
24
|
redis_timeout = options[:redis_timeout] || DEFAULT_REDIS_TIMEOUT
|
@@ -25,20 +29,23 @@ module Redlock
|
|
25
29
|
RedisInstance.new(server)
|
26
30
|
end
|
27
31
|
end
|
28
|
-
@quorum = servers.length / 2 + 1
|
32
|
+
@quorum = (servers.length / 2).to_i + 1
|
29
33
|
@retry_count = options[:retry_count] || DEFAULT_RETRY_COUNT
|
30
34
|
@retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
|
35
|
+
@retry_jitter = options[:retry_jitter] || DEFAULT_RETRY_JITTER
|
31
36
|
end
|
32
37
|
|
33
38
|
# Locks a resource for a given time.
|
34
39
|
# Params:
|
35
40
|
# +resource+:: the resource (or key) string to be locked.
|
36
41
|
# +ttl+:: The time-to-live in ms for the lock.
|
37
|
-
# +
|
42
|
+
# +options+:: Hash of optional parameters
|
43
|
+
# * +extend+: A lock ("lock_info") to extend.
|
44
|
+
# * +extend_only_if_life+: If +extend+ is given, only acquire lock if currently held
|
38
45
|
# +block+:: an optional block to be executed; after its execution, the lock (if successfully
|
39
46
|
# acquired) is automatically unlocked.
|
40
|
-
def lock(resource, ttl,
|
41
|
-
lock_info = try_lock_instances(resource, ttl,
|
47
|
+
def lock(resource, ttl, options = {}, &block)
|
48
|
+
lock_info = try_lock_instances(resource, ttl, options)
|
42
49
|
|
43
50
|
if block_given?
|
44
51
|
begin
|
@@ -62,10 +69,10 @@ module Redlock
|
|
62
69
|
# Locks a resource, executing the received block only after successfully acquiring the lock,
|
63
70
|
# and returning its return value as a result.
|
64
71
|
# See Redlock::Client#lock for parameters.
|
65
|
-
def lock!(*args
|
72
|
+
def lock!(*args)
|
66
73
|
fail 'No block passed' unless block_given?
|
67
74
|
|
68
|
-
lock(*args
|
75
|
+
lock(*args) do |lock_info|
|
69
76
|
raise LockError, 'failed to acquire lock' unless lock_info
|
70
77
|
return yield
|
71
78
|
end
|
@@ -86,7 +93,7 @@ module Redlock
|
|
86
93
|
# also https://github.com/sbertrang/redis-distlock/issues/2 which proposes the value-checking
|
87
94
|
# and @maltoe for https://github.com/leandromoreira/redlock-rb/pull/20#discussion_r38903633
|
88
95
|
LOCK_SCRIPT = <<-eos
|
89
|
-
if redis.call("exists", KEYS[1]) == 0 or redis.call("get", KEYS[1]) == ARGV[1] then
|
96
|
+
if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
|
90
97
|
return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
|
91
98
|
end
|
92
99
|
eos
|
@@ -95,15 +102,15 @@ module Redlock
|
|
95
102
|
if connection.respond_to?(:client)
|
96
103
|
@redis = connection
|
97
104
|
else
|
98
|
-
@redis
|
105
|
+
@redis = Redis.new(connection)
|
99
106
|
end
|
100
107
|
|
101
108
|
load_scripts
|
102
109
|
end
|
103
110
|
|
104
|
-
def lock(resource, val, ttl)
|
111
|
+
def lock(resource, val, ttl, allow_new_lock)
|
105
112
|
recover_from_script_flush do
|
106
|
-
@redis.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl]
|
113
|
+
@redis.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl, allow_new_lock]
|
107
114
|
end
|
108
115
|
end
|
109
116
|
|
@@ -141,23 +148,26 @@ module Redlock
|
|
141
148
|
end
|
142
149
|
end
|
143
150
|
|
144
|
-
def try_lock_instances(resource, ttl,
|
145
|
-
@retry_count
|
146
|
-
|
147
|
-
|
151
|
+
def try_lock_instances(resource, ttl, options)
|
152
|
+
tries = options[:extend] ? 1 : @retry_count
|
153
|
+
|
154
|
+
tries.times do |attempt_number|
|
155
|
+
# Wait a random delay before retrying.
|
156
|
+
sleep((@retry_delay + rand(@retry_jitter)).to_f / 1000) if attempt_number > 0
|
148
157
|
|
149
|
-
|
150
|
-
|
158
|
+
lock_info = lock_instances(resource, ttl, options)
|
159
|
+
return lock_info if lock_info
|
151
160
|
end
|
152
161
|
|
153
162
|
false
|
154
163
|
end
|
155
164
|
|
156
|
-
def lock_instances(resource, ttl,
|
157
|
-
value =
|
165
|
+
def lock_instances(resource, ttl, options)
|
166
|
+
value = options.fetch(:extend, { value: SecureRandom.uuid })[:value]
|
167
|
+
allow_new_lock = (options[:extend_life] || options[:extend_only_if_life]) ? 'no' : 'yes'
|
158
168
|
|
159
169
|
locked, time_elapsed = timed do
|
160
|
-
@servers.select { |s| s.lock
|
170
|
+
@servers.select { |s| s.lock resource, value, ttl, allow_new_lock }.size
|
161
171
|
end
|
162
172
|
|
163
173
|
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', '
|
21
|
+
spec.add_dependency 'redis', '>= 3.0.0', '< 5.0'
|
22
22
|
|
23
|
-
spec.add_development_dependency "
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency "rspec", "~> 3.1"
|
23
|
+
spec.add_development_dependency "coveralls", "~> 0.8.13"
|
24
|
+
spec.add_development_dependency 'rake', '~> 11.1', '>= 11.1.2'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3', '>= 3.0.0'
|
27
26
|
end
|
data/spec/client_spec.rb
CHANGED
@@ -3,20 +3,26 @@ require 'securerandom'
|
|
3
3
|
|
4
4
|
RSpec.describe Redlock::Client do
|
5
5
|
# It is recommended to have at least 3 servers in production
|
6
|
-
let(:
|
6
|
+
let(:lock_manager_opts) { { retry_count: 3 } }
|
7
|
+
let(:lock_manager) { Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, lock_manager_opts) }
|
7
8
|
let(:resource_key) { SecureRandom.hex(3) }
|
8
9
|
let(:ttl) { 1000 }
|
10
|
+
let(:redis1_host) { ENV["REDIS1_HOST"] || "localhost" }
|
11
|
+
let(:redis1_port) { ENV["REDIS1_PORT"] || "6379" }
|
12
|
+
let(:redis2_host) { ENV["REDIS2_HOST"] || "127.0.0.1" }
|
13
|
+
let(:redis2_port) { ENV["REDIS2_PORT"] || "6379" }
|
9
14
|
|
10
15
|
describe 'initialize' do
|
11
16
|
it 'accepts both redis URLs and Redis objects' do
|
12
|
-
|
17
|
+
print redis1_host
|
18
|
+
servers = [ "redis://#{redis1_host}:#{redis1_port}", Redis.new(url: "redis://#{redis2_host}:#{redis2_port}") ]
|
13
19
|
redlock = Redlock::Client.new(servers)
|
14
20
|
|
15
21
|
redlock_servers = redlock.instance_variable_get(:@servers).map do |s|
|
16
|
-
s.instance_variable_get(:@redis).
|
22
|
+
s.instance_variable_get(:@redis).connection[:host]
|
17
23
|
end
|
18
24
|
|
19
|
-
expect(redlock_servers).to match_array(
|
25
|
+
expect(redlock_servers).to match_array([redis1_host, redis2_host])
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
@@ -43,10 +49,19 @@ RSpec.describe Redlock::Client do
|
|
43
49
|
expect(@lock_info[:value]).to eq(my_lock_info[:value])
|
44
50
|
end
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
context 'when extend_only_if_life flag is given' do
|
53
|
+
it 'does not extend a non-existent lock' do
|
54
|
+
@lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: true)
|
55
|
+
expect(@lock_info).to eq(false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when extend_only_if_life flag is not given' do
|
60
|
+
it "sets the given value when trying to extend a non-existent lock" do
|
61
|
+
@lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: false)
|
62
|
+
expect(@lock_info).to be_lock_info_for(resource_key)
|
63
|
+
expect(@lock_info[:value]).to eq('hello world') # really we should test what's in redis
|
64
|
+
end
|
50
65
|
end
|
51
66
|
|
52
67
|
it "doesn't extend somebody else's lock" do
|
@@ -71,6 +86,33 @@ RSpec.describe Redlock::Client do
|
|
71
86
|
lock_info = lock_manager.lock(resource_key, ttl, extend: yet_another_lock_info)
|
72
87
|
expect(lock_info).to eql(false)
|
73
88
|
end
|
89
|
+
|
90
|
+
it 'retries up to \'retry_count\' times' do
|
91
|
+
expect(lock_manager).to receive(:lock_instances).exactly(
|
92
|
+
lock_manager_opts[:retry_count]).times.and_return(false)
|
93
|
+
lock_manager.lock(resource_key, ttl)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'sleeps in between retries' do
|
97
|
+
expect(lock_manager).to receive(:sleep).exactly(lock_manager_opts[:retry_count] - 1).times
|
98
|
+
lock_manager.lock(resource_key, ttl)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'sleeps at least the specified retry_delay in milliseconds' do
|
102
|
+
expected_minimum = described_class::DEFAULT_RETRY_DELAY
|
103
|
+
expect(lock_manager).to receive(:sleep) do |sleep|
|
104
|
+
expect(sleep).to satisfy { |value| value >= expected_minimum / 1000.to_f }
|
105
|
+
end.at_least(:once)
|
106
|
+
lock_manager.lock(resource_key, ttl)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'sleeps a maximum of retry_delay + retry_jitter in milliseconds' do
|
110
|
+
expected_maximum = described_class::DEFAULT_RETRY_DELAY + described_class::DEFAULT_RETRY_JITTER
|
111
|
+
expect(lock_manager).to receive(:sleep) do |sleep|
|
112
|
+
expect(sleep).to satisfy { |value| value < expected_maximum / 1000.to_f }
|
113
|
+
end.at_least(:once)
|
114
|
+
lock_manager.lock(resource_key, ttl)
|
115
|
+
end
|
74
116
|
end
|
75
117
|
|
76
118
|
context 'when script cache has been flushed' do
|
@@ -192,6 +234,11 @@ RSpec.describe Redlock::Client do
|
|
192
234
|
lock_manager.lock!(resource_key, ttl) { fail } rescue nil
|
193
235
|
expect(resource_key).to be_lockable(lock_manager, ttl)
|
194
236
|
end
|
237
|
+
|
238
|
+
it 'passes the extension parameter' do
|
239
|
+
my_lock_info = lock_manager.lock(resource_key, ttl)
|
240
|
+
expect{ lock_manager.lock!(resource_key, ttl, extend: my_lock_info){} }.to_not raise_error
|
241
|
+
end
|
195
242
|
end
|
196
243
|
|
197
244
|
context 'when lock is not available' do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,91 +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.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leandro Moreira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-11-15 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
|
-
version:
|
20
|
-
- -
|
19
|
+
version: 3.0.0
|
20
|
+
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: '5.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
|
-
version:
|
30
|
-
- -
|
29
|
+
version: 3.0.0
|
30
|
+
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
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.7'
|
40
|
-
type: :development
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - ~>
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '1.7'
|
32
|
+
version: '5.0'
|
47
33
|
- !ruby/object:Gem::Dependency
|
48
34
|
name: coveralls
|
49
35
|
requirement: !ruby/object:Gem::Requirement
|
50
36
|
requirements:
|
51
|
-
- -
|
37
|
+
- - "~>"
|
52
38
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
39
|
+
version: 0.8.13
|
54
40
|
type: :development
|
55
41
|
prerelease: false
|
56
42
|
version_requirements: !ruby/object:Gem::Requirement
|
57
43
|
requirements:
|
58
|
-
- -
|
44
|
+
- - "~>"
|
59
45
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
46
|
+
version: 0.8.13
|
61
47
|
- !ruby/object:Gem::Dependency
|
62
48
|
name: rake
|
63
49
|
requirement: !ruby/object:Gem::Requirement
|
64
50
|
requirements:
|
65
|
-
- - ~>
|
51
|
+
- - "~>"
|
66
52
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
53
|
+
version: '11.1'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 11.1.2
|
68
57
|
type: :development
|
69
58
|
prerelease: false
|
70
59
|
version_requirements: !ruby/object:Gem::Requirement
|
71
60
|
requirements:
|
72
|
-
- - ~>
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '11.1'
|
64
|
+
- - ">="
|
73
65
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
66
|
+
version: 11.1.2
|
75
67
|
- !ruby/object:Gem::Dependency
|
76
68
|
name: rspec
|
77
69
|
requirement: !ruby/object:Gem::Requirement
|
78
70
|
requirements:
|
79
|
-
- - ~>
|
71
|
+
- - "~>"
|
80
72
|
- !ruby/object:Gem::Version
|
81
|
-
version: '3
|
73
|
+
version: '3'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 3.0.0
|
82
77
|
type: :development
|
83
78
|
prerelease: false
|
84
79
|
version_requirements: !ruby/object:Gem::Requirement
|
85
80
|
requirements:
|
86
|
-
- - ~>
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '3'
|
84
|
+
- - ">="
|
87
85
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
86
|
+
version: 3.0.0
|
89
87
|
description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
|
90
88
|
email:
|
91
89
|
- leandro.ribeiro.moreira@gmail.com
|
@@ -93,15 +91,18 @@ executables: []
|
|
93
91
|
extensions: []
|
94
92
|
extra_rdoc_files: []
|
95
93
|
files:
|
96
|
-
- .gitignore
|
97
|
-
- .rspec
|
98
|
-
- .travis.yml
|
94
|
+
- ".gitignore"
|
95
|
+
- ".rspec"
|
96
|
+
- ".travis.yml"
|
99
97
|
- CONTRIBUTORS
|
100
98
|
- Gemfile
|
101
99
|
- Gemfile.lock
|
102
100
|
- LICENSE
|
101
|
+
- Makefile
|
103
102
|
- README.md
|
104
103
|
- Rakefile
|
104
|
+
- clean_docker.sh
|
105
|
+
- docker-compose.yml
|
105
106
|
- lib/redlock.rb
|
106
107
|
- lib/redlock/client.rb
|
107
108
|
- lib/redlock/testing.rb
|
@@ -120,17 +121,17 @@ require_paths:
|
|
120
121
|
- lib
|
121
122
|
required_ruby_version: !ruby/object:Gem::Requirement
|
122
123
|
requirements:
|
123
|
-
- -
|
124
|
+
- - ">="
|
124
125
|
- !ruby/object:Gem::Version
|
125
126
|
version: '0'
|
126
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
128
|
requirements:
|
128
|
-
- -
|
129
|
+
- - ">="
|
129
130
|
- !ruby/object:Gem::Version
|
130
131
|
version: '0'
|
131
132
|
requirements: []
|
132
133
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.
|
134
|
+
rubygems_version: 2.5.1
|
134
135
|
signing_key:
|
135
136
|
specification_version: 4
|
136
137
|
summary: Distributed lock using Redis written in Ruby.
|