redlock 1.1.0 → 1.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,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d883de4dd36be4eab1fd32417cdc59a99cd4a7e0a68efbbe1a3865a8f9150ea7
4
- data.tar.gz: f732c4b1fb41bb2d9b34917879d6bc893375d9a2d4d527ecfe29d935c3d998a2
3
+ metadata.gz: f27e3a9be9009072c86f2f38a18bdfdcd2f177890aac365d981711dc040acd2e
4
+ data.tar.gz: efedd1080833cdee147afe6427878ad0df7c824e981e4e8a48c563ac68350d05
5
5
  SHA512:
6
- metadata.gz: b05d03ae1e3e6de8b21d9cc2d7c367d03dba26a1b72b06e821a07e81183345a9ea4e32a5a984d941eb6ef5cd628acb446946e9395d37c7e532b16b5e6444b1eb
7
- data.tar.gz: 3066e7c68d4dd14e7e193bb58f29aad00a4624a47aabd4b7b1dfff32318bcaffad7f38910e1b2ab77a0e8572a71f828dc4c83d8af8a3cd13fe95da7e95910a6e
6
+ metadata.gz: aed115da4ab51ae932533d73e2f2f79d7722e6d77471028bb685d89149d6085fd395af368e9a143231b25b680a57c1ad4d236365396974c12416dac1131cabd8
7
+ data.tar.gz: 75b69cd60c2f562b087179dc939c2fb2bbf3514018dd8c302cf4bc2c4cfa4352a1310dd7cfc35042badcd8c86fc5d237d3a6bcafc57385ca2afcc5a2a280c7bf
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redlock (1.1.0)
4
+ redlock (1.2.0)
5
5
  redis (>= 3.0.0, < 5.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ connection_pool (2.2.2)
10
11
  coveralls (0.8.22)
11
12
  json (>= 1.8, < 3)
12
13
  simplecov (~> 0.16.1)
@@ -16,7 +17,7 @@ GEM
16
17
  diff-lcs (1.3)
17
18
  docile (1.3.1)
18
19
  json (2.1.0)
19
- rake (11.3.0)
20
+ rake (13.0.1)
20
21
  redis (4.1.1)
21
22
  rspec (3.5.0)
22
23
  rspec-core (~> 3.5.0)
@@ -45,8 +46,9 @@ PLATFORMS
45
46
  ruby
46
47
 
47
48
  DEPENDENCIES
49
+ connection_pool (~> 2.2)
48
50
  coveralls (~> 0.8)
49
- rake (~> 11.1, >= 11.1.2)
51
+ rake (~> 13.0, >= 11.1.2)
50
52
  redlock!
51
53
  rspec (~> 3, >= 3.0.0)
52
54
 
data/README.md CHANGED
@@ -1,11 +1,9 @@
1
- [![Stories in Ready](https://badge.waffle.io/leandromoreira/redlock-rb.png?label=ready&title=Ready)](https://waffle.io/leandromoreira/redlock-rb)
2
1
  [![Build Status](https://travis-ci.org/leandromoreira/redlock-rb.svg?branch=master)](https://travis-ci.org/leandromoreira/redlock-rb)
3
2
  [![Coverage Status](https://coveralls.io/repos/leandromoreira/redlock-rb/badge.svg?branch=master)](https://coveralls.io/r/leandromoreira/redlock-rb?branch=master)
4
3
  [![Code Climate](https://codeclimate.com/github/leandromoreira/redlock-rb/badges/gpa.svg)](https://codeclimate.com/github/leandromoreira/redlock-rb)
5
4
  [![Gem Version](https://badge.fury.io/rb/redlock.svg)](http://badge.fury.io/rb/redlock)
6
5
  [![security](https://hakiri.io/github/leandromoreira/redlock-rb/master.svg)](https://hakiri.io/github/leandromoreira/redlock-rb/master)
7
6
  [![Inline docs](http://inch-ci.org/github/leandromoreira/redlock-rb.svg?branch=master)](http://inch-ci.org/github/leandromoreira/redlock-rb)
8
- [![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)
9
7
 
10
8
 
11
9
  # Redlock - A ruby distributed lock using redis.
@@ -139,6 +137,14 @@ It's possible to customize the retry logic providing the following options:
139
137
  })
140
138
  ```
141
139
 
140
+ It is possible to associate `:retry_delay` option with `Proc` object. It will be called every time, with attempt number
141
+ as argument, to get delay time value before next retry.
142
+
143
+ ```ruby
144
+ retry_delay = proc { |attempt_number| 200 * attempt_number ** 2 } # delay of 200ms for 1st retry, 800ms for 2nd retry, etc.
145
+ lock_manager = Redlock::Client.new(servers, retry_delay: retry_delay)
146
+ ```
147
+
142
148
  For more information you can check [documentation](http://www.rubydoc.info/gems/redlock/Redlock%2FClient:initialize).
143
149
 
144
150
  ## Run tests
@@ -3,5 +3,9 @@ require 'redlock/version'
3
3
  module Redlock
4
4
  autoload :Client, 'redlock/client'
5
5
 
6
- LockError = Class.new(StandardError)
6
+ class LockError < StandardError
7
+ def initialize(resource)
8
+ super "failed to acquire lock on '#{resource}'".freeze
9
+ end
10
+ end
7
11
  end
@@ -93,11 +93,11 @@ module Redlock
93
93
  # Locks a resource, executing the received block only after successfully acquiring the lock,
94
94
  # and returning its return value as a result.
95
95
  # See Redlock::Client#lock for parameters.
96
- def lock!(*args)
96
+ def lock!(resource, *args)
97
97
  fail 'No block passed' unless block_given?
98
98
 
99
- lock(*args) do |lock_info|
100
- raise LockError, 'failed to acquire lock' unless lock_info
99
+ lock(resource, *args) do |lock_info|
100
+ raise LockError, resource unless lock_info
101
101
  return yield
102
102
  end
103
103
  end
@@ -122,11 +122,22 @@ module Redlock
122
122
  end
123
123
  eos
124
124
 
125
+ module ConnectionPoolLike
126
+ def with
127
+ yield self
128
+ end
129
+ end
130
+
125
131
  def initialize(connection)
126
- if connection.respond_to?(:client)
132
+ if connection.respond_to?(:with)
127
133
  @redis = connection
128
134
  else
129
- @redis = Redis.new(connection)
135
+ if connection.respond_to?(:client)
136
+ @redis = connection
137
+ else
138
+ @redis = Redis.new(connection)
139
+ end
140
+ @redis.extend(ConnectionPoolLike)
130
141
  end
131
142
 
132
143
  load_scripts
@@ -134,7 +145,7 @@ module Redlock
134
145
 
135
146
  def lock(resource, val, ttl, allow_new_lock)
136
147
  recover_from_script_flush do
137
- @redis.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl, allow_new_lock]
148
+ @redis.with { |conn| conn.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl, allow_new_lock] }
138
149
  end
139
150
  rescue Redis::BaseConnectionError
140
151
  false
@@ -142,7 +153,7 @@ module Redlock
142
153
 
143
154
  def unlock(resource, val)
144
155
  recover_from_script_flush do
145
- @redis.evalsha @unlock_script_sha, keys: [resource], argv: [val]
156
+ @redis.with { |conn| conn.evalsha @unlock_script_sha, keys: [resource], argv: [val] }
146
157
  end
147
158
  rescue
148
159
  # Nothing to do, unlocking is just a best-effort attempt.
@@ -151,8 +162,8 @@ module Redlock
151
162
  private
152
163
 
153
164
  def load_scripts
154
- @unlock_script_sha = @redis.script(:load, UNLOCK_SCRIPT)
155
- @lock_script_sha = @redis.script(:load, LOCK_SCRIPT)
165
+ @unlock_script_sha = @redis.with { |conn| conn.script(:load, UNLOCK_SCRIPT) }
166
+ @lock_script_sha = @redis.with { |conn| conn.script(:load, LOCK_SCRIPT) }
156
167
  end
157
168
 
158
169
  def recover_from_script_flush
@@ -179,7 +190,7 @@ module Redlock
179
190
 
180
191
  tries.times do |attempt_number|
181
192
  # Wait a random delay before retrying.
182
- sleep((@retry_delay + rand(@retry_jitter)).to_f / 1000) if attempt_number > 0
193
+ sleep(attempt_retry_delay(attempt_number)) if attempt_number > 0
183
194
 
184
195
  lock_info = lock_instances(resource, ttl, options)
185
196
  return lock_info if lock_info
@@ -188,6 +199,17 @@ module Redlock
188
199
  false
189
200
  end
190
201
 
202
+ def attempt_retry_delay(attempt_number)
203
+ retry_delay =
204
+ if @retry_delay.respond_to?(:call)
205
+ @retry_delay.call(attempt_number)
206
+ else
207
+ @retry_delay
208
+ end
209
+
210
+ (retry_delay + rand(@retry_jitter)).to_f / 1000
211
+ end
212
+
191
213
  def lock_instances(resource, ttl, options)
192
214
  value = (options[:extend] || { value: SecureRandom.uuid })[:value]
193
215
  allow_new_lock = options[:extend_only_if_locked] ? 'no' : 'yes'
@@ -1,17 +1,27 @@
1
1
  module Redlock
2
2
  class Client
3
- attr_writer :testing_mode
3
+ class << self
4
+ attr_accessor :testing_mode
5
+ end
6
+
7
+ def testing_mode=(mode)
8
+ warn 'DEPRECATION WARNING: Instance-level `testing_mode` has been removed, and this ' +
9
+ 'setter will be removed in the future. Please set the testing mode on the `Redlock::Client` ' +
10
+ 'instead, e.g. `Redlock::Client.testing_mode = :bypass`.'
11
+
12
+ self.class.testing_mode = mode
13
+ end
4
14
 
5
15
  alias_method :try_lock_instances_without_testing, :try_lock_instances
6
16
 
7
17
  def try_lock_instances(resource, ttl, options)
8
- if @testing_mode == :bypass
18
+ if self.class.testing_mode == :bypass
9
19
  {
10
20
  validity: ttl,
11
21
  resource: resource,
12
22
  value: options[:extend] ? options[:extend].fetch(:value) : SecureRandom.uuid
13
23
  }
14
- elsif @testing_mode == :fail
24
+ elsif self.class.testing_mode == :fail
15
25
  false
16
26
  else
17
27
  try_lock_instances_without_testing resource, ttl, options
@@ -21,14 +31,14 @@ module Redlock
21
31
  alias_method :unlock_without_testing, :unlock
22
32
 
23
33
  def unlock(lock_info)
24
- unlock_without_testing lock_info unless @testing_mode == :bypass
34
+ unlock_without_testing lock_info unless self.class.testing_mode == :bypass
25
35
  end
26
36
 
27
37
  class RedisInstance
28
38
  alias_method :load_scripts_without_testing, :load_scripts
29
39
 
30
40
  def load_scripts
31
- load_scripts_without_testing
41
+ load_scripts_without_testing unless Redlock::Client.testing_mode == :bypass
32
42
  rescue Redis::CommandError
33
43
  # FakeRedis doesn't have #script, but doesn't need it either.
34
44
  raise unless defined?(::FakeRedis)
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency 'redis', '>= 3.0.0', '< 5.0'
22
22
 
23
23
  spec.add_development_dependency "coveralls", "~> 0.8"
24
- spec.add_development_dependency 'rake', '~> 11.1', '>= 11.1.2'
24
+ spec.add_development_dependency 'rake', '>= 11.1.2', '~> 13.0'
25
25
  spec.add_development_dependency 'rspec', '~> 3', '>= 3.0.0'
26
+ spec.add_development_dependency 'connection_pool', '~> 2.2'
26
27
  end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'securerandom'
3
3
  require 'redis'
4
+ require 'connection_pool'
4
5
 
5
6
  RSpec.describe Redlock::Client do
6
7
  # It is recommended to have at least 3 servers in production
@@ -25,6 +26,15 @@ RSpec.describe Redlock::Client do
25
26
 
26
27
  expect(redlock_servers).to match_array([redis1_host, redis2_host])
27
28
  end
29
+
30
+ it 'accepts ConnectionPool objects' do
31
+ pool = ConnectionPool.new { Redis.new(url: "redis://#{redis1_host}:#{redis1_port}") }
32
+ redlock = Redlock::Client.new([pool])
33
+
34
+ lock_info = lock_manager.lock(resource_key, ttl)
35
+ expect(resource_key).to_not be_lockable(lock_manager, ttl)
36
+ lock_manager.unlock(lock_info)
37
+ end
28
38
  end
29
39
 
30
40
  describe 'lock' do
@@ -160,13 +170,29 @@ RSpec.describe Redlock::Client do
160
170
  end.at_least(:once)
161
171
  lock_manager.lock(resource_key, ttl)
162
172
  end
173
+
174
+ it 'accepts retry_delay as proc' do
175
+ retry_delay = proc do |attempt_number|
176
+ expect(attempt_number).to eq(1)
177
+ 2000
178
+ end
179
+
180
+ lock_manager = Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, retry_count: 1, retry_delay: retry_delay)
181
+ another_lock_info = lock_manager.lock(resource_key, ttl)
182
+
183
+ expect(lock_manager).to receive(:sleep) do |sleep|
184
+ expect(sleep * 1000).to be_within(described_class::DEFAULT_RETRY_JITTER).of(2000)
185
+ end.exactly(:once)
186
+ lock_manager.lock(resource_key, ttl)
187
+ lock_manager.unlock(another_lock_info)
188
+ end
163
189
  end
164
190
 
165
191
  context 'when a server goes away' do
166
192
  it 'does not raise an error on connection issues' do
167
193
  # We re-route the lock manager to a (hopefully) non-existent Redis URL.
168
194
  redis_instance = lock_manager.instance_variable_get(:@servers).first
169
- redis_instance.instance_variable_set(:@redis, Redis.new(url: 'redis://localhost:46864'))
195
+ redis_instance.instance_variable_set(:@redis, unreachable_redis)
170
196
 
171
197
  expect {
172
198
  expect(lock_manager.lock(resource_key, ttl)).to be_falsey
@@ -178,13 +204,22 @@ RSpec.describe Redlock::Client do
178
204
  it 'recovers from connection issues' do
179
205
  # Same as above.
180
206
  redis_instance = lock_manager.instance_variable_get(:@servers).first
181
- redis_instance.instance_variable_set(:@redis, Redis.new(url: 'redis://localhost:46864'))
207
+ old_redis = redis_instance.instance_variable_get(:@redis)
208
+ redis_instance.instance_variable_set(:@redis, unreachable_redis)
182
209
  expect(lock_manager.lock(resource_key, ttl)).to be_falsey
183
- redis_instance.instance_variable_set(:@redis, Redis.new(url: "redis://#{redis1_host}:#{redis1_port}"))
210
+ redis_instance.instance_variable_set(:@redis, old_redis)
184
211
  expect(lock_manager.lock(resource_key, ttl)).to be_truthy
185
212
  end
186
213
  end
187
214
 
215
+ def unreachable_redis
216
+ redis = Redis.new(url: 'redis://localhost:46864')
217
+ def redis.with
218
+ yield self
219
+ end
220
+ redis
221
+ end
222
+
188
223
  context 'when script cache has been flushed' do
189
224
  before(:each) do
190
225
  @manipulated_instance = lock_manager.instance_variable_get(:@servers).first
@@ -316,7 +351,9 @@ RSpec.describe Redlock::Client do
316
351
  after { lock_manager.unlock(@another_lock_info) }
317
352
 
318
353
  it 'raises a LockError' do
319
- expect { lock_manager.lock!(resource_key, ttl) {} }.to raise_error(Redlock::LockError)
354
+ expect { lock_manager.lock!(resource_key, ttl) {} }.to raise_error(
355
+ Redlock::LockError, "failed to acquire lock on '#{resource_key}'"
356
+ )
320
357
  end
321
358
 
322
359
  it 'does not execute the block' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redlock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.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: 2019-11-15 00:00:00.000000000 Z
11
+ date: 2020-02-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -48,42 +48,56 @@ dependencies:
48
48
  name: rake
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '11.1'
54
51
  - - ">="
55
52
  - !ruby/object:Gem::Version
56
53
  version: 11.1.2
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '13.0'
57
57
  type: :development
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - "~>"
62
- - !ruby/object:Gem::Version
63
- version: '11.1'
64
61
  - - ">="
65
62
  - !ruby/object:Gem::Version
66
63
  version: 11.1.2
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '13.0'
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: rspec
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- version: 3.0.0
74
71
  - - "~>"
75
72
  - !ruby/object:Gem::Version
76
73
  version: '3'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
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
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3'
81
84
  - - ">="
82
85
  - !ruby/object:Gem::Version
83
86
  version: 3.0.0
87
+ - !ruby/object:Gem::Dependency
88
+ name: connection_pool
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
84
91
  - - "~>"
85
92
  - !ruby/object:Gem::Version
86
- version: '3'
93
+ version: '2.2'
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '2.2'
87
101
  description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
88
102
  email:
89
103
  - leandro.ribeiro.moreira@gmail.com
@@ -130,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
144
  - !ruby/object:Gem::Version
131
145
  version: '0'
132
146
  requirements: []
133
- rubygems_version: 3.0.3
147
+ rubygems_version: 3.1.2
134
148
  signing_key:
135
149
  specification_version: 4
136
150
  summary: Distributed lock using Redis written in Ruby.