redlock 0.1.8 → 0.2.0

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
- NzQwMDRhZWQ2ZjUyM2FjMTgxMzJiY2Q1ZmY5YzI3ODk1ZWY4NTNmMA==
5
- data.tar.gz: !binary |-
6
- ZDliZjZlMmM3OGNmZGMyYzhlNWI3YjBlMmUyNGRmNGY0Yjk0N2FkYw==
2
+ SHA1:
3
+ metadata.gz: 3820d9812a87989c395ff8b4856a4cea7801d84b
4
+ data.tar.gz: 7cc006346948cf1132be55de820949f37cb9f07d
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZTZjYWUyNmI2NTliZjliODZkM2ZhYzczZDAyM2RlM2FiY2Y5MTkxMDk4YjVk
10
- YzVhMGJjMjBiMTM4NDYzY2ZkNWNiYzRmODk5YWFlZTJlMzczMTZlMzI4ZmRh
11
- YTNkZDNhNjgxNzcxOTlhNGE2YTdjNzgyZTgwNTY0YWY4OTUwMjc=
12
- data.tar.gz: !binary |-
13
- NDllNDgxMmQ0ODk1NTdlNTNkMjczN2EyYjQyMjE2MWU5YmJmMDVlMmJkY2Q1
14
- YTZjNTE5NjFjNTI3Mjc3NzJiZDBlMDIwYzUwM2NlMGM1YjE0NDVkNDIzODFm
15
- MjVhNjU3OTI4NzY0YzY3OTE1ZDhiNGRhNmRlMzM5OGE3YjczMTI=
6
+ metadata.gz: ba94bdcbdd4f659a6564baad82a3f022ea64f541789579803f33bfd0981ee6a3df53b6887a05791f3794202c85748ae85bdd357d1703d690464e6f0844b3e899
7
+ data.tar.gz: c36ec9b01716486eeacdd9a82f27cbaa8d6b3165da1e17e11196ac4d700dfe82acf439e6b027b8852f5f33241af08927fea6c2d2f379ffca8392b1ac69a69c1d
@@ -1,45 +1,45 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redlock (0.1.8)
4
+ redlock (0.2.0)
5
5
  redis (~> 3, >= 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- coveralls (0.8.13)
11
- json (~> 1.8)
12
- simplecov (~> 0.11.0)
10
+ coveralls (0.8.19)
11
+ json (>= 1.8, < 3)
12
+ simplecov (~> 0.12.0)
13
13
  term-ansicolor (~> 1.3)
14
14
  thor (~> 0.19.1)
15
- tins (~> 1.6.0)
16
- diff-lcs (1.2.5)
15
+ tins (~> 1.6)
16
+ diff-lcs (1.3)
17
17
  docile (1.1.5)
18
- json (1.8.3)
19
- rake (11.1.2)
20
- redis (3.3.0)
21
- rspec (3.4.0)
22
- rspec-core (~> 3.4.0)
23
- rspec-expectations (~> 3.4.0)
24
- rspec-mocks (~> 3.4.0)
25
- rspec-core (3.4.4)
26
- rspec-support (~> 3.4.0)
27
- rspec-expectations (3.4.0)
18
+ json (2.0.3)
19
+ rake (11.3.0)
20
+ redis (3.3.3)
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)
28
28
  diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.4.0)
30
- rspec-mocks (3.4.1)
29
+ rspec-support (~> 3.5.0)
30
+ rspec-mocks (3.5.0)
31
31
  diff-lcs (>= 1.2.0, < 2.0)
32
- rspec-support (~> 3.4.0)
33
- rspec-support (3.4.1)
34
- simplecov (0.11.2)
32
+ rspec-support (~> 3.5.0)
33
+ rspec-support (3.5.0)
34
+ simplecov (0.12.0)
35
35
  docile (~> 1.1.0)
36
- json (~> 1.8)
36
+ json (>= 1.8, < 3)
37
37
  simplecov-html (~> 0.10.0)
