redlock 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a91b03cbb845e7b01556262e80e946e2d72980beeebce12d51380d7c57c57fb9
4
- data.tar.gz: 73d38bac32a8003a556d3d8967155d15a8edbe988fc43387174ec506b21e8c99
3
+ metadata.gz: 1870dc8b8577a8e8545fd952fd6c2f1ff3ac1f336bff5f632b3b89eb92eb0069
4
+ data.tar.gz: b5153369a5cd331a4bbc0884efd657692da4b824e0cbe0dd8ed78fdd407d7047
5
5
  SHA512:
6
- metadata.gz: e55aa47e007175e619e22ec2b2ca996add245143290750c79a27fbafc8bb6bd32e5c02b07d9a9752c1ad8cb7f22a10a2b245c9b963c1b4f186427d6c7a987d3a
7
- data.tar.gz: dd61e0d187a5972ecbe09bde3345c1a6174263f06fca6b7d1da3ce8be19fe5bb69ffca4cfc24073705c5b5a40551bb1a65ffba1358c5fbaf37f10ea46a15a929
6
+ metadata.gz: 747aa1155827f513a0612e6610dd754143c3fb65c50aaeca87506830d91c83ec40a89f7fcfb81d22395ea2cc16048cc99136894f0b6a8adb7455d2adae60c244
7
+ data.tar.gz: b033303f287bdbeca822c6b3f0ad829531c5740ad0716f6a6cdeb75370ea9e3874838b761f65ac242b07a07af0b1c0ab955e288059f0953aed3aeb686773a6cc
data/.gitignore CHANGED
@@ -1,5 +1,4 @@
1
1
  *.gem
2
2
  coverage/
3
3
  .bundle/
4
- Gemfile.lock
5
4
  dump.rdb
data/Gemfile.lock ADDED
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redlock (1.2.2)
5
+ redis (>= 3.0.0, < 5.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ connection_pool (2.2.5)
11
+ coveralls (0.8.23)
12
+ json (>= 1.8, < 3)
13
+ simplecov (~> 0.16.1)
14
+ term-ansicolor (~> 1.3)
15
+ thor (>= 0.19.4, < 2.0)
16
+ tins (~> 1.6)
17
+ diff-lcs (1.4.4)
18
+ docile (1.4.0)
19
+ json (2.3.1)
20
+ rake (13.0.6)
21
+ redis (4.4.0)
22
+ rspec (3.10.0)
23
+ rspec-core (~> 3.10.0)
24
+ rspec-expectations (~> 3.10.0)
25
+ rspec-mocks (~> 3.10.0)
26
+ rspec-core (3.10.1)
27
+ rspec-support (~> 3.10.0)
28
+ rspec-expectations (3.10.1)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.10.0)
31
+ rspec-mocks (3.10.2)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.10.0)
34
+ rspec-support (3.10.2)
35
+ simplecov (0.16.1)
36
+ docile (~> 1.1)
37
+ json (>= 1.8, < 3)
38
+ simplecov-html (~> 0.10.0)
39
+ simplecov-html (0.10.2)
40
+ sync (0.5.0)
41
+ term-ansicolor (1.7.1)
42
+ tins (~> 1.0)
43
+ thor (1.1.0)
44
+ tins (1.29.1)
45
+ sync
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ connection_pool (~> 2.2)
52
+ coveralls (~> 0.8)
53
+ json (~> 2.3.1, >= 2.3.0)
54
+ rake (~> 13.0, >= 11.1.2)
55
+ redlock!
56
+ rspec (~> 3, >= 3.0.0)
57
+
58
+ BUNDLED WITH
59
+ 2.2.22
data/Makefile CHANGED
@@ -7,3 +7,6 @@ build:
7
7
 
8
8
  publish:
9
9
  docker-compose run --rm test gem push `ls -lt *gem | head -n 1 | awk '{ print $$9 }'`
10
+
11
+ updateLock:
12
+ docker-compose run --rm test bundle lock --update
@@ -2,6 +2,8 @@ require 'redis'
2
2
  require 'securerandom'
3
3
 
4
4
  module Redlock
5
+ include Scripts
6
+
5
7
  class Client
6
8
  DEFAULT_REDIS_HOST = ENV["DEFAULT_REDIS_HOST"] || "localhost"
