Skip to content

Commit 9f6c9f5

Browse files
committed
Call getaddrinfo from Ruby
`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.
1 parent e577853 commit 9f6c9f5

File tree

5 files changed

+28
-4
lines changed

5 files changed

+28
-4
lines changed

contrib/ruby/ext/trilogy-ruby/cext.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows,
2525
id_ivar_affected_rows, id_ivar_fields, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password,
2626
id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key,
2727
id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result,
28-
id_from_code, id_from_errno, id_connection_options, id_max_allowed_packet;
28+
id_from_code, id_from_errno, id_connection_options, id_max_allowed_packet, id_ip_address;
2929

3030
struct trilogy_ctx {
3131
trilogy_conn_t conn;
@@ -488,6 +488,11 @@ static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE
488488
Check_Type(val, T_FIXNUM);
489489
connopt.port = NUM2USHORT(val);
490490
}
491+
492+
if ((val = rb_hash_lookup(opts, ID2SYM(id_ip_address))) != Qnil) {
493+
Check_Type(val, T_STRING);
494+
connopt.ip_address = StringValueCStr(val);
495+
}
491496
} else {
492497
connopt.path = (char *)"/tmp/mysql.sock";
493498

@@ -1184,6 +1189,7 @@ RUBY_FUNC_EXPORTED void Init_cext()
11841189

11851190
id_socket = rb_intern("socket");
11861191
id_host = rb_intern("host");
1192+
id_ip_address = rb_intern("ip_address");
11871193
id_port = rb_intern("port");
11881194
id_username = rb_intern("username");
11891195
id_password = rb_intern("password");

contrib/ruby/lib/trilogy.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,23 @@
88

99
class Trilogy
1010
def initialize(options = {})
11+
options = options.dup
1112
options[:port] = options[:port].to_i if options[:port]
1213
mysql_encoding = options[:encoding] || "utf8mb4"
1314
encoding = Trilogy::Encoding.find(mysql_encoding)
1415
charset = Trilogy::Encoding.charset(mysql_encoding)
16+
17+
if options[:host]
18+
addrs = begin
19+
Socket.getaddrinfo(options[:host], options[:port], :PF_UNSPEC, :SOCK_STREAM)
20+
rescue SocketError => error
21+
raise Trilogy::BaseConnectionError, "Couldn't resolve host: #{options[:host].inspect}"
22+
end
23+
24+
addrs.sort_by!(&:first) # Priority to IPv4
25+
options[:ip_address] = addrs.first[3]
26+
end
27+
1528
@connection_options = options
1629
@connected_host = nil
1730

contrib/ruby/test/client_test.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def test_trilogy_connection_options
6868
ssl_mode: 4,
6969
tls_min_version: 3,
7070
}
71-
assert_equal expected_connection_options, client.connection_options
71+
actual_options = client.connection_options.dup
72+
actual_options.delete(:ip_address)
73+
assert_equal expected_connection_options, actual_options
7274
end
7375

7476
def test_trilogy_ping
@@ -953,7 +955,7 @@ def test_connection_invalid_dns
953955
ex = assert_raises Trilogy::ConnectionError do
954956
new_tcp_client(host: "mysql.invalid", port: 3306)
955957
end
956-
assert_equal "trilogy_connect - unable to connect to mysql.invalid:3306: TRILOGY_DNS_ERROR", ex.message
958+
assert_includes ex.message, %{Couldn't resolve host: "mysql.invalid"}
957959
end
958960

959961
def test_memsize

inc/trilogy/socket.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ typedef enum {
3636

3737
typedef struct {
3838
char *hostname;
39+
char *ip_address;
3940
char *path;
4041
char *database;
4142
char *username;

src/socket.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ static int _cb_raw_close(trilogy_sock_t *_sock)
105105
}
106106

107107
free(sock->base.opts.hostname);
108+
free(sock->base.opts.ip_address);
108109
free(sock->base.opts.path);
109110
free(sock->base.opts.database);
110111
free(sock->base.opts.username);
@@ -341,7 +342,8 @@ int trilogy_sock_resolve(trilogy_sock_t *_sock)
341342
char port[6];
342343
snprintf(port, sizeof(port), "%hu", sock->base.opts.port);
343344

344-
if (getaddrinfo(sock->base.opts.hostname, port, &hint, &sock->addr) != 0) {
345+
char *address = sock->base.opts.ip_address ? sock->base.opts.ip_address : sock->base.opts.hostname;
346+
if (getaddrinfo(address, port, &hint, &sock->addr) != 0) {
345347
return TRILOGY_DNS_ERR;
346348
}
347349
} else if (sock->base.opts.path != NULL) {

0 commit comments

Comments
 (0)