38
38
  simplecov-html (0.10.0)
39
- term-ansicolor (1.3.2)
39
+ term-ansicolor (1.4.0)
40
40
  tins (~> 1.0)
41
- thor (0.19.1)
42
- tins (1.6.0)
41
+ thor (0.19.4)
42
+ tins (1.13.0)
43
43
 
44
44
  PLATFORMS
45
45
  ruby
@@ -49,3 +49,6 @@ DEPENDENCIES
49
49
  rake (~> 11.1, >= 11.1.2)
50
50
  redlock!
51
51
  rspec (~> 3, >= 3.0.0)
52
+
53
+ BUNDLED WITH
54
+ 1.12.5
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
 
@@ -123,6 +121,20 @@ rescue Redlock::LockError
123
121
  end
124
122
  ```
125
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
+ retry_timeout: 0.1 # seconds
133
+ })
134
+ ```
135
+
136
+ For more information you can check [documentation](https://github.com/leandromoreira/redlock-rb/blob/master/lib/redlock/client.rb#L13-L20)
137
+
126
138
 
127
139
  ## Run tests
128
140
 
@@ -7,14 +7,16 @@ module Redlock
7
7
  DEFAULT_REDIS_TIMEOUT = 0.1
8
8
  DEFAULT_RETRY_COUNT = 3
9
9
  DEFAULT_RETRY_DELAY = 200
10
+ DEFAULT_RETRY_JITTER = 50
10
11
  CLOCK_DRIFT_FACTOR = 0.01
11
12
 
12
13
  # Create a distributed lock manager implementing redlock algorithm.
13
14
  # Params:
14
15
  # +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`.
16
+ # +options+:: You can override the default value for `retry_count`, `retry_delay` and `retry_gitter`.
16
17
  # * `retry_count` being how many times it'll try to lock a resource (default: 3)
17
18
  # * `retry_delay` being how many ms to sleep before try to lock again (default: 200)
19
+ # * `retry_jitter` being how many ms to jitter retry delay (default: 50)
18
20
  # * `redis_timeout` being how the Redis timeout will be set in seconds (default: 0.1)
19
21
  def initialize(servers = DEFAULT_REDIS_URLS, options = {})
20
22
  redis_timeout = options[:redis_timeout] || DEFAULT_REDIS_TIMEOUT
@@ -28,13 +30,16 @@ module Redlock
28
30
  @quorum = servers.length / 2 + 1
29
31
  @retry_count = options[:retry_count] || DEFAULT_RETRY_COUNT
30
32
  @retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
33
+ @retry_jitter = options[:retry_jitter] || DEFAULT_RETRY_JITTER
31
34
  end
32
35
 
33
36
  # Locks a resource for a given time.
34
37
  # Params:
35
38
  # +resource+:: the resource (or key) string to be locked.
36
39
  # +ttl+:: The time-to-live in ms for the lock.
37
- # +extend+: A lock ("lock_info") to extend.
40
+ # +options+:: Hash of optional parameters
41
+ # * +extend+: A lock ("lock_info") to extend.
42
+ # * +extend_only_if_life+: If +extend+ is given, only acquire lock if currently held
38
43
  # +block+:: an optional block to be executed; after its execution, the lock (if successfully
39
44
  # acquired) is automatically unlocked.
40
45
  def lock(resource, ttl, options = {}, &block)
@@ -86,40 +91,24 @@ module Redlock
86
91
  # also https://github.com/sbertrang/redis-distlock/issues/2 which proposes the value-checking
87
92
  # and @maltoe for https://github.com/leandromoreira/redlock-rb/pull/20#discussion_r38903633
88
93
  LOCK_SCRIPT = <<-eos
89
- if redis.call("exists", KEYS[1]) == 0 or redis.call("get", KEYS[1]) == ARGV[1] then
94
+ if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
90
95
  return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
91
96
  end
92
97
  eos
93
98
 
94
- EXTEND_LIFE_SCRIPT = <<-eos
95
- if redis.call("get", KEYS[1]) == ARGV[1] then
96
- redis.call("expire", KEYS[1], ARGV[2])
97
- return 0
98
- else
99
- return 1
100
- end
101
- eos
102
-
103
99
  def initialize(connection)
104
100
  if connection.respond_to?(:client)
105
101
  @redis = connection
106
102
  else
107
- @redis = Redis.new(connection)
103
+ @redis = Redis.new(connection)
108
104
  end
109
105
 
110
106
  load_scripts
111
107
  end
112
108
 
113
- def lock(resource, val, ttl)
109
+ def lock(resource, val, ttl, allow_new_lock)
114
110
  recover_from_script_flush do
115
- @redis.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl]
116
- end
117
- end
118
-
119
- def extend(resource, val, ttl)
120
- recover_from_script_flush do
121
- rc = @redis.evalsha @extend_life_script_sha, keys: [resource], argv: [val, ttl]
122
- rc == 0
111
+ @redis.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl, allow_new_lock]
123
112
  end