7
9
  DEFAULT_REDIS_PORT = ENV["DEFAULT_REDIS_PORT"] || "6379"
@@ -54,6 +56,9 @@ module Redlock
54
56
  # +resource+:: the resource (or key) string to be locked.
55
57
  # +ttl+:: The time-to-live in ms for the lock.
56
58
  # +options+:: Hash of optional parameters
59
+ # * +retry_count+: see +initialize+
60
+ # * +retry_delay+: see +initialize+
61
+ # * +retry_jitter+: see +initialize+
57
62
  # * +extend+: A lock ("lock_info") to extend.
58
63
  # * +extend_only_if_locked+: Boolean, if +extend+ is given, only acquire lock if currently held
59
64
  # * +extend_only_if_life+: Deprecated, same as +extend_only_if_locked+
@@ -142,27 +147,6 @@ module Redlock
142
147
  private
143
148
 
144
149
  class RedisInstance
145
- UNLOCK_SCRIPT = <<-eos
146
- if redis.call("get",KEYS[1]) == ARGV[1] then
147
- return redis.call("del",KEYS[1])
148
- else
149
- return 0
150
- end
151
- eos
152
-
153
- # thanks to https://github.com/sbertrang/redis-distlock/blob/master/lib/Redis/DistLock.pm
154
- # also https://github.com/sbertrang/redis-distlock/issues/2 which proposes the value-checking
155
- # and @maltoe for https://github.com/leandromoreira/redlock-rb/pull/20#discussion_r38903633
156
- LOCK_SCRIPT = <<-eos
157
- if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
158
- return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
159
- end
160
- eos
161
-
162
- PTTL_SCRIPT = <<-eos
163
- return { redis.call("get", KEYS[1]), redis.call("pttl", KEYS[1]) }
164
- eos
165
-
166
150
  module ConnectionPoolLike
167
151
  def with
168
152
  yield self
@@ -180,13 +164,11 @@ module Redlock
180
164
  end
181
165
  @redis.extend(ConnectionPoolLike)
182
166
  end
183
-
184
- load_scripts
185
167
  end
186
168
 
187
169
  def lock(resource, val, ttl, allow_new_lock)
188
170
  recover_from_script_flush do
189
- @redis.with { |conn| conn.evalsha @lock_script_sha, keys: [resource], argv: [val, ttl, allow_new_lock] }
171
+ @redis.with { |conn| conn.evalsha Scripts::LOCK_SCRIPT_SHA, keys: [resource], argv: [val, ttl, allow_new_lock] }
190
172
  end
191
173
  rescue Redis::BaseConnectionError
192
174
  false
@@ -194,7 +176,7 @@ module Redlock
194
176
 
195
177
  def unlock(resource, val)
196
178
  recover_from_script_flush do
197
- @redis.with { |conn| conn.evalsha @unlock_script_sha, keys: [resource], argv: [val] }
179
+ @redis.with { |conn| conn.evalsha Scripts::UNLOCK_SCRIPT_SHA, keys: [resource], argv: [val] }
198
180
  end
199
181
  rescue
200
182
  # Nothing to do, unlocking is just a best-effort attempt.
@@ -202,7 +184,7 @@ module Redlock
202
184
 
203
185
  def get_remaining_ttl(resource)
204
186
  recover_from_script_flush do
205
- @redis.with { |conn| conn.evalsha @pttl_script_sha, keys: [resource] }
187
+ @redis.with { |conn| conn.evalsha Scripts::PTTL_SCRIPT_SHA, keys: [resource] }
206
188
  end
207
189
  rescue Redis::BaseConnectionError
208
190
  nil
@@ -211,9 +193,15 @@ module Redlock
211
193
  private
212
194
 
213
195
  def load_scripts
214
- @unlock_script_sha = @redis.with { |conn| conn.script(:load, UNLOCK_SCRIPT) }
215
- @lock_script_sha = @redis.with { |conn| conn.script(:load, LOCK_SCRIPT) }
216
- @pttl_script_sha = @redis.with { |conn| conn.script(:load, PTTL_SCRIPT) }
196
+ scripts = [
197
+ Scripts::UNLOCK_SCRIPT,
198
+ Scripts::LOCK_SCRIPT,
199
+ Scripts::PTTL_SCRIPT
200
+ ]
201
+
202
+ scripts.each do |script|
203
+ @redis.with { |conn| conn.script(:load, script) }
204
+ end
217
205
  end
