Skip to content

Commit ab5415c

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

File tree

2 files changed

+80
-16
lines changed

2 files changed

+80
-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: 54 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,57 @@ 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_noauth
381+
@redis.call("ACL", "DELUSER", "AzureDiamond")
382+
@redis.call("ACL", "SETUSER", "AzureDiamond", ">hunter2", "on", "~*", "&*", "+@all")
383+
backup = new_client(username: "AzureDiamond", password: "hunter2")
384+
backup.call("ACL", "SETUSER", "default", "off")
385+
386+
client = new_client(protocol: 2)
387+
error = assert_raises RedisClient::CommandError do
388+
client.call("PING")
389+
end
390+
assert_match(/NOAUTH Authentication required/, error.message)
391+
392+
backup.call("ACL", "SETUSER", "default", "on")
393+
client.call("PING")
394+
ensure
395+
backup.call("ACL", "SETUSER", "default", "on")
344396
end
345397

346398
def test_transaction

0 commit comments

Comments
 (0)