124
113
  end
125
114
 
@@ -136,7 +125,6 @@ module Redlock
136
125
  def load_scripts
137
126
  @unlock_script_sha = @redis.script(:load, UNLOCK_SCRIPT)
138
127
  @lock_script_sha = @redis.script(:load, LOCK_SCRIPT)
139
- @extend_life_script_sha = @redis.script(:load, EXTEND_LIFE_SCRIPT)
140
128
  end
141
129
 
142
130
  def recover_from_script_flush
@@ -161,23 +149,23 @@ module Redlock
161
149
  def try_lock_instances(resource, ttl, options)
162
150
  tries = options[:extend] ? 1 : @retry_count
163
151
 
164
- tries.times do
152
+ tries.times do |attempt_number|
153
+ # Wait a random delay before retrying.
154
+ sleep((@retry_delay + rand(@retry_jitter)).to_f / 1000) if attempt_number > 0
155
+
165
156
  lock_info = lock_instances(resource, ttl, options)
166
157
  return lock_info if lock_info
167
-
168
- # Wait a random delay before retrying
169
- sleep(rand(@retry_delay).to_f / 1000)
170
158
  end
171
159
 
172
160
  false
173
161
  end
174
162
 
175
163
  def lock_instances(resource, ttl, options)
176
- value = options[:extend] ? options[:extend].fetch(:value) : SecureRandom.uuid
177
- method = options[:extend_life] ? :extend : :lock
164
+ value = options.fetch(:extend, { value: SecureRandom.uuid })[:value]
165
+ allow_new_lock = (options[:extend_life] || options[:extend_only_if_life]) ? 'no' : 'yes'
178
166
 
179
167
  locked, time_elapsed = timed do
180
- @servers.select { |s| s.send(method, resource, value, ttl) }.size
168
+ @servers.select { |s| s.lock resource, value, ttl, allow_new_lock }.size
181
169
  end
182
170
 
183
171
  validity = ttl - time_elapsed - drift(ttl)
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = '0.1.8'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -3,7 +3,8 @@ 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 }
9
10
 
@@ -43,16 +44,16 @@ RSpec.describe Redlock::Client do
43
44
  expect(@lock_info[:value]).to eq(my_lock_info[:value])
44
45
  end
45
46
 
46
- context 'when extend_life flag is given' do
47
+ context 'when extend_only_if_life flag is given' do
47
48
  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
+ @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: true)
49
50
  expect(@lock_info).to eq(false)
50
51
  end
51
52
  end
52
53
 
53
- context 'when extend_life flag is not given' do
54
+ context 'when extend_only_if_life flag is not given' do
54
55
  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
+ @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: false)
56
57
  expect(@lock_info).to be_lock_info_for(resource_key)
57
58
  expect(@lock_info[:value]).to eq('hello world') # really we should test what's in redis
58
59
  end
@@ -80,6 +81,33 @@ RSpec.describe Redlock::Client do
80
81
  lock_info = lock_manager.lock(resource_key, ttl, extend: yet_another_lock_info)