218
206
 
219
207
  def recover_from_script_flush
@@ -236,11 +224,12 @@ module Redlock
236
224
  end
237
225
 
238
226
  def try_lock_instances(resource, ttl, options)
239
- tries = options[:extend] ? 1 : (@retry_count + 1)
227
+ retry_count = options[:retry_count] || @retry_count
228
+ tries = options[:extend] ? 1 : (retry_count + 1)
240
229
 
241
230
  tries.times do |attempt_number|
242
231
  # Wait a random delay before retrying.
243
- sleep(attempt_retry_delay(attempt_number)) if attempt_number > 0
232
+ sleep(attempt_retry_delay(attempt_number, options)) if attempt_number > 0
244
233
 
245
234
  lock_info = lock_instances(resource, ttl, options)
246
235
  return lock_info if lock_info
@@ -249,15 +238,18 @@ module Redlock
249
238
  false
250
239
  end
251
240
 
252
- def attempt_retry_delay(attempt_number)
241
+ def attempt_retry_delay(attempt_number, options)
242
+ retry_delay = options[:retry_delay] || @retry_delay
243
+ retry_jitter = options[:retry_jitter] || @retry_jitter
244
+
253
245
  retry_delay =
254
- if @retry_delay.respond_to?(:call)
255
- @retry_delay.call(attempt_number)
246
+ if retry_delay.respond_to?(:call)
247
+ retry_delay.call(attempt_number)
256
248
  else
257
- @retry_delay
249
+ retry_delay
258
250
  end
259
251
 
260
- (retry_delay + rand(@retry_jitter)).to_f / 1000
252
+ (retry_delay + rand(retry_jitter)).to_f / 1000
261
253
  end
262
254
 
263
255
  def lock_instances(resource, ttl, options)
@@ -0,0 +1,34 @@
1
+ require 'digest'
2
+
3
+ module Redlock
4
+ module Scripts
5
+ UNLOCK_SCRIPT = <<-eos
6
+ if redis.call("get",KEYS[1]) == ARGV[1] then
7
+ return redis.call("del",KEYS[1])
8
+ else
9
+ return 0
10
+ end
11
+ eos
12
+
13
+ # thanks to https://github.com/sbertrang/redis-distlock/blob/master/lib/Redis/DistLock.pm
14
+ # also https://github.com/sbertrang/redis-distlock/issues/2 which proposes the value-checking
15
+ # and @maltoe for https://github.com/leandromoreira/redlock-rb/pull/20#discussion_r38903633
16
+ LOCK_SCRIPT = <<-eos
17
+ if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
18
+ return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
19
+ end
20
+ eos
21
+
22
+ PTTL_SCRIPT = <<-eos
23
+ return { redis.call("get", KEYS[1]), redis.call("pttl", KEYS[1]) }
24
+ eos
25
+
26
+ # We do not want to load the scripts on every Redlock::Client initialization.
27
+ # Hence, we rely on Redis handing out SHA1 hashes of the cached scripts and
28
+ # pre-calculate them instead of loading the scripts unconditionally. If the scripts
29
+ # have not been cached on Redis, `recover_from_script_flush` has our backs.
30
+ UNLOCK_SCRIPT_SHA = Digest::SHA1.hexdigest(UNLOCK_SCRIPT)
31
+ LOCK_SCRIPT_SHA = Digest::SHA1.hexdigest(LOCK_SCRIPT)
32
+ PTTL_SCRIPT_SHA = Digest::SHA1.hexdigest(PTTL_SCRIPT)
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = '1.2.1'
2
+ VERSION = '1.2.2'
3
3
  end
data/lib/redlock.rb CHANGED
@@ -2,6 +2,7 @@ require 'redlock/version'
2
2
 
3
3
  module Redlock
4
4
  autoload :Client, 'redlock/client'
5
+ autoload :Scripts, 'redlock/scripts'
5
6
 
6
7
  class LockError < StandardError
7
8
  def initialize(resource)
