Skip to content

Commit

Permalink
Merge pull request #125 from bf4/consistent_with_client
Browse files Browse the repository at this point in the history
fix: always treat redis instance as pool-like
  • Loading branch information
leandromoreira authored Feb 14, 2023
2 parents b6e3031 + d7d5d8c commit d39759e
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 12 deletions.
12 changes: 9 additions & 3 deletions lib/redlock/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,27 @@ def initialize(connection)

def lock(resource, val, ttl, allow_new_lock)
recover_from_script_flush do
@redis.call('EVALSHA', Scripts::LOCK_SCRIPT_SHA, 1, resource, val, ttl, allow_new_lock)
@redis.with { |conn|
conn.call('EVALSHA', Scripts::LOCK_SCRIPT_SHA, 1, resource, val, ttl, allow_new_lock)
}
end
end

def unlock(resource, val)
recover_from_script_flush do
@redis.call('EVALSHA', Scripts::UNLOCK_SCRIPT_SHA, 1, resource, val)
@redis.with { |conn|
conn.call('EVALSHA', Scripts::UNLOCK_SCRIPT_SHA, 1, resource, val)
}
end
rescue
# Nothing to do, unlocking is just a best-effort attempt.
end

def get_remaining_ttl(resource)
recover_from_script_flush do
@redis.call('EVALSHA', Scripts::PTTL_SCRIPT_SHA, 1, resource)
@redis.with { |conn|
conn.call('EVALSHA', Scripts::PTTL_SCRIPT_SHA, 1, resource)
}
end
rescue RedisClient::ConnectionError
nil
Expand Down
29 changes: 23 additions & 6 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@
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_urls_or_clients) {
urls = Redlock::Client::DEFAULT_REDIS_URLS
if rand(0..1).zero?
RSpec.configuration.reporter.message "variant: client urls"
urls
else
RSpec.configuration.reporter.message "variant: client objects"
urls.map {|url|
ConnectionPool.new { RedisClient.new(url: url) }
}
end
}
let(:lock_manager) {
Redlock::Client.new(redis_urls_or_clients, lock_manager_opts)
}
let(:redis_client) { RedisClient.new(url: "redis://#{redis1_host}:#{redis1_port}") }
let(:resource_key) { SecureRandom.hex(3) }
let(:ttl) { 1000 }
Expand Down Expand Up @@ -37,9 +51,10 @@ def redis.with

it 'accepts ConnectionPool objects' do
pool = ConnectionPool.new { RedisClient.new(url: "redis://#{redis1_host}:#{redis1_port}") }
redlock = Redlock::Client.new([pool])
_redlock = Redlock::Client.new([pool])

lock_info = lock_manager.lock(resource_key, ttl)
expect(lock_info).to be_a(Hash)
expect(resource_key).to_not be_lockable(lock_manager, ttl)
lock_manager.unlock(lock_info)
end
Expand All @@ -48,7 +63,7 @@ def redis.with
redis_client.call('SCRIPT', 'FLUSH')

pool = ConnectionPool.new { RedisClient.new(url: "redis://#{redis1_host}:#{redis1_port}") }
redlock = Redlock::Client.new([pool])
_redlock = Redlock::Client.new([pool])

raw_info = redis_client.call('INFO')
number_of_cached_scripts = raw_info[/number_of_cached_scripts\:\d+/].split(':').last
Expand Down Expand Up @@ -197,7 +212,7 @@ def redis.with
2000
end

lock_manager = Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, retry_count: 1, retry_delay: retry_delay)
lock_manager = Redlock::Client.new(redis_urls_or_clients, retry_count: 1, retry_delay: retry_delay)
another_lock_info = lock_manager.lock(resource_key, ttl)

expect(lock_manager).to receive(:sleep) do |sleep|
Expand Down Expand Up @@ -272,7 +287,9 @@ def redis.with
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).call('SCRIPT', 'FLUSH')
@manipulated_instance.instance_variable_get(:@redis).with { |conn|
conn.call('SCRIPT', 'FLUSH')
}
end

it 'does not raise a RedisClient::CommandError: NOSCRIPT error' do
Expand Down Expand Up @@ -475,7 +492,7 @@ def redis.with

# Replace redis with unreachable instance
redis_instance = lock_manager.instance_variable_get(:@servers).first
old_redis = redis_instance.instance_variable_get(:@redis)
_old_redis = redis_instance.instance_variable_get(:@redis)
redis_instance.instance_variable_set(:@redis, unreachable_redis)

expect {
Expand Down
6 changes: 3 additions & 3 deletions spec/testing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
describe '(testing mode)' do
describe 'try_lock_instances' do
context 'when testing with bypass mode' do
before { lock_manager.testing_mode = :bypass }
before { Redlock::Client.testing_mode = :bypass }

it 'bypasses the redis servers' do
expect(lock_manager).to_not receive(:try_lock_instances_without_testing)
Expand All @@ -22,7 +22,7 @@
end

context 'when testing with fail mode' do
before { lock_manager.testing_mode = :fail }
before { Redlock::Client.testing_mode = :fail }

it 'fails' do
expect(lock_manager).to_not receive(:try_lock_instances_without_testing)
Expand All @@ -33,7 +33,7 @@
end

context 'when testing is disabled' do
before { lock_manager.testing_mode = nil }
before { Redlock::Client.testing_mode = nil }

it 'works as usual' do
expect(lock_manager).to receive(:try_lock_instances_without_testing)
Expand Down

0 comments on commit d39759e

Please sign in to comment.