From 3c42db61ff6909792e5b15e28be34c18347852a1 Mon Sep 17 00:00:00 2001 From: tylerbrewer2 Date: Fri, 18 Nov 2022 20:05:07 -0500 Subject: [PATCH] Treat ReadOnlyError as a ConnectionError Fix: https://github.com/redis/redis-rb/pull/1168 --- CHANGELOG.md | 2 ++ lib/redis/errors.rb | 7 ++++--- test/redis/internals_test.rb | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 891622c0b..b65f3f00f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Treat ReadOnlyError as ConnectionError. See #1168. + # 5.0.5 - Fix automatic disconnection when the process was forked. See #1157. diff --git a/lib/redis/errors.rb b/lib/redis/errors.rb index b6198ee1c..bef5a8d45 100644 --- a/lib/redis/errors.rb +++ b/lib/redis/errors.rb @@ -26,9 +26,6 @@ class PermissionError < CommandError class WrongTypeError < CommandError end - class ReadOnlyError < CommandError - end - class OutOfMemoryError < CommandError end @@ -52,6 +49,10 @@ class TimeoutError < BaseConnectionError class InheritedError < BaseConnectionError end + # Generally raised during Redis failover scenarios + class ReadOnlyError < BaseConnectionError + end + # Raised when client options are invalid. class InvalidClientOptionError < BaseError end diff --git a/test/redis/internals_test.rb b/test/redis/internals_test.rb index 6cad48a5b..1b718a1fc 100644 --- a/test/redis/internals_test.rb +++ b/test/redis/internals_test.rb @@ -291,4 +291,38 @@ def test_can_be_duped_to_create_a_new_connection assert_equal clients + 1, r.info["connected_clients"].to_i end + + def test_reconnect_on_readonly_errors + tcp_server = TCPServer.new("127.0.0.1", 0) + tcp_server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + port = tcp_server.addr[1] + + server_thread = Thread.new do + session = tcp_server.accept + io = RedisClient::RubyConnection::BufferedIO.new(session, read_timeout: 1, write_timeout: 1) + 2.times do + command = RedisClient::RESP3.load(io) + case command.first.upcase + when "PING" + session.write("+PONG\r\n") + when "SET" + session.write("-READONLY You can't write against a read only replica.\r\n") + else + session.write("-ERR Unknown command #{command.first}\r\n") + end + end + session.close + end + + redis = Redis.new(host: "127.0.0.1", port: port, timeout: 2, reconnect_attempts: 0) + assert_equal "PONG", redis.ping + + assert_raises Redis::ReadOnlyError do + redis.set("foo", "bar") + end + + refute_predicate redis, :connected? + ensure + server_thread&.kill + end end