redlock 1.2.1 → 1.2.2
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 +4 -4
- data/.gitignore +0 -1
- data/Gemfile.lock +59 -0
- data/Makefile +3 -0
- data/lib/redlock/client.rb +28 -36
- data/lib/redlock/scripts.rb +34 -0
- data/lib/redlock/version.rb +1 -1
- data/lib/redlock.rb +1 -0
- data/redlock.gemspec +0 -1
- data/spec/client_spec.rb +44 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1870dc8b8577a8e8545fd952fd6c2f1ff3ac1f336bff5f632b3b89eb92eb0069
|
4
|
+
data.tar.gz: b5153369a5cd331a4bbc0884efd657692da4b824e0cbe0dd8ed78fdd407d7047
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 747aa1155827f513a0612e6610dd754143c3fb65c50aaeca87506830d91c83ec40a89f7fcfb81d22395ea2cc16048cc99136894f0b6a8adb7455d2adae60c244
|
7
|
+
data.tar.gz: b033303f287bdbeca822c6b3f0ad829531c5740ad0716f6a6cdeb75370ea9e3874838b761f65ac242b07a07af0b1c0ab955e288059f0953aed3aeb686773a6cc
|
data/.gitignore
CHANGED
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
data/lib/redlock/client.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
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
|
255
|
-
|
246
|
+
if retry_delay.respond_to?(:call)
|
247
|
+
retry_delay.call(attempt_number)
|
256
248
|
else
|
257
|
-
|
249
|
+
retry_delay
|
258
250
|
end
|
259
251
|
|
260
|
-
(retry_delay + rand(
|
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
|
data/lib/redlock/version.rb
CHANGED
data/lib/redlock.rb
CHANGED
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.
|
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:
|
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.
|
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.
|