Skip to content

Commit

Permalink
Call getaddrinfo from Ruby
Browse files Browse the repository at this point in the history
`getaddrinfo` is notorious for not having a timeout, which can make
it hang for a very long time. Since Trilogy releases the GVL before
calling it, that can cause the whole VM to be unresponsive and no
longer respond to ctrl+c etc.

In 3.3.0, Ruby is now doing name resolution from a background thread,
making `Socket.getaddrinfo` interruptible. So by doing the name resolution
on the Ruby side, we should avoid such scenarios.
  • Loading branch information
byroot committed Dec 13, 2023
1 parent e577853 commit 34e9763
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 3 deletions.
8 changes: 7 additions & 1 deletion contrib/ruby/ext/trilogy-ruby/cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows,
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_from_errno, id_connection_options, id_max_allowed_packet;
id_from_code, id_from_errno, id_connection_options, id_max_allowed_packet, id_ip_address;

struct trilogy_ctx {
trilogy_conn_t conn;
Expand Down Expand Up @@ -488,6 +488,11 @@ static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE
Check_Type(val, T_FIXNUM);
connopt.port = NUM2USHORT(val);
}

if ((val = rb_hash_lookup(opts, ID2SYM(id_ip_address))) != Qnil) {
Check_Type(val, T_STRING);
connopt.ip_address = StringValueCStr(val);
}
} else {
connopt.path = (char *)"/tmp/mysql.sock";

Expand Down Expand Up @@ -1184,6 +1189,7 @@ RUBY_FUNC_EXPORTED void Init_cext()

id_socket = rb_intern("socket");
id_host = rb_intern("host");
id_ip_address = rb_intern("ip_address");
id_port = rb_intern("port");
id_username = rb_intern("username");
id_password = rb_intern("password");
Expand Down
13 changes: 13 additions & 0 deletions contrib/ruby/lib/trilogy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,23 @@

class Trilogy
def initialize(options = {})
options = options.dup
options[:port] = options[:port].to_i if options[:port]
mysql_encoding = options[:encoding] || "utf8mb4"
encoding = Trilogy::Encoding.find(mysql_encoding)
charset = Trilogy::Encoding.charset(mysql_encoding)

if options[:host]
addrs = begin
Socket.getaddrinfo(options[:host], options[:port], :PF_UNSPEC, :SOCK_STREAM)
rescue SocketError => error
raise Trilogy::BaseConnectionError, "Couldn't resolve host: #{options[:host].inspect}"
end

addrs.sort_by!(&:first) # Priority to IPv4
options[:ip_address] = addrs.first[3]
end

@connection_options = options
@connected_host = nil

Expand Down
2 changes: 1 addition & 1 deletion contrib/ruby/test/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ def test_connection_invalid_dns
ex = assert_raises Trilogy::ConnectionError do
new_tcp_client(host: "mysql.invalid", port: 3306)
end
assert_equal "trilogy_connect - unable to connect to mysql.invalid:3306: TRILOGY_DNS_ERROR", ex.message
assert_includes ex.message, %{Couldn't resolve host: "mysql.invalid"}
end

def test_memsize
Expand Down
1 change: 1 addition & 0 deletions inc/trilogy/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ typedef enum {

typedef struct {
char *hostname;
char *ip_address;
char *path;
char *database;
char *username;
Expand Down
3 changes: 2 additions & 1 deletion src/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ int trilogy_sock_resolve(trilogy_sock_t *_sock)
char port[6];
snprintf(port, sizeof(port), "%hu", sock->base.opts.port);

if (getaddrinfo(sock->base.opts.hostname, port, &hint, &sock->addr) != 0) {
char *address = sock->base.opts.ip_address ? sock->base.opts.ip_address : sock->base.opts.hostname;
if (getaddrinfo(address, port, &hint, &sock->addr) != 0) {
return TRILOGY_DNS_ERR;
}
} else if (sock->base.opts.path != NULL) {
Expand Down

0 comments on commit 34e9763

Please sign in to comment.