spec/client_spec.rb in redlock-1.2.0 vs spec/client_spec.rb in redlock-1.2.1
- old
+ new
@@ -5,17 +5,26 @@
RSpec.describe Redlock::Client do
# It is recommended to have at least 3 servers in production
let(:lock_manager_opts) { { retry_count: 3 } }
let(:lock_manager) { Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, lock_manager_opts) }
- let(:redis_client) { Redis.new }
+ let(:redis_client) { Redis.new(url: "redis://#{redis1_host}:#{redis1_port}") }
let(:resource_key) { SecureRandom.hex(3) }
let(:ttl) { 1000 }
let(:redis1_host) { ENV["REDIS1_HOST"] || "localhost" }
let(:redis1_port) { ENV["REDIS1_PORT"] || "6379" }
let(:redis2_host) { ENV["REDIS2_HOST"] || "127.0.0.1" }
let(:redis2_port) { ENV["REDIS2_PORT"] || "6379" }
+ let(:redis3_host) { ENV["REDIS3_HOST"] || "127.0.0.1" }
+ let(:redis3_port) { ENV["REDIS3_PORT"] || "6379" }
+ let(:unreachable_redis) {
+ redis = Redis.new(url: 'redis://localhost:46864')
+ def redis.with
+ yield self
+ end
+ redis
+ }
describe 'initialize' do
it 'accepts both redis URLs and Redis objects' do
servers = [ "redis://#{redis1_host}:#{redis1_port}", Redis.new(url: "redis://#{redis2_host}:#{redis2_port}") ]
redlock = Redlock::Client.new(servers)
@@ -210,18 +219,10 @@
redis_instance.instance_variable_set(:@redis, old_redis)
expect(lock_manager.lock(resource_key, ttl)).to be_truthy
end
end
- def unreachable_redis
- redis = Redis.new(url: 'redis://localhost:46864')
- def redis.with
- yield self
- end
- redis
- end
-
context 'when script cache has been flushed' do
before(:each) do
@manipulated_instance = lock_manager.instance_variable_get(:@servers).first
@manipulated_instance.instance_variable_get(:@redis).script(:flush)
end
@@ -361,9 +362,157 @@
begin
lock_manager.lock!(resource_key, ttl) { fail }
rescue Redlock::LockError
end
end.to_not raise_error
+ end
+ end
+ end
+
+ describe 'get_remaining_ttl_for_resource' do
+ context 'when lock is valid' do
+ after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
+
+ it 'gets the remaining ttl of a lock' do
+ ttl = 20_000
+ @lock_info = lock_manager.lock(resource_key, ttl)
+ remaining_ttl = lock_manager.get_remaining_ttl_for_resource(resource_key)
+ expect(remaining_ttl).to be_within(300).of(ttl)
+ end
+
+ context 'when servers respond with varying ttls' do
+ let (:servers) {
+ [
+ "redis://#{redis1_host}:#{redis1_port}",
+ "redis://#{redis2_host}:#{redis2_port}",
+ "redis://#{redis3_host}:#{redis3_port}"
+ ]
+ }
+ let (:redlock) { Redlock::Client.new(servers) }
+ after(:each) { redlock.unlock(@lock_info) if @lock_info }
+
+ it 'returns the minimum ttl value' do
+ ttl = 20_000
+ @lock_info = redlock.lock(resource_key, ttl)
+
+ # Mock redis server responses to return different ttls
+ returned_ttls = [20_000, 15_000, 10_000]
+ redlock.instance_variable_get(:@servers).each_with_index do |server, index|
+ allow(server).to(receive(:get_remaining_ttl))
+ .with(resource_key)
+ .and_return([@lock_info[:value], returned_ttls[index]])
+ end
+
+ remaining_ttl = redlock.get_remaining_ttl_for_lock(@lock_info)
+
+ # Assert that the TTL is closest to the closest to the correct value
+ expect(remaining_ttl).to be_within(300).of(returned_ttls[1])
+ end
+ end
+ end
+
+ context 'when lock is not valid' do
+ it 'returns nil' do
+ lock_info = lock_manager.lock(resource_key, ttl)
+ lock_manager.unlock(lock_info)
+ remaining_ttl = lock_manager.get_remaining_ttl_for_resource(resource_key)
+ expect(remaining_ttl).to be_nil
+ end
+ end
+
+ context 'when server goes away' do
+ after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
+
+ it 'does not raise an error on connection issues' do
+ @lock_info = lock_manager.lock(resource_key, ttl)
+
+ # Replace redis with unreachable instance
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
+ old_redis = redis_instance.instance_variable_get(:@redis)
+ redis_instance.instance_variable_set(:@redis, unreachable_redis)
+
+ expect {
+ remaining_ttl = lock_manager.get_remaining_ttl_for_resource(resource_key)
+ expect(remaining_ttl).to be_nil
+ }.to_not raise_error
+ end
+ end
+
+ context 'when a server comes back' do
+ after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
+
+ it 'recovers from connection issues' do
+ @lock_info = lock_manager.lock(resource_key, ttl)
+
+ # Replace redis with unreachable instance
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
+ old_redis = redis_instance.instance_variable_get(:@redis)
+ redis_instance.instance_variable_set(:@redis, unreachable_redis)
+
+ expect(lock_manager.get_remaining_ttl_for_resource(resource_key)).to be_nil
+
+ # Restore redis
+ redis_instance.instance_variable_set(:@redis, old_redis)
+ expect(lock_manager.get_remaining_ttl_for_resource(resource_key)).to be_truthy
+ end
+ end
+ end
+
+ describe 'get_remaining_ttl_for_lock' do
+ context 'when lock is valid' do
+ it 'gets the remaining ttl of a lock' do
+ ttl = 20_000
+ lock_info = lock_manager.lock(resource_key, ttl)
+ remaining_ttl = lock_manager.get_remaining_ttl_for_lock(lock_info)
+ expect(remaining_ttl).to be_within(300).of(ttl)
+ lock_manager.unlock(lock_info)
+ end
+ end
+
+ context 'when lock is not valid' do
+ it 'returns nil' do
+ lock_info = lock_manager.lock(resource_key, ttl)
+ lock_manager.unlock(lock_info)
+ remaining_ttl = lock_manager.get_remaining_ttl_for_lock(lock_info)
+ expect(remaining_ttl).to be_nil
+ end
+ end
+ end
+
+ describe 'locked?' do
+ context 'when lock is available' do
+ after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
+
+ it 'returns true' do
+ @lock_info = lock_manager.lock(resource_key, ttl)
+ expect(lock_manager).to be_locked(resource_key)
+ end
+ end
+
+ context 'when lock is not available' do
+ it 'returns false' do
+ lock_info = lock_manager.lock(resource_key, ttl)
+ lock_manager.unlock(lock_info)
+ expect(lock_manager).not_to be_locked(resource_key)
+ end
+ end
+ end
+
+ describe 'valid_lock?' do
+ context 'when lock is available' do
+ after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
+
+ it 'returns true' do
+ @lock_info = lock_manager.lock(resource_key, ttl)
+ expect(lock_manager).to be_valid_lock(@lock_info)
+ end
+ end
+
+ context 'when lock is not available' do
+ it 'returns false' do
+ lock_info = lock_manager.lock(resource_key, ttl)
+ lock_manager.unlock(lock_info)
+ expect(lock_manager).not_to be_valid_lock(lock_info)
end
end
end
describe '#default_time_source' do