From 64efd8f7ac04504d098d6f8f4eacfd35a72e20ce Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 18 Aug 2023 15:49:57 -0700 Subject: [PATCH 1/2] Introduce Trilogy::EOFError This is a new Ruby error to represent the TRILOGY_CLOSED_CONNECTION status code, which is the status returned when read returns 0, write hits an EPIPE, or when attempting to communicate through a socket after it was shutdown/closed due to a previous error. This subclasses BaseConnectionError so that it can be caught by `rescue Trilogy::ConnectionError` (which is what we'd like to recommend for detecting network/communication issues) and will always include the TRILOGY_CLOSED_CONNECTION as part of the message for backwards compatibility, as that's how currently Rails and others have been detecting it. Previously this would have been raised as a QueryError. --- contrib/ruby/ext/trilogy-ruby/cext.c | 11 +++++++++-- contrib/ruby/lib/trilogy/error.rb | 6 ++++++ contrib/ruby/test/test_helper.rb | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index 7e128e1a..5244a306 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -18,7 +18,7 @@ VALUE Trilogy_CastError; static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError, Trilogy_ConnectionClosedError, Trilogy_ConnectionRefusedError, Trilogy_ConnectionResetError, - Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result; + Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_EOFError; static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout, id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count, @@ -96,7 +96,7 @@ static void trilogy_syserr_fail_str(int e, VALUE msg) rb_raise(Trilogy_ConnectionResetError, "%" PRIsVALUE, msg); } else if (e == EPIPE) { // Backwards compatibility: This error class makes no sense, but matches legacy behavior - rb_raise(Trilogy_QueryError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION", msg); + rb_raise(Trilogy_EOFError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION: EPIPE", msg); } else { VALUE exc = rb_funcall(Trilogy_SyscallError, id_from_errno, 2, INT2NUM(e), msg); rb_exc_raise(exc); @@ -158,6 +158,10 @@ static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *ms rb_raise(Trilogy_BaseConnectionError, "%" PRIsVALUE ": TRILOGY_DNS_ERROR", rbmsg); } + case TRILOGY_CLOSED_CONNECTION: { + rb_raise(Trilogy_EOFError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION", rbmsg); + } + default: rb_raise(Trilogy_QueryError, "%" PRIsVALUE ": %s", rbmsg, trilogy_error(rc)); } @@ -1176,6 +1180,9 @@ RUBY_FUNC_EXPORTED void Init_cext() Trilogy_CastError = rb_const_get(Trilogy, rb_intern("CastError")); rb_global_variable(&Trilogy_CastError); + Trilogy_EOFError = rb_const_get(Trilogy, rb_intern("EOFError")); + rb_global_variable(&Trilogy_EOFError); + id_socket = rb_intern("socket"); id_host = rb_intern("host"); id_port = rb_intern("port"); diff --git a/contrib/ruby/lib/trilogy/error.rb b/contrib/ruby/lib/trilogy/error.rb index 72569f9e..756c74c2 100644 --- a/contrib/ruby/lib/trilogy/error.rb +++ b/contrib/ruby/lib/trilogy/error.rb @@ -112,7 +112,13 @@ class SSLError < BaseError include ConnectionError end + # Raised on attempt to use connection which was explicitly closed by the user class ConnectionClosed < IOError include ConnectionError end + + # Occurrs when a socket read or write returns EOF or when an operation is + # attempted on a socket which previously encountered an error. + class EOFError < BaseConnectionError + end end diff --git a/contrib/ruby/test/test_helper.rb b/contrib/ruby/test/test_helper.rb index 2c288ecc..2adf517e 100644 --- a/contrib/ruby/test/test_helper.rb +++ b/contrib/ruby/test/test_helper.rb @@ -137,7 +137,7 @@ def create_test_table(client) def assert_raises_connection_error(&block) err = assert_raises(Trilogy::Error, &block) - if err.is_a?(Trilogy::QueryError) + if err.is_a?(Trilogy::EOFError) assert_includes err.message, "TRILOGY_CLOSED_CONNECTION" elsif err.is_a?(Trilogy::SSLError) assert_includes err.message, "unexpected eof while reading" From 3bc3927bef78005b621b98aff7a997d93f2af33e Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 21 Aug 2023 11:10:07 -0700 Subject: [PATCH 2/2] Include ConnectionError in SyscallErrors --- contrib/ruby/lib/trilogy/error.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/ruby/lib/trilogy/error.rb b/contrib/ruby/lib/trilogy/error.rb index 756c74c2..e4af3a47 100644 --- a/contrib/ruby/lib/trilogy/error.rb +++ b/contrib/ruby/lib/trilogy/error.rb @@ -20,7 +20,7 @@ class SyscallError .select { |c| c.is_a?(Class) && c < SystemCallError } .each do |c| errno_name = c.to_s.split('::').last - ERRORS[c::Errno] = const_set(errno_name, Class.new(c) { include Trilogy::Error }) + ERRORS[c::Errno] = const_set(errno_name, Class.new(c) { include Trilogy::ConnectionError }) end ERRORS.freeze