Skip to content

Commit aba5b7b

Browse files
committed
retry connection prelude on connect errors
1 parent 7655bb8 commit aba5b7b

File tree

2 files changed

+93
-16
lines changed

2 files changed

+93
-16
lines changed

lib/redis_client.rb

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ def initialize(config, **)
248248
super
249249
@middlewares = config.middlewares_stack.new(self)
250250
@raw_connection = nil
251+
@prelude_sent = false
251252
@disable_reconnection = false
252253
@retry_attempt = nil
253254
end
@@ -745,6 +746,7 @@ def ensure_connected(retryable: true)
745746

746747
if @disable_reconnection
747748
@raw_connection.retry_attempt = nil
749+
send_prelude unless @prelude_sent
748750
if block_given?
749751
yield @raw_connection
750752
else
@@ -798,13 +800,17 @@ def ensure_connected(retryable: true)
798800
def raw_connection
799801
if @raw_connection.nil? || !@raw_connection.revalidate
800802
connect
803+
elsif !@prelude_sent
804+
send_prelude
801805
end
806+
802807
@raw_connection.retry_attempt = @retry_attempt
803808
@raw_connection
804809
end
805810

806811
def connect
807812
@pid = PIDCache.pid
813+
@prelude_sent = false
808814

809815
if @raw_connection&.revalidate
810816
@middlewares.connect(config) do
@@ -822,6 +828,24 @@ def connect
822828
end
823829
@raw_connection.retry_attempt = @retry_attempt
824830

831+
send_prelude
832+
rescue FailoverError, CannotConnectError => error
833+
error._set_config(config)
834+
raise error
835+
rescue ConnectionError => error
836+
connect_error = CannotConnectError.with_config(error.message, config)
837+
connect_error.set_backtrace(error.backtrace)
838+
raise connect_error
839+
rescue CommandError => error
840+
if error.message.match?(/ERR unknown command ['`]HELLO['`]/)
841+
raise UnsupportedServer,
842+
"redis-client requires Redis 6+ with HELLO command available (#{config.server_url})"
843+
else
844+
raise
845+
end
846+
end
847+
848+
def send_prelude
825849
prelude = config.connection_prelude.dup
826850

827851
if id
@@ -842,20 +866,8 @@ def connect
842866
end
843867
end
844868
end
845-
rescue FailoverError, CannotConnectError => error
846-
error._set_config(config)
847-
raise error
848-
rescue ConnectionError => error
849-
connect_error = CannotConnectError.with_config(error.message, config)
850-
connect_error.set_backtrace(error.backtrace)
851-
raise connect_error
852-
rescue CommandError => error
853-
if error.message.match?(/ERR unknown command ['`]HELLO['`]/)
854-
raise UnsupportedServer,
855-
"redis-client requires Redis 6+ with HELLO command available (#{config.server_url})"
856-
else
857-
raise
858-
end
869+
870+
@prelude_sent = true
859871
end
860872
end
861873

test/shared/redis_client_tests.rb

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,12 @@ def test_command_missing
328328
end
329329

330330
def test_authentication
331-
@redis.call("ACL", "SETUSER", "AzureDiamond", ">hunter2", "on", "+PING")
331+
@redis.call("ACL", "DELUSER", "AzureDiamond")
332+
@redis.call("ACL", "SETUSER", "AzureDiamond", ">hunter2", "on", "+PING", "+CLIENT")
333+
@redis.call("ACL", "DELUSER", "backup_admin")
334+
@redis.call("ACL", "SETUSER", "backup_admin", ">hunter2", "on", "~*", "&*", "+@all")
335+
backup = new_client(username: "backup_admin", password: "hunter2")
336+
backup.call("ACL", "SETUSER", "default", "off")
332337

333338
client = new_client(username: "AzureDiamond", password: "hunter2")
334339
assert_equal "PONG", client.call("PING")
@@ -337,10 +342,70 @@ def test_authentication
337342
client.call("GET", "foo")
338343
end
339344

345+
# Wrong password
340346
client = new_client(username: "AzureDiamond", password: "trolilol")
341-
assert_raises RedisClient::AuthenticationError do
347+
error = assert_raises RedisClient::AuthenticationError do
342348
client.call("PING")
343349
end
350+
assert_match(/WRONGPASS invalid username-password pair/, error.message)
351+
352+
# The same error is raised, this shows that the client retried AUTH and didn't fall back to the default user
353+
error = assert_raises RedisClient::AuthenticationError do
354+
client.call("PING")
355+
end
356+
assert_match(/WRONGPASS invalid username-password pair/, error.message)
357+
358+
# Correct password, but user disabled
359+
backup.call("ACL", "SETUSER", "AzureDiamond", "<hunter2", ">trolilol", "off")
360+
error = assert_raises RedisClient::AuthenticationError do
361+
client.call_once("PING")
362+
end
363+
assert_match(/WRONGPASS invalid username-password pair/, error.message)
364+
365+
# Correct password, user enabled
366+
backup.call("ACL", "SETUSER", "AzureDiamond", "on")
367+
assert_equal "PONG", client.call_once("PING")
368+
assert_match(/user=AzureDiamond/, client.call("CLIENT", "INFO"))
369+
370+
# Wrong username
371+
client = new_client(username: "GreenOpal", password: "trolilol")
372+
error = assert_raises RedisClient::AuthenticationError do
373+
client.call("PING")
374+
end
375+
assert_match(/WRONGPASS invalid username-password pair/, error.message)
376+
ensure
377+
backup.call("ACL", "SETUSER", "default", "on")
378+
end
379+
380+
def test_prelude_failure
381+
client = new_client(db: 100)
382+
error = assert_raises RedisClient::CommandError do
383+
client.call("PING")
384+
end
385+
assert_match(/ERR DB index is out of range/, error.message)
386+
387+
error = assert_raises RedisClient::CommandError do
388+
client.call("PING")
389+
end
390+
assert_match(/ERR DB index is out of range/, error.message)
391+
end
392+
393+
def test_noauth
394+
@redis.call("ACL", "DELUSER", "AzureDiamond")
395+
@redis.call("ACL", "SETUSER", "AzureDiamond", ">hunter2", "on", "~*", "&*", "+@all")
396+
backup = new_client(username: "AzureDiamond", password: "hunter2")
397+
backup.call("ACL", "SETUSER", "default", "off")
398+
399+
client = new_client(protocol: 2)
400+
error = assert_raises RedisClient::CommandError do
401+
client.call("PING")
402+
end
403+
assert_match(/NOAUTH Authentication required/, error.message)
404+
405+
backup.call("ACL", "SETUSER", "default", "on")
406+
client.call("PING")
407+
ensure
408+
backup.call("ACL", "SETUSER", "default", "on")
344409
end
345410

346411
def test_transaction

0 commit comments

Comments
 (0)