redlock 0.1.8 → 0.2.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 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.