redlock 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +20 -7
- data/README.md +62 -0
- data/docker-compose.yml +6 -0
- data/lib/redlock/client.rb +81 -0
- data/lib/redlock/testing.rb +2 -0
- data/lib/redlock/version.rb +1 -1
- data/redlock.gemspec +13 -11
- data/spec/client_spec.rb +158 -9
- metadata +37 -18
- data/Gemfile.lock +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a91b03cbb845e7b01556262e80e946e2d72980beeebce12d51380d7c57c57fb9
|
4
|
+
data.tar.gz: 73d38bac32a8003a556d3d8967155d15a8edbe988fc43387174ec506b21e8c99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e55aa47e007175e619e22ec2b2ca996add245143290750c79a27fbafc8bb6bd32e5c02b07d9a9752c1ad8cb7f22a10a2b245c9b963c1b4f186427d6c7a987d3a
|
7
|
+
data.tar.gz: dd61e0d187a5972ecbe09bde3345c1a6174263f06fca6b7d1da3ce8be19fe5bb69ffca4cfc24073705c5b5a40551bb1a65ffba1358c5fbaf37f10ea46a15a929
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,10 +1,23 @@
|
|
1
1
|
language: ruby
|
2
|
-
|
3
|
-
|
2
|
+
cache: bundler
|
3
|
+
sudo: false
|
4
|
+
|
4
5
|
rvm:
|
5
|
-
- 2.
|
6
|
-
- 2.5.
|
7
|
-
- 2.6.
|
6
|
+
- 2.4.9
|
7
|
+
- 2.5.7
|
8
|
+
- 2.6.5
|
9
|
+
- 2.7.0
|
10
|
+
- ruby-head
|
11
|
+
|
12
|
+
before_install:
|
13
|
+
- yes | gem update --system
|
14
|
+
- gem install bundler -v "~> 2.0"
|
15
|
+
|
8
16
|
script: bundle exec rspec spec
|
9
|
-
|
10
|
-
|
17
|
+
|
18
|
+
jobs:
|
19
|
+
allow_failures:
|
20
|
+
- rvm: ruby-head
|
21
|
+
|
22
|
+
services:
|
23
|
+
- redis-server
|
data/README.md
CHANGED
@@ -112,6 +112,68 @@ The above code will also acquire the lock if the previous lock has expired and t
|
|
112
112
|
lock_manager.lock("resource key", 3000, extend: lock_info, extend_only_if_locked: true)
|
113
113
|
```
|
114
114
|
|
115
|
+
### Querying lock status
|
116
|
+
|
117
|
+
You can check if a resource is locked:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
resource = "resource_key"
|
121
|
+
lock_info = lock_manager.lock(resource, 2000)
|
122
|
+
lock_manager.locked?(resource)
|
123
|
+
#=> true
|
124
|
+
|
125
|
+
lock_manager.unlock(lock_info)
|
126
|
+
lock_manager.locked?(resource)
|
127
|
+
#=> false
|
128
|
+
```
|
129
|
+
|
130
|
+
Any caller can call the above method to query the status. If you hold a lock and would like to check if it is valid, you can use the `valid_lock?` method:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
lock_info = lock_manager.lock("resource_key", 2000)
|
134
|
+
lock_manager.valid_lock?(lock_info)
|
135
|
+
#=> true
|
136
|
+
|
137
|
+
lock_manager.unlock(lock_info)
|
138
|
+
lock_manager.valid_lock?(lock_info)
|
139
|
+
#=> false
|
140
|
+
```
|
141
|
+
|
142
|
+
The above methods **are not safe if you are using this to time critical code**, since they return true if the lock has not expired, even if there's only (for example) 1ms left on the lock. If you want to safely time the lock validity, you can use the `get_remaining_ttl_for_lock` and `get_remaining_ttl_for_resource` methods.
|
143
|
+
|
144
|
+
Use `get_remaining_ttl_for_lock` if you hold a lock and want to check the TTL specifically for your lock:
|
145
|
+
```ruby
|
146
|
+
resource = "resource_key"
|
147
|
+
lock_info = lock_manager.lock(resource, 2000)
|
148
|
+
sleep 1
|
149
|
+
|
150
|
+
lock_manager.get_remaining_ttl_for_lock(lock_info)
|
151
|
+
#=> 986
|
152
|
+
|
153
|
+
lock_manager.unlock(lock_info)
|
154
|
+
lock_manager.get_remaining_ttl_for_lock(lock_info)
|
155
|
+
#=> nil
|
156
|
+
```
|
157
|
+
|
158
|
+
Use `get_remaining_ttl_for_resource` if you do not hold a lock, but want to know the remaining TTL on a locked resource:
|
159
|
+
```ruby
|
160
|
+
# Some part of the code
|
161
|
+
resource = "resource_key"
|
162
|
+
lock_info = lock_manager.lock(resource, 2000)
|
163
|
+
|
164
|
+
# Some other part of the code
|
165
|
+
lock_manager.locked?(resource)
|
166
|
+
#=> true
|
167
|
+
lock_manager.get_remaining_ttl_for_resource(resource)
|
168
|
+
#=> 1975
|
169
|
+
|
170
|
+
# Sometime later
|
171
|
+
lock_manager.locked?(resource)
|
172
|
+
#=> false
|
173
|
+
lock_manager.get_remaining_ttl_for_resource(resource)
|
174
|
+
#=> nil
|
175
|
+
```
|
176
|
+
|
115
177
|
## Redis client configuration
|
116
178
|
|
117
179
|
`Redlock::Client` expects URLs or Redis objects on initialization. Redis objects should be used for configuring the connection in more detail, i.e. setting username and password.
|
data/docker-compose.yml
CHANGED
@@ -11,17 +11,23 @@ services:
|
|
11
11
|
- REDIS1_PORT=6379
|
12
12
|
- REDIS2_HOST=redis2.local.com
|
13
13
|
- REDIS2_PORT=6379
|
14
|
+
- REDIS3_HOST=redis3.local.com
|
15
|
+
- REDIS3_PORT=6379
|
14
16
|
- DEFAULT_REDIS_HOST=redis1.local.com
|
15
17
|
- DEFAULT_REDIS_PORT=6379
|
16
18
|
links:
|
17
19
|
- redis1:redis1.local.com
|
18
20
|
- redis2:redis2.local.com
|
21
|
+
- redis3:redis3.local.com
|
19
22
|
depends_on:
|
20
23
|
- redis1
|
21
24
|
- redis2
|
25
|
+
- redis3
|
22
26
|
|
23
27
|
redis1:
|
24
28
|
image: redis
|
25
29
|
redis2:
|
26
30
|
image: redis
|
31
|
+
redis3:
|
32
|
+
image: redis
|
27
33
|
|
data/lib/redlock/client.rb
CHANGED
@@ -102,6 +102,43 @@ module Redlock
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
# Gets remaining ttl of a resource. The ttl is returned if the holder
|
106
|
+
# currently holds the lock and it has not expired, otherwise the method
|
107
|
+
# returns nil.
|
108
|
+
# Params:
|
109
|
+
# +lock_info+:: the lock that has been acquired when you locked the resource
|
110
|
+
def get_remaining_ttl_for_lock(lock_info)
|
111
|
+
ttl_info = try_get_remaining_ttl(lock_info[:resource])
|
112
|
+
return nil if ttl_info.nil? || ttl_info[:value] != lock_info[:value]
|
113
|
+
ttl_info[:ttl]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Gets remaining ttl of a resource. If there is no valid lock, the method
|
117
|
+
# returns nil.
|
118
|
+
# Params:
|
119
|
+
# +resource+:: the name of the resource (string) for which to check the ttl
|
120
|
+
def get_remaining_ttl_for_resource(resource)
|
121
|
+
ttl_info = try_get_remaining_ttl(resource)
|
122
|
+
return nil if ttl_info.nil?
|
123
|
+
ttl_info[:ttl]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Checks if a resource is locked
|
127
|
+
# Params:
|
128
|
+
# +lock_info+:: the lock that has been acquired when you locked the resource
|
129
|
+
def locked?(resource)
|
130
|
+
ttl = get_remaining_ttl_for_resource(resource)
|
131
|
+
!(ttl.nil? || ttl.zero?)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Checks if a lock is still valid
|
135
|
+
# Params:
|
136
|
+
# +lock_info+:: the lock that has been acquired when you locked the resource
|
137
|
+
def valid_lock?(lock_info)
|
138
|
+
ttl = get_remaining_ttl_for_lock(lock_info)
|
139
|
+
!(ttl.nil? || ttl.zero?)
|
140
|
+
end
|
141
|
+
|
105
142
|
private
|
106
143
|
|
107
144
|
class RedisInstance
|
@@ -122,6 +159,10 @@ module Redlock
|
|
122
159
|
end
|
123
160
|
eos
|
124
161
|
|
162
|
+
PTTL_SCRIPT = <<-eos
|
163
|
+
return { redis.call("get", KEYS[1]), redis.call("pttl", KEYS[1]) }
|
164
|
+
eos
|
165
|
+
|
125
166
|
module ConnectionPoolLike
|
126
167
|
def with
|
127
168
|
yield self
|
@@ -159,11 +200,20 @@ module Redlock
|
|
159
200
|
# Nothing to do, unlocking is just a best-effort attempt.
|
160
201
|
end
|
161
202
|
|
203
|
+
def get_remaining_ttl(resource)
|
204
|
+
recover_from_script_flush do
|
205
|
+
@redis.with { |conn| conn.evalsha @pttl_script_sha, keys: [resource] }
|
206
|
+
end
|
207
|
+
rescue Redis::BaseConnectionError
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
|
162
211
|
private
|
163
212
|
|
164
213
|
def load_scripts
|
165
214
|
@unlock_script_sha = @redis.with { |conn| conn.script(:load, UNLOCK_SCRIPT) }
|
166
215
|
@lock_script_sha = @redis.with { |conn| conn.script(:load, LOCK_SCRIPT) }
|
216
|
+
@pttl_script_sha = @redis.with { |conn| conn.script(:load, PTTL_SCRIPT) }
|
167
217
|
end
|
168
218
|
|
169
219
|
def recover_from_script_flush
|
@@ -228,6 +278,37 @@ module Redlock
|
|
228
278
|
end
|
229
279
|
end
|
230
280
|
|
281
|
+
def try_get_remaining_ttl(resource)
|
282
|
+
# Responses from the servers are a 2 tuple of format [lock_value, ttl].
|
283
|
+
# The lock_value is nil if it does not exist. Since servers may have
|
284
|
+
# different lock values, the responses are grouped by the lock_value and
|
285
|
+
# transofrmed into a hash: { lock_value1 => [ttl1, ttl2, ttl3],
|
286
|
+
# lock_value2 => [ttl4, tt5] }
|
287
|
+
ttls_by_value, time_elapsed = timed do
|
288
|
+
@servers.map { |s| s.get_remaining_ttl(resource) }
|
289
|
+
.select { |ttl_tuple| ttl_tuple&.first }
|
290
|
+
.group_by(&:first)
|
291
|
+
.transform_values { |ttl_tuples| ttl_tuples.map { |t| t.last } }
|
292
|
+
end
|
293
|
+
|
294
|
+
# Authoritative lock value is that which is returned by the majority of
|
295
|
+
# servers
|
296
|
+
authoritative_value, ttls =
|
297
|
+
ttls_by_value.max_by { |(lock_value, ttls)| ttls.length }
|
298
|
+
|
299
|
+
if ttls && ttls.size >= @quorum
|
300
|
+
# Return the minimum TTL of an N/2+1 selection. It will always be
|
301
|
+
# correct (it will guarantee that at least N/2+1 servers have a TTL that
|
302
|
+
# value or longer)
|
303
|
+
min_ttl = ttls.sort.last(@quorum).first
|
304
|
+
min_ttl = min_ttl - time_elapsed - drift(min_ttl)
|
305
|
+
{ value: authoritative_value, ttl: min_ttl }
|
306
|
+
else
|
307
|
+
# No lock_value is authoritatively held for the resource
|
308
|
+
nil
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
231
312
|
def drift(ttl)
|
232
313
|
# Add 2 milliseconds to the drift to account for Redis expires
|
233
314
|
# precision, which is 1 millisecond, plus 1 millisecond min drift
|
data/lib/redlock/testing.rb
CHANGED
data/lib/redlock/version.rb
CHANGED
data/redlock.gemspec
CHANGED
@@ -1,27 +1,29 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'redlock/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'redlock'
|
8
9
|
spec.version = Redlock::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage =
|
10
|
+
spec.authors = ['Leandro Moreira']
|
11
|
+
spec.email = ['leandro.ribeiro.moreira@gmail.com']
|
12
|
+
spec.summary = 'Distributed lock using Redis written in Ruby.'
|
13
|
+
spec.description = 'Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.'
|
14
|
+
spec.homepage = 'https://github.com/leandromoreira/redlock-rb'
|
14
15
|
spec.license = 'BSD-2-Clause'
|
15
16
|
|
16
17
|
spec.files = `git ls-files -z`.split("\x0")
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
20
|
+
spec.require_paths = ['lib']
|
20
21
|
|
21
22
|
spec.add_dependency 'redis', '>= 3.0.0', '< 5.0'
|
22
23
|
|
23
|
-
spec.add_development_dependency
|
24
|
+
spec.add_development_dependency 'connection_pool', '~> 2.2'
|
25
|
+
spec.add_development_dependency 'coveralls', '~> 0.8'
|
26
|
+
spec.add_development_dependency 'json', '>= 2.3.0', '~> 2.3.1'
|
24
27
|
spec.add_development_dependency 'rake', '>= 11.1.2', '~> 13.0'
|
25
28
|
spec.add_development_dependency 'rspec', '~> 3', '>= 3.0.0'
|
26
|
-
spec.add_development_dependency 'connection_pool', '~> 2.2'
|
27
29
|
end
|
data/spec/client_spec.rb
CHANGED
@@ -7,13 +7,22 @@ RSpec.describe Redlock::Client do
|
|
7
7
|
# It is recommended to have at least 3 servers in production
|
8
8
|
let(:lock_manager_opts) { { retry_count: 3 } }
|
9
9
|
let(:lock_manager) { Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, lock_manager_opts) }
|
10
|
-
let(:redis_client) { Redis.new }
|
10
|
+
let(:redis_client) { Redis.new(url: "redis://#{redis1_host}:#{redis1_port}") }
|
11
11
|
let(:resource_key) { SecureRandom.hex(3) }
|
12
12
|
let(:ttl) { 1000 }
|
13
13
|
let(:redis1_host) { ENV["REDIS1_HOST"] || "localhost" }
|
14
14
|
let(:redis1_port) { ENV["REDIS1_PORT"] || "6379" }
|
15
15
|
let(:redis2_host) { ENV["REDIS2_HOST"] || "127.0.0.1" }
|
16
16
|
let(:redis2_port) { ENV["REDIS2_PORT"] || "6379" }
|
17
|
+
let(:redis3_host) { ENV["REDIS3_HOST"] || "127.0.0.1" }
|
18
|
+
let(:redis3_port) { ENV["REDIS3_PORT"] || "6379" }
|
19
|
+
let(:unreachable_redis) {
|
20
|
+
redis = Redis.new(url: 'redis://localhost:46864')
|
21
|
+
def redis.with
|
22
|
+
yield self
|
23
|
+
end
|
24
|
+
redis
|
25
|
+
}
|
17
26
|
|
18
27
|
describe 'initialize' do
|
19
28
|
it 'accepts both redis URLs and Redis objects' do
|
@@ -212,14 +221,6 @@ RSpec.describe Redlock::Client do
|
|
212
221
|
end
|
213
222
|
end
|
214
223
|
|
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
|
-
|
223
224
|
context 'when script cache has been flushed' do
|
224
225
|
before(:each) do
|
225
226
|
@manipulated_instance = lock_manager.instance_variable_get(:@servers).first
|
@@ -367,6 +368,154 @@ RSpec.describe Redlock::Client do
|
|
367
368
|
end
|
368
369
|
end
|
369
370
|
|
371
|
+
describe 'get_remaining_ttl_for_resource' do
|
372
|
+
context 'when lock is valid' do
|
373
|
+
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
374
|
+
|
375
|
+
it 'gets the remaining ttl of a lock' do
|
376
|
+
ttl = 20_000
|
377
|
+
@lock_info = lock_manager.lock(resource_key, ttl)
|
378
|
+
remaining_ttl = lock_manager.get_remaining_ttl_for_resource(resource_key)
|
379
|
+
expect(remaining_ttl).to be_within(300).of(ttl)
|
380
|
+
end
|
381
|
+
|
382
|
+
context 'when servers respond with varying ttls' do
|
383
|
+
let (:servers) {
|
384
|
+
[
|
385
|
+
"redis://#{redis1_host}:#{redis1_port}",
|
386
|
+
"redis://#{redis2_host}:#{redis2_port}",
|
387
|
+
"redis://#{redis3_host}:#{redis3_port}"
|
388
|
+
]
|
389
|
+
}
|
390
|
+
let (:redlock) { Redlock::Client.new(servers) }
|
391
|
+
after(:each) { redlock.unlock(@lock_info) if @lock_info }
|
392
|
+
|
393
|
+
it 'returns the minimum ttl value' do
|
394
|
+
ttl = 20_000
|
395
|
+
@lock_info = redlock.lock(resource_key, ttl)
|
396
|
+
|
397
|
+
# Mock redis server responses to return different ttls
|
398
|
+
returned_ttls = [20_000, 15_000, 10_000]
|
399
|
+
redlock.instance_variable_get(:@servers).each_with_index do |server, index|
|
400
|
+
allow(server).to(receive(:get_remaining_ttl))
|
401
|
+
.with(resource_key)
|
402
|
+
.and_return([@lock_info[:value], returned_ttls[index]])
|
403
|
+
end
|
404
|
+
|
405
|
+
remaining_ttl = redlock.get_remaining_ttl_for_lock(@lock_info)
|
406
|
+
|
407
|
+
# Assert that the TTL is closest to the closest to the correct value
|
408
|
+
expect(remaining_ttl).to be_within(300).of(returned_ttls[1])
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
context 'when lock is not valid' do
|
414
|
+
it 'returns nil' do
|
415
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
416
|
+
lock_manager.unlock(lock_info)
|
417
|
+
remaining_ttl = lock_manager.get_remaining_ttl_for_resource(resource_key)
|
418
|
+
expect(remaining_ttl).to be_nil
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
context 'when server goes away' do
|
423
|
+
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
424
|
+
|
425
|
+
it 'does not raise an error on connection issues' do
|
426
|
+
@lock_info = lock_manager.lock(resource_key, ttl)
|
427
|
+
|
428
|
+
# Replace redis with unreachable instance
|
429
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
430
|
+
old_redis = redis_instance.instance_variable_get(:@redis)
|
431
|
+
redis_instance.instance_variable_set(:@redis, unreachable_redis)
|
432
|
+
|
433
|
+
expect {
|
434
|
+
remaining_ttl = lock_manager.get_remaining_ttl_for_resource(resource_key)
|
435
|
+
expect(remaining_ttl).to be_nil
|
436
|
+
}.to_not raise_error
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
context 'when a server comes back' do
|
441
|
+
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
442
|
+
|
443
|
+
it 'recovers from connection issues' do
|
444
|
+
@lock_info = lock_manager.lock(resource_key, ttl)
|
445
|
+
|
446
|
+
# Replace redis with unreachable instance
|
447
|
+
redis_instance = lock_manager.instance_variable_get(:@servers).first
|
448
|
+
old_redis = redis_instance.instance_variable_get(:@redis)
|
449
|
+
redis_instance.instance_variable_set(:@redis, unreachable_redis)
|
450
|
+
|
451
|
+
expect(lock_manager.get_remaining_ttl_for_resource(resource_key)).to be_nil
|
452
|
+
|
453
|
+
# Restore redis
|
454
|
+
redis_instance.instance_variable_set(:@redis, old_redis)
|
455
|
+
expect(lock_manager.get_remaining_ttl_for_resource(resource_key)).to be_truthy
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
describe 'get_remaining_ttl_for_lock' do
|
461
|
+
context 'when lock is valid' do
|
462
|
+
it 'gets the remaining ttl of a lock' do
|
463
|
+
ttl = 20_000
|
464
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
465
|
+
remaining_ttl = lock_manager.get_remaining_ttl_for_lock(lock_info)
|
466
|
+
expect(remaining_ttl).to be_within(300).of(ttl)
|
467
|
+
lock_manager.unlock(lock_info)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
context 'when lock is not valid' do
|
472
|
+
it 'returns nil' do
|
473
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
474
|
+
lock_manager.unlock(lock_info)
|
475
|
+
remaining_ttl = lock_manager.get_remaining_ttl_for_lock(lock_info)
|
476
|
+
expect(remaining_ttl).to be_nil
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
describe 'locked?' do
|
482
|
+
context 'when lock is available' do
|
483
|
+
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
484
|
+
|
485
|
+
it 'returns true' do
|
486
|
+
@lock_info = lock_manager.lock(resource_key, ttl)
|
487
|
+
expect(lock_manager).to be_locked(resource_key)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context 'when lock is not available' do
|
492
|
+
it 'returns false' do
|
493
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
494
|
+
lock_manager.unlock(lock_info)
|
495
|
+
expect(lock_manager).not_to be_locked(resource_key)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
describe 'valid_lock?' do
|
501
|
+
context 'when lock is available' do
|
502
|
+
after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
|
503
|
+
|
504
|
+
it 'returns true' do
|
505
|
+
@lock_info = lock_manager.lock(resource_key, ttl)
|
506
|
+
expect(lock_manager).to be_valid_lock(@lock_info)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
context 'when lock is not available' do
|
511
|
+
it 'returns false' do
|
512
|
+
lock_info = lock_manager.lock(resource_key, ttl)
|
513
|
+
lock_manager.unlock(lock_info)
|
514
|
+
expect(lock_manager).not_to be_valid_lock(lock_info)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
370
519
|
describe '#default_time_source' do
|
371
520
|
context 'when CLOCK_MONOTONIC is available (MRI, JRuby)' do
|
372
521
|
it 'returns a callable using Process.clock_gettime()' 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.1
|
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-
|
11
|
+
date: 2020-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -30,6 +30,20 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '5.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: connection_pool
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.2'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.2'
|
33
47
|
- !ruby/object:Gem::Dependency
|
34
48
|
name: coveralls
|
35
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,6 +58,26 @@ dependencies:
|
|
44
58
|
- - "~>"
|
45
59
|
- !ruby/object:Gem::Version
|
46
60
|
version: '0.8'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: json
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 2.3.0
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 2.3.1
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.3.0
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 2.3.1
|
47
81
|
- !ruby/object:Gem::Dependency
|
48
82
|
name: rake
|
49
83
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,20 +118,6 @@ dependencies:
|
|
84
118
|
- - ">="
|
85
119
|
- !ruby/object:Gem::Version
|
86
120
|
version: 3.0.0
|
87
|
-
- !ruby/object:Gem::Dependency
|
88
|
-
name: connection_pool
|
89
|
-
requirement: !ruby/object:Gem::Requirement
|
90
|
-
requirements:
|
91
|
-
- - "~>"
|
92
|
-
- !ruby/object:Gem::Version
|
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'
|
101
121
|
description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
|
102
122
|
email:
|
103
123
|
- leandro.ribeiro.moreira@gmail.com
|
@@ -110,7 +130,6 @@ files:
|
|
110
130
|
- ".travis.yml"
|
111
131
|
- CONTRIBUTORS
|
112
132
|
- Gemfile
|
113
|
-
- Gemfile.lock
|
114
133
|
- LICENSE
|
115
134
|
- Makefile
|
116
135
|
- README.md
|
@@ -144,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
163
|
- !ruby/object:Gem::Version
|
145
164
|
version: '0'
|
146
165
|
requirements: []
|
147
|
-
rubygems_version: 3.1.
|
166
|
+
rubygems_version: 3.1.4
|
148
167
|
signing_key:
|
149
168
|
specification_version: 4
|
150
169
|
summary: Distributed lock using Redis written in Ruby.
|
data/Gemfile.lock
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
redlock (1.2.0)
|
5
|
-
redis (>= 3.0.0, < 5.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
connection_pool (2.2.2)
|
11
|
-
coveralls (0.8.22)
|
12
|
-
json (>= 1.8, < 3)
|
13
|
-
simplecov (~> 0.16.1)
|
14
|
-
term-ansicolor (~> 1.3)
|
15
|
-
thor (~> 0.19.4)
|
16
|
-
tins (~> 1.6)
|
17
|
-
diff-lcs (1.3)
|
18
|
-
docile (1.3.1)
|
19
|
-
json (2.1.0)
|
20
|
-
rake (13.0.1)
|
21
|
-
redis (4.1.1)
|
22
|
-
rspec (3.5.0)
|
23
|
-
rspec-core (~> 3.5.0)
|
24
|
-
rspec-expectations (~> 3.5.0)
|
25
|
-
rspec-mocks (~> 3.5.0)
|
26
|
-
rspec-core (3.5.4)
|
27
|
-
rspec-support (~> 3.5.0)
|
28
|
-
rspec-expectations (3.5.0)
|
29
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
-
rspec-support (~> 3.5.0)
|
31
|
-
rspec-mocks (3.5.0)
|
32
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
-
rspec-support (~> 3.5.0)
|
34
|
-
rspec-support (3.5.0)
|
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
|
-
term-ansicolor (1.6.0)
|
41
|
-
tins (~> 1.0)
|
42
|
-
thor (0.19.4)
|
43
|
-
tins (1.16.3)
|
44
|
-
|
45
|
-
PLATFORMS
|
46
|
-
ruby
|
47
|
-
|
48
|
-
DEPENDENCIES
|
49
|
-
connection_pool (~> 2.2)
|
50
|
-
coveralls (~> 0.8)
|
51
|
-
rake (~> 13.0, >= 11.1.2)
|
52
|
-
redlock!
|
53
|
-
rspec (~> 3, >= 3.0.0)
|
54
|
-
|
55
|
-
BUNDLED WITH
|
56
|
-
1.17.2
|