Skip to content

Commit

Permalink
Raise all SystemCallErrors as Trilogy::Error.
Browse files Browse the repository at this point in the history
This allows syscall errors to be rescued generically by Trilogy::Error,
or handled individually by consumers using their oriinal Errno classes.

To make this work, we subclass all of the SystemCallErrors and include
the base error module on each of them.

I've also removed the constructors from the existing Errno class subclasses
(Trilogy::TimeoutError, Trilogy::ConnectionClosedError, etc.). We never
set an error_code on syscall errors, so the constructors are not needed.
  • Loading branch information
adrianna-chang-shopify committed Mar 27, 2023
1 parent a269683 commit b243b50
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 19 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
### Fixed
- Fix msec values for time columns. #61

### Changed
- All SystemCallErrors classified as `Trilogy::Error`.

## 2.3.0

### Added
Expand Down
12 changes: 8 additions & 4 deletions contrib/ruby/ext/trilogy-ruby/cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
VALUE Trilogy_CastError;
static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError,
Trilogy_ConnectionClosedError, Trilogy_ConnectionRefusedError, Trilogy_ConnectionResetError,
Trilogy_TimeoutError, Trilogy_Result;
Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result;

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,
id_ivar_affected_rows, id_ivar_fields, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password,
id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key,
id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result,
id_from_code, id_connection_options;
id_from_code, id_from_errno, id_connection_options;

struct trilogy_ctx {
trilogy_conn_t conn;
Expand Down Expand Up @@ -87,8 +87,8 @@ static void trilogy_syserr_fail_str(int e, VALUE msg)
} else if (e == ECONNRESET) {
rb_raise(Trilogy_ConnectionResetError, "%" PRIsVALUE, msg);
} else {
// TODO: All syserr should be wrapped.
rb_syserr_fail_str(e, msg);
VALUE exc = rb_funcall(Trilogy_SyscallError, id_from_errno, 2, msg, INT2NUM(e));
rb_exc_raise(exc);
}
}

Expand Down Expand Up @@ -1065,6 +1065,9 @@ RUBY_FUNC_EXPORTED void Init_cext()
Trilogy_Result = rb_const_get(Trilogy, rb_intern("Result"));
rb_global_variable(&Trilogy_Result);

Trilogy_SyscallError = rb_const_get(Trilogy, rb_intern("SyscallError"));
rb_global_variable(&Trilogy_SyscallError);

Trilogy_CastError = rb_const_get(Trilogy, rb_intern("CastError"));
rb_global_variable(&Trilogy_CastError);

Expand Down Expand Up @@ -1096,6 +1099,7 @@ RUBY_FUNC_EXPORTED void Init_cext()
id_multi_statement = rb_intern("multi_statement");
id_multi_result = rb_intern("multi_result");
id_from_code = rb_intern("from_code");
id_from_errno = rb_intern("from_errno");
id_ivar_affected_rows = rb_intern("@affected_rows");
id_ivar_fields = rb_intern("@fields");
id_ivar_last_insert_id = rb_intern("@last_insert_id");
Expand Down
32 changes: 17 additions & 15 deletions contrib/ruby/lib/trilogy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ module ConnectionError
include Error
end

# Trilogy may raise various syscall errors, which we treat as Trilogy::Errors.
class SyscallError
ERRORS = {}

ObjectSpace.each_object(::Class).select { |klass| klass < SystemCallError }.each do |klass|
ERRORS[klass::Errno] = Class.new(klass) { include Trilogy::Error }
end

ERRORS.freeze

class << self
def from_errno(message, errno)
ERRORS[errno].new(message)
end
end
end

class BaseError < StandardError
include Error

Expand Down Expand Up @@ -41,29 +58,14 @@ class CastError < ClientError

class TimeoutError < Errno::ETIMEDOUT
include ConnectionError

def initialize(error_message = nil, error_code = nil)
super
@error_code = error_code
end
end

class ConnectionRefusedError < Errno::ECONNREFUSED
include ConnectionError

def initialize(error_message = nil, error_code = nil)
super
@error_code = error_code
end
end

class ConnectionResetError < Errno::ECONNRESET
include ConnectionError

def initialize(error_message = nil, error_code = nil)
super
@error_code = error_code
end
end

# DatabaseError was replaced by ProtocolError, but we'll keep it around as an
Expand Down
8 changes: 8 additions & 0 deletions contrib/ruby/test/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,14 @@ def test_timeout_deadlines
end
end

def test_generic_syscall_error
err = assert_raises Trilogy::Error do
new_unix_client(File::NULL)
end

assert err.class < Errno::ENOTSOCK
end

def test_timeout_error
client_1 = new_tcp_client
client_2 = new_tcp_client
Expand Down

0 comments on commit b243b50

Please sign in to comment.