data/redlock.gemspec CHANGED
@@ -15,7 +15,6 @@ Gem::Specification.new do |spec|
15
15
  spec.license = 'BSD-2-Clause'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0")
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
19
  spec.require_paths = ['lib']
21
20
 
data/spec/client_spec.rb CHANGED
@@ -44,6 +44,15 @@ RSpec.describe Redlock::Client do
44
44
  expect(resource_key).to_not be_lockable(lock_manager, ttl)
45
45
  lock_manager.unlock(lock_info)
46
46
  end
47
+
48
+ it 'does not load scripts' do
49
+ redis_client.script(:flush)
50
+
51
+ pool = ConnectionPool.new { Redis.new(url: "redis://#{redis1_host}:#{redis1_port}") }
52
+ redlock = Redlock::Client.new([pool])
53
+
54
+ expect(redis_client.info["number_of_cached_scripts"]).to eq("0")
55
+ end
47
56
  end
48
57
 
49
58
  describe 'lock' do
@@ -195,6 +204,41 @@ RSpec.describe Redlock::Client do
195
204
  lock_manager.lock(resource_key, ttl)
196
205
  lock_manager.unlock(another_lock_info)
197
206
  end
207
+
208
+ context 'when retry_count is given' do
209
+ it 'prioritizes the retry_count in option and tries up to \'retry_count\' + 1 times' do
210
+ retry_count = 1
211
+ expect(retry_count).not_to eq(lock_manager_opts[:retry_count])
212
+ expect(lock_manager).to receive(:lock_instances).exactly(retry_count + 1).times.and_return(false)
213
+ lock_manager.lock(resource_key, ttl, retry_count: retry_count)
214
+ end
215
+ end
216
+
217
+ context 'when retry_delay is given' do
218
+ it 'prioritizes the retry_delay in option and sleeps at least the specified retry_delay in milliseconds' do
219
+ retry_delay = 300
220
+ expect(retry_delay > described_class::DEFAULT_RETRY_DELAY).to eq(true)
221
+ expected_minimum = retry_delay
222
+
223
+ expect(lock_manager).to receive(:sleep) do |sleep|
224
+ expect(sleep).to satisfy { |value| value >= expected_minimum / 1000.to_f }
225
+ end.at_least(:once)
226
+ lock_manager.lock(resource_key, ttl, retry_delay: retry_delay)
227
+ end
228
+ end
229
+
230
+ context 'when retry_jitter is given' do
231
+ it 'prioritizes the retry_jitter in option and sleeps a maximum of retry_delay + retry_jitter in milliseconds' do
232
+ retry_jitter = 60
233
+ expect(retry_jitter > described_class::DEFAULT_RETRY_JITTER).to eq(true)
234
+
235
+ expected_maximum = described_class::DEFAULT_RETRY_DELAY + retry_jitter
236
+ expect(lock_manager).to receive(:sleep) do |sleep|
237
+ expect(sleep).to satisfy { |value| value < expected_maximum / 1000.to_f }
238
+ end.at_least(:once)
239
+ lock_manager.lock(resource_key, ttl, retry_jitter: retry_jitter)
240
+ end
241
+ end
198
242
  end
199
243
 
200
244
  context 'when a server goes away' 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.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Moreira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-08 00:00:00.000000000 Z
11
+ date: 2021-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -130,6 +130,7 @@ files:
130
130
  - ".travis.yml"
131
131
  - CONTRIBUTORS
132
132
  - Gemfile
133
+ - Gemfile.lock
133
134
  - LICENSE
134
135
  - Makefile
135
136
  - README.md
@@ -138,6 +139,7 @@ files:
138
139
  - docker-compose.yml
139
140
  - lib/redlock.rb
140
141
  - lib/redlock/client.rb
142
+ - lib/redlock/scripts.rb
141
143
  - lib/redlock/testing.rb
142
144
  - lib/redlock/version.rb
143
145
  - redlock.gemspec
@@ -163,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
165
  - !ruby/object:Gem::Version
164
166
  version: '0'
165
167
  requirements: []
166
- rubygems_version: 3.1.4
168
+ rubygems_version: 3.2.22
167
169
  signing_key:
168
170
  specification_version: 4
169
171
  summary: Distributed lock using Redis written in Ruby.