81
82
  expect(lock_info).to eql(false)
82
83
  end
84
+
85
+ it 'retries up to \'retry_count\' times' do
86
+ expect(lock_manager).to receive(:lock_instances).exactly(
87
+ lock_manager_opts[:retry_count]).times.and_return(false)
88
+ lock_manager.lock(resource_key, ttl)
89
+ end
90
+
91
+ it 'sleeps in between retries' do
92
+ expect(lock_manager).to receive(:sleep).exactly(lock_manager_opts[:retry_count] - 1).times
93
+ lock_manager.lock(resource_key, ttl)
94
+ end
95
+
96
+ it 'sleeps at least the specified retry_delay in milliseconds' do
97
+ expected_minimum = described_class::DEFAULT_RETRY_DELAY
98
+ expect(lock_manager).to receive(:sleep) do |sleep|
99
+ expect(sleep).to satisfy { |value| value >= expected_minimum / 1000.to_f }
100
+ end.at_least(:once)
101
+ lock_manager.lock(resource_key, ttl)
102
+ end
103
+
104
+ it 'sleeps a maximum of retry_delay + retry_jitter in milliseconds' do
105
+ expected_maximum = described_class::DEFAULT_RETRY_DELAY + described_class::DEFAULT_RETRY_JITTER
106
+ expect(lock_manager).to receive(:sleep) do |sleep|
107
+ expect(sleep).to satisfy { |value| value < expected_maximum / 1000.to_f }
108
+ end.at_least(:once)
109
+ lock_manager.lock(resource_key, ttl)
110
+ end
83
111
  end
84
112
 
85
113
  context 'when script cache has been flushed' do
metadata CHANGED
@@ -1,87 +1,87 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redlock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.2.0
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-24 00:00:00.000000000 Z
11
+ date: 2017-02-02 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
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
32
  version: 3.0.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: coveralls
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ~>
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: 0.8.13
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ~>
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: 0.8.13
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rake
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - ~>
51
+ - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '11.1'
54
- - - ! '>='
54
+ - - ">="
55
55
  - !ruby/object:Gem::Version
56
56
  version: 11.1.2
57
57
  type: :development
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - ~>
61
+ - - "~>"
62
62
  - !ruby/object:Gem::Version
63
63
  version: '11.1'
64
- - - ! '>='
64
+ - - ">="
65
65
  - !ruby/object:Gem::Version
66
66
  version: 11.1.2
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: rspec
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - ~>
71
+ - - "~>"
72
72
  - !ruby/object:Gem::Version
73
73
  version: '3'
74
- - - ! '>='
74
+ - - ">="
75
75
  - !ruby/object:Gem::Version
76
76
  version: 3.0.0
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - ~>
81
+ - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '3'
84
- - - ! '>='
84
+ - - ">="
85
85
  - !ruby/object:Gem::Version
86
86
  version: 3.0.0
87
87
  description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
@@ -91,9 +91,9 @@ executables: []
91
91
  extensions: []
92
92
  extra_rdoc_files: []
93
93
  files:
94
- - .gitignore
95
- - .rspec
96
- - .travis.yml
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - ".travis.yml"
97
97
  - CONTRIBUTORS
98
98
  - Gemfile
99
99
  - Gemfile.lock
@@ -118,17 +118,17 @@ require_paths:
118
118
  - lib
119
119
  required_ruby_version: !ruby/object:Gem::Requirement
120
120
  requirements:
121
- - - ! '>='
121
+ - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  required_rubygems_version: !ruby/object:Gem::Requirement
125
125
  requirements:
126
- - - ! '>='
126
+ - - ">="
127
127
  - !ruby/object:Gem::Version
128
128
  version: '0'
129
129
  requirements: []
130
130
  rubyforge_project:
131
- rubygems_version: 2.4.6
131
+ rubygems_version: 2.4.5
132
132
  signing_key:
133
133
  specification_version: 4
134
134
  summary: Distributed lock using Redis written in Ruby.