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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZWE3MWMxNjdhNDQ5OTE3ZDk4ODM5YTk0YzJhMTdiYmIyZjMzNWNlYg==
5
- data.tar.gz: !binary |-
6
- ZmQ4YmY4ODEzZDZlOWVmNmZlYzBmMTFiZDIzNjg2NDk1NWYyYmEzZg==
2
+ SHA1:
3
+ metadata.gz: 8e34b717846f5f8faa005ab9a8d5cac3c3ea865e
4
+ data.tar.gz: f7a615768b2fc2746d16128122a69259c252b78f
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZTdjMTdkNjZmNDBkNmQyYmIyMmQ4OTJjOTBlNGQxZmNlNGIxY2NlZjY0NTIx
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
@@ -1,2 +1,3 @@
1
1
  *.gem
2
2
  coverage/
3
+ .bundle/
data/.travis.yml CHANGED
@@ -2,7 +2,7 @@ language: ruby
2
2
  services:
3
3
  - redis-server
4
4
  rvm:
5
- - "2.0.0"
5
+ - "2.2.2"
6
6
  script: bundle exec rspec spec
7
7
  sudo: false
8
8
  cache: bundler
data/Gemfile.lock CHANGED
@@ -1,57 +1,54 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redlock (0.1.5)
5
- redis (~> 3, >= 3.0.5)
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.7.11)
11
- multi_json (~> 1.10)
12
- rest-client (>= 1.6.8, < 2)
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
- diff-lcs (1.2.5)
15
+ tins (~> 1.6)
16
+ diff-lcs (1.3)
17
17
  docile (1.1.5)
18
- mime-types (2.4.3)
19
- multi_json (1.11.0)
20
- netrc (0.10.3)
21
- rake (10.4.2)
22
- redis (3.2.2)
23
- rest-client (1.7.3)
24
- mime-types (>= 1.16, < 3.0)
25
- netrc (~> 0.7)
26
- rspec (3.2.0)
27
- rspec-core (~> 3.2.0)
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.2.0)
35
- rspec-mocks (3.2.1)
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.2.0)
38
- rspec-support (3.2.2)
39
- simplecov (0.9.2)
32
+ rspec-support (~> 3.5.0)
33
+ rspec-support (3.5.0)
34
+ simplecov (0.12.0)
40
35
  docile (~> 1.1.0)
41
- multi_json (~> 1.0)
42
- simplecov-html (~> 0.9.0)
43
- simplecov-html (0.9.0)
44
- term-ansicolor (1.3.0)
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.1)
47
- tins (1.3.5)
41
+ thor (0.19.4)
42
+ tins (1.13.0)
48
43
 
49
44
  PLATFORMS
50
45
  ruby
51
46
 
52
47
  DEPENDENCIES
53
- bundler (~> 1.7)
54
- coveralls
55
- rake (~> 10.0)
48
+ coveralls (~> 0.8.13)
49
+ rake (~> 11.1, >= 11.1.2)
56
50
  redlock!
57
- rspec (~> 3.1)
51
+ rspec (~> 3, >= 3.0.0)
52
+
53
+ BUNDLED WITH
54
+ 1.16.0
data/Makefile ADDED
@@ -0,0 +1,4 @@
1
+ default: test
2
+ test:
3
+ docker-compose run --rm test
4
+
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 at least 1 redis instances up.
141
+ Make sure you have [docker installed](https://docs.docker.com/engine/installation/).
97
142
 
98
- $ rspec
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
@@ -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
+
@@ -3,18 +3,22 @@ require 'securerandom'
3
3
 
4
4
  module Redlock
5
5
  class Client
6
- DEFAULT_REDIS_URLS = ['redis://localhost:6379']
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 `retry_delay`.
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
- # +extend+: A lock ("lock_info") to extend.
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, extend: nil, &block)
41
- lock_info = try_lock_instances(resource, ttl, extend)
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, **keyword_args)
72
+ def lock!(*args)
66
73
  fail 'No block passed' unless block_given?
67
74
 
68
- lock(*args, **keyword_args) do |lock_info|
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 = Redis.new(connection)
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, extend)
145
- @retry_count.times do
146
- lock_info = lock_instances(resource, ttl, extend)
147
- return lock_info if lock_info
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
- # Wait a random delay before retrying
150
- sleep(rand(@retry_delay).to_f / 1000)
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, extend)
157
- value = extend ? extend.fetch(:value) : SecureRandom.uuid
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(resource, value, ttl) }.size
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)
@@ -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, extend)
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, extend
17
+ try_lock_instances_without_testing resource, ttl, options
18
18
  end
19
19
  end
20
20
 
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = '0.1.5'
2
+ VERSION = '0.2.1'
3
3
  end
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.0.5'
21
+ spec.add_dependency 'redis', '>= 3.0.0', '< 5.0'
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.7"
24
- spec.add_development_dependency "coveralls"
25
- spec.add_development_dependency "rake", "~> 10.0"
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(:lock_manager) { Redlock::Client.new }
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
- servers = [ 'redis://localhost:6379', Redis.new(url: 'redis://127.0.0.1:6379') ]
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).client.host
22
+ s.instance_variable_get(:@redis).connection[:host]
17
23
  end
18
24
 
19
- expect(redlock_servers).to match_array(%w{ localhost 127.0.0.1 })
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
- it "sets the given value when trying to extend a non-existent lock" do
47
- @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'})
48
- expect(@lock_info).to be_lock_info_for(resource_key)
49
- expect(@lock_info[:value]).to eq('hello world') # really we should test what's in redis
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
@@ -3,7 +3,7 @@ require 'coveralls'
3
3
  Coveralls.wear!
4
4
  require 'redlock'
5
5
 
6
- LOCK_INFO_KEYS = %i{validity resource value}
6
+ LOCK_INFO_KEYS = [:validity, :resource, :value]
7
7
 
8
8
  RSpec::Matchers.define :be_lock_info_for do |resource|
9
9
  def correct_type?(actual)
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.5
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: 2015-12-01 00:00:00.000000000 Z
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: '3'
20
- - - ! '>='
19
+ version: 3.0.0
20
+ - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 3.0.5
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: '3'
30
- - - ! '>='
29
+ version: 3.0.0
30
+ - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 3.0.5
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: '0'
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: '0'
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: '10.0'
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: '10.0'
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.1'
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: '3.1'
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.4.6
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.