Skip to content

Commit 716eacf

Browse files
committed
Merge pull request #505 from dylanahsmith/fix-eintr-retry
Fix connect retry on interrupt and connection timeout
2 parents fba67a9 + 53591ca commit 716eacf

File tree

3 files changed

+37
-4
lines changed

3 files changed

+37
-4
lines changed

ext/mysql2/client.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <mysql2_ext.h>
22

3+
#include <time.h>
34
#include <errno.h>
45
#ifndef _WIN32
56
#include <sys/types.h>
@@ -255,6 +256,7 @@ static VALUE allocate(VALUE klass) {
255256
wrapper->active_thread = Qnil;
256257
wrapper->server_version = 0;
257258
wrapper->reconnect_enabled = 0;
259+
wrapper->connect_timeout = 0;
258260
wrapper->connected = 0; /* means that a database connection is open */
259261
wrapper->initialized = 0; /* means that that the wrapper is initialized */
260262
wrapper->refcount = 1;
@@ -326,6 +328,8 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
326328
struct nogvl_connect_args args;
327329
VALUE rv;
328330
GET_CLIENT(self);
331+
time_t start_time, end_time;
332+
unsigned int elapsed_time, connect_timeout;
329333

330334
args.host = NIL_P(host) ? NULL : StringValuePtr(host);
331335
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
@@ -336,12 +340,31 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
336340
args.mysql = wrapper->client;
337341
args.client_flag = NUM2ULONG(flags);
338342

343+
if (wrapper->connect_timeout)
344+
time(&start_time);
339345
rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
340346
if (rv == Qfalse) {
341-
while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) {
347+
while (rv == Qfalse && errno == EINTR) {
348+
if (wrapper->connect_timeout) {
349+
time(&end_time);
350+
/* avoid long connect timeout from system time changes */
351+
if (end_time < start_time)
352+
start_time = end_time;
353+
elapsed_time = end_time - start_time;
354+
/* avoid an early timeout due to time truncating milliseconds off the start time */
355+
if (elapsed_time > 0)
356+
elapsed_time--;
357+
if (elapsed_time >= wrapper->connect_timeout)
358+
break;
359+
connect_timeout = wrapper->connect_timeout - elapsed_time;
360+
mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
361+
}
342362
errno = 0;
343363
rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
344364
}
365+
/* restore the connect timeout for reconnecting */
366+
if (wrapper->connect_timeout)
367+
mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
345368
if (rv == Qfalse)
346369
return rb_raise_mysql2_error(wrapper);
347370
}
@@ -795,9 +818,15 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
795818
if (result != 0) {
796819
rb_warn("%s\n", mysql_error(wrapper->client));
797820
} else {
798-
/* Special case for reconnect, this option is also stored in the wrapper struct */
799-
if (opt == MYSQL_OPT_RECONNECT)
800-
wrapper->reconnect_enabled = boolval;
821+
/* Special case for options that are stored in the wrapper struct */
822+
switch (opt) {
823+
case MYSQL_OPT_RECONNECT:
824+
wrapper->reconnect_enabled = boolval;
825+
break;
826+
case MYSQL_OPT_CONNECT_TIMEOUT:
827+
wrapper->connect_timeout = intval;
828+
break;
829+
}
801830
}
802831

803832
return (result == 0) ? Qtrue : Qfalse;

ext/mysql2/client.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ typedef struct {
4141
VALUE active_thread; /* rb_thread_current() or Qnil */
4242
long server_version;
4343
int reconnect_enabled;
44+
int connect_timeout;
4445
int active;
4546
int connected;
4647
int initialized;

lib/mysql2/client.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ def initialize(opts = {})
2323

2424
initialize_ext
2525

26+
# Set default connect_timeout to avoid unlimited retries from signal interruption
27+
opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
28+
2629
[:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key|
2730
next unless opts.key?(key)
2831
case key

0 commit comments

Comments
 (0)