Skip to content

Commit 9e2db8e

Browse files
lthfacebook-github-bot
authored andcommitted
Support millisecond timeouts for reads, writes, and connects
Summary: WebScaleSQL Feature: Millisecond Client Timeouts This diff exposes three new client options: MYSQL_OPT_CONNECT_TIMEOUT_MS MYSQL_OPT_READ_TIMEOUT_MS MYSQL_OPT_WRITE_TIMEOUT_MS Which are similar to the non-_MS options, except the value is, of course, in milliseconds. This diff also changes a number of timeout-related codepaths to use a structure rather than a naked integer. This helps prevent many, many classes of errors that come from accidentally multiplying or dividing by 1000 to convert (or forgetting to), and creates a form of type safety for timeouts. Reference patch: b53fcf8 Reviewed By: abal147 Differential Revision: D7298713 fbshipit-source-id: 7719130
1 parent 767e089 commit 9e2db8e

24 files changed

+388
-150
lines changed

client/mysqltest.cc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6526,6 +6526,7 @@ static void do_connect(struct st_command *command) {
65266526
int con_port = opt_port;
65276527
char *con_options;
65286528
bool con_ssl = 0, con_compress = 0;
6529+
bool con_timeout_1s = 0, con_timeout_1500ms = 0;
65296530
bool con_pipe = 0, con_shm = 0, con_cleartext_enable = 0;
65306531
struct st_connection *con_slot;
65316532
#if defined(HAVE_OPENSSL)
@@ -6604,13 +6605,20 @@ static void do_connect(struct st_command *command) {
66046605
while (*end && !my_isspace(charset_info, *end)) end++;
66056606

66066607
size_t con_option_len = end - con_options;
6607-
char cur_con_option[10];
6608-
strmake(cur_con_option, con_options, con_option_len);
6608+
const size_t cur_con_option_len = 32;
6609+
char cur_con_option[cur_con_option_len + 1];
6610+
DBUG_ASSERT(cur_con_option_len >= con_option_len);
6611+
strmake(cur_con_option, con_options,
6612+
std::min(cur_con_option_len, con_option_len));
66096613

66106614
if (!std::strcmp(cur_con_option, "SSL"))
66116615
con_ssl = 1;
66126616
else if (!std::strcmp(cur_con_option, "COMPRESS"))
66136617
con_compress = 1;
6618+
else if (!std::strcmp(cur_con_option, "TIMEOUT_1S"))
6619+
con_timeout_1s = 1;
6620+
else if (!std::strcmp(cur_con_option, "TIMEOUT_1500MS"))
6621+
con_timeout_1500ms = 1;
66146622
else if (!std::strcmp(cur_con_option, "PIPE"))
66156623
con_pipe = 1;
66166624
else if (!std::strcmp(cur_con_option, "SHM"))
@@ -6647,6 +6655,18 @@ static void do_connect(struct st_command *command) {
66476655

66486656
if (opt_compress || con_compress)
66496657
mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESS, NullS);
6658+
6659+
if (con_timeout_1s) {
6660+
int timeout = 1;
6661+
mysql_options(&con_slot->mysql, MYSQL_OPT_READ_TIMEOUT, &timeout);
6662+
mysql_options(&con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout);
6663+
mysql_options(&con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
6664+
} else if (con_timeout_1500ms) {
6665+
int timeout = 1500;
6666+
mysql_options(&con_slot->mysql, MYSQL_OPT_READ_TIMEOUT_MS, &timeout);
6667+
mysql_options(&con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT_MS, &timeout);
6668+
mysql_options(&con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT_MS, &timeout);
6669+
}
66506670
mysql_options(&con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, 0);
66516671
mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_NAME, charset_info->csname);
66526672
if (opt_charsets_dir)

include/mysql.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,10 @@ enum mysql_option {
215215
MYSQL_OPT_SSL_SESSION,
216216
MYSQL_OPT_SSL_CONTEXT,
217217
MYSQL_OPT_NET_RECEIVE_BUFFER_SIZE,
218-
MYSQL_OPT_COMP_LIB
218+
MYSQL_OPT_COMP_LIB,
219+
MYSQL_OPT_CONNECT_TIMEOUT_MS,
220+
MYSQL_OPT_READ_TIMEOUT_MS,
221+
MYSQL_OPT_WRITE_TIMEOUT_MS,
219222
};
220223

221224
/**
@@ -225,7 +228,7 @@ enum mysql_option {
225228
struct st_mysql_options_extention;
226229

227230
struct st_mysql_options {
228-
unsigned int connect_timeout, read_timeout, write_timeout;
231+
timeout_t connect_timeout, read_timeout, write_timeout;
229232
unsigned int port, protocol;
230233
unsigned long client_flag;
231234
char *host, *user, *password, *unix_socket, *db;

include/mysql.h.pp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,18 @@
107107
enum mysql_compression_lib { MYSQL_COMPRESSION_ZLIB, MYSQL_COMPRESSION_ZSTD };
108108
typedef struct ZSTD_CCtx_s ZSTD_CCtx;
109109
typedef struct ZSTD_DCtx_s ZSTD_DCtx;
110+
typedef struct {
111+
uint value_ms_;
112+
} timeout_t;
110113
typedef struct NET {
111114
struct Vio * vio;
112115
unsigned char *buff, *buff_end, *write_pos, *read_pos;
113116
my_socket fd;
114117
unsigned long remain_in_buf, length, buf_length, where_b;
115118
unsigned long max_packet, max_packet_size;
116119
unsigned int pkt_nr, compress_pkt_nr;
117-
unsigned int write_timeout, read_timeout, retry_count;
120+
timeout_t write_timeout, read_timeout;
121+
unsigned int retry_count;
118122
int fcntl;
119123
unsigned int *return_status;
120124
unsigned char reading_or_writing;
@@ -176,9 +180,16 @@
176180
bool net_write_packet(struct NET *net, const unsigned char *packet,
177181
size_t length);
178182
unsigned long my_net_read(struct NET *net);
179-
void my_net_set_write_timeout(struct NET *net, unsigned int timeout);
180-
void my_net_set_read_timeout(struct NET *net, unsigned int timeout);
183+
void my_net_set_write_timeout(struct NET *net, const timeout_t timeout);
184+
void my_net_set_read_timeout(struct NET *net, const timeout_t timeout);
181185
void my_net_set_retry_count(struct NET *net, unsigned int retry_count);
186+
timeout_t timeout_from_seconds(uint seconds);
187+
timeout_t timeout_from_millis(uint ms);
188+
timeout_t timeout_infinite(void);
189+
bool timeout_is_infinite(const timeout_t t);
190+
int timeout_is_nonzero(const timeout_t t);
191+
unsigned int timeout_to_millis(const timeout_t t);
192+
unsigned int timeout_to_seconds(const timeout_t t);
182193
struct rand_struct {
183194
unsigned long seed1, seed2, max_value;
184195
double max_value_dbl;
@@ -425,11 +436,14 @@
425436
MYSQL_OPT_SSL_SESSION,
426437
MYSQL_OPT_SSL_CONTEXT,
427438
MYSQL_OPT_NET_RECEIVE_BUFFER_SIZE,
428-
MYSQL_OPT_COMP_LIB
439+
MYSQL_OPT_COMP_LIB,
440+
MYSQL_OPT_CONNECT_TIMEOUT_MS,
441+
MYSQL_OPT_READ_TIMEOUT_MS,
442+
MYSQL_OPT_WRITE_TIMEOUT_MS,
429443
};
430444
struct st_mysql_options_extention;
431445
struct st_mysql_options {
432-
unsigned int connect_timeout, read_timeout, write_timeout;
446+
timeout_t connect_timeout, read_timeout, write_timeout;
433447
unsigned int port, protocol;
434448
unsigned long client_flag;
435449
char *host, *user, *password, *unix_socket, *db;

include/mysql_com.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,17 @@ enum mysql_compression_lib { MYSQL_COMPRESSION_ZLIB, MYSQL_COMPRESSION_ZSTD };
824824
typedef struct ZSTD_CCtx_s ZSTD_CCtx;
825825
typedef struct ZSTD_DCtx_s ZSTD_DCtx;
826826

827+
/*
828+
In order to avoid confusion about whether a timeout value is in
829+
seconds or milliseconds, a timeout_t struct is used. It simply tracks
830+
milliseconds but this helps ensure type safety and clear intention
831+
when converting for use in syscalls etc.
832+
*/
833+
834+
typedef struct {
835+
uint value_ms_;
836+
} timeout_t;
837+
827838
typedef struct NET {
828839
MYSQL_VIO vio;
829840
unsigned char *buff, *buff_end, *write_pos, *read_pos;
@@ -836,7 +847,8 @@ typedef struct NET {
836847
unsigned long remain_in_buf, length, buf_length, where_b;
837848
unsigned long max_packet, max_packet_size;
838849
unsigned int pkt_nr, compress_pkt_nr;
839-
unsigned int write_timeout, read_timeout, retry_count;
850+
timeout_t write_timeout, read_timeout;
851+
unsigned int retry_count;
840852
int fcntl;
841853
unsigned int *return_status;
842854
unsigned char reading_or_writing;
@@ -1001,10 +1013,19 @@ bool net_write_command(struct NET *net, unsigned char command,
10011013
bool net_write_packet(struct NET *net, const unsigned char *packet,
10021014
size_t length);
10031015
unsigned long my_net_read(struct NET *net);
1004-
void my_net_set_write_timeout(struct NET *net, unsigned int timeout);
1005-
void my_net_set_read_timeout(struct NET *net, unsigned int timeout);
1016+
void my_net_set_write_timeout(struct NET *net, const timeout_t timeout);
1017+
void my_net_set_read_timeout(struct NET *net, const timeout_t timeout);
10061018
void my_net_set_retry_count(struct NET *net, unsigned int retry_count);
10071019

1020+
timeout_t timeout_from_seconds(uint seconds);
1021+
timeout_t timeout_from_millis(uint ms);
1022+
timeout_t timeout_infinite(void);
1023+
bool timeout_is_infinite(const timeout_t t);
1024+
int timeout_is_nonzero(const timeout_t t);
1025+
unsigned int timeout_to_millis(const timeout_t t);
1026+
// toSeconds rounds down.
1027+
unsigned int timeout_to_seconds(const timeout_t t);
1028+
10081029
struct rand_struct {
10091030
unsigned long seed1, seed2, max_value;
10101031
double max_value_dbl;

include/violite.h

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "mysql/components/services/my_io_bits.h"
4545
#include "mysql/components/services/my_thread_bits.h"
4646
#include "mysql/components/services/mysql_socket_bits.h"
47+
#include "mysql_com.h"
4748

4849
struct Vio;
4950

@@ -131,9 +132,11 @@ enum enum_vio_io_event {
131132
VIO_IO_EVENT_CONNECT
132133
};
133134

134-
#define VIO_SOCKET_ERROR ((size_t)-1)
135-
#define VIO_SOCKET_WANT_READ ((size_t)-2)
136-
#define VIO_SOCKET_WANT_WRITE ((size_t)-3)
135+
#define VIO_SOCKET_ERROR ((ssize_t)-1)
136+
#define VIO_SOCKET_WANT_READ ((ssize_t)-2)
137+
#define VIO_SOCKET_WANT_WRITE ((ssize_t)-3)
138+
#define VIO_SOCKET_READ_TIMEOUT ((ssize_t)-4)
139+
#define VIO_SOCKET_WRITE_TIMEOUT ((ssize_t)-5)
137140

138141
#define VIO_LOCALHOST 1 /* a localhost connection */
139142
#define VIO_BUFFERED_READ 2 /* use buffered read */
@@ -186,16 +189,16 @@ my_socket vio_fd(MYSQL_VIO vio);
186189
/* Remote peer's address and name in text form */
187190
bool vio_peer_addr(MYSQL_VIO vio, char *buf, uint16 *port, size_t buflen);
188191
/* Wait for an I/O event notification. */
189-
int vio_io_wait(MYSQL_VIO vio, enum enum_vio_io_event event, int timeout);
192+
int vio_io_wait(MYSQL_VIO vio, enum enum_vio_io_event event, timeout_t timeout);
190193
bool vio_is_connected(MYSQL_VIO vio);
191194
#ifndef DBUG_OFF
192195
ssize_t vio_pending(MYSQL_VIO vio);
193196
#endif
194197
/* Set timeout for a network operation. */
195-
int vio_timeout(MYSQL_VIO vio, uint which, int timeout_sec);
198+
int vio_timeout(MYSQL_VIO vio, uint which, timeout_t timeout);
196199
/* Connect to a peer. */
197200
bool vio_socket_connect(MYSQL_VIO vio, struct sockaddr *addr, socklen_t len,
198-
bool nonblocking, int timeout);
201+
bool nonblocking, timeout_t timeout);
199202

200203
bool vio_get_normalized_ip_string(const struct sockaddr *addr,
201204
size_t addr_length, char *ip_string,
@@ -345,10 +348,10 @@ struct Vio {
345348
bool localhost = {false}; /* Are we from localhost? */
346349
enum_vio_type type = {NO_VIO_TYPE}; /* Type of connection */
347350

348-
int read_timeout = {-1}; /* Timeout value (ms) for read ops. */
349-
int write_timeout = {-1}; /* Timeout value (ms) for write ops. */
350-
int retry_count = {1}; /* Retry count */
351-
bool inactive = {false}; /* Connection has been shutdown */
351+
timeout_t read_timeout = {UINT_MAX}; /* Timeout value (ms) for read ops. */
352+
timeout_t write_timeout = {UINT_MAX}; /* Timeout value (ms) for write ops. */
353+
int retry_count = {1}; /* Retry count */
354+
bool inactive = {false}; /* Connection has been shutdown */
352355

353356
struct sockaddr_storage local; /* Local internet address */
354357
struct sockaddr_storage remote; /* Remote internet address */
@@ -415,7 +418,7 @@ struct Vio {
415418
int (*vioshutdown)(MYSQL_VIO) = {nullptr};
416419
bool (*is_connected)(MYSQL_VIO) = {nullptr};
417420
bool (*has_data)(MYSQL_VIO) = {nullptr};
418-
int (*io_wait)(MYSQL_VIO, enum enum_vio_io_event, int) = {nullptr};
421+
int (*io_wait)(MYSQL_VIO, enum enum_vio_io_event, timeout_t) = {nullptr};
419422
bool (*connect)(MYSQL_VIO, struct sockaddr *, socklen_t, int) = {nullptr};
420423
#ifdef _WIN32
421424
#ifdef __clang__

libmysql/libmysql.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,8 @@ void my_net_local_init(NET *net) {
946946
&local_net_buffer_length);
947947

948948
net->max_packet = (uint)local_net_buffer_length;
949-
my_net_set_read_timeout(net, CLIENT_NET_READ_TIMEOUT);
950-
my_net_set_write_timeout(net, CLIENT_NET_WRITE_TIMEOUT);
949+
my_net_set_read_timeout(net, timeout_from_seconds(CLIENT_NET_READ_TIMEOUT));
950+
my_net_set_write_timeout(net, timeout_from_seconds(CLIENT_NET_WRITE_TIMEOUT));
951951
my_net_set_retry_count(net, CLIENT_NET_RETRY_COUNT);
952952
net->max_packet_size =
953953
MY_MAX(local_net_buffer_length, local_max_allowed_packet);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
select "no timeout, should work";
2+
no timeout, should work
3+
no timeout, should work
4+
select "short timeout, should work", sleep(0.70);
5+
short timeout, should work sleep(0.70)
6+
short timeout, should work 0
7+
select "long timeout, should fail 1s accuracy", sleep(2);
8+
ERROR HY000: Read timeout is reached
9+
select "no timeout, should work";
10+
no timeout, should work
11+
no timeout, should work
12+
select "short timeout, should work", sleep(0.5);
13+
short timeout, should work sleep(0.5)
14+
short timeout, should work 0
15+
select "short timeout, over one second, should also work", sleep(1.1);
16+
short timeout, over one second, should also work sleep(1.1)
17+
short timeout, over one second, should also work 0
18+
select "long timeout, should 1500ms accuracy", sleep(3);
19+
ERROR HY000: Read timeout is reached
20+
ERROR HY000: Can't connect to MySQL server on '192.0.2.1' (101/110)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Two test cases, one for each type of timeout. First the integer timeout
2+
# (which uses MYSQL_OPT_READ_TIMEOUT).
3+
--source include/count_sessions.inc
4+
--disable_async_client
5+
connect (con1,127.0.0.1,root,,,,,TIMEOUT_1S);
6+
connection con1;
7+
select "no timeout, should work";
8+
select "short timeout, should work", sleep(0.70);
9+
--error 2066 # CR_NET_READ_INTERRUPTED
10+
select "long timeout, should fail 1s accuracy", sleep(2);
11+
12+
# Now confirm MYSQL_OPT_READ_TIMEOUT_MS works, too.
13+
connect (con2,127.0.0.1,root,,,,,TIMEOUT_1500MS);
14+
connection con2;
15+
select "no timeout, should work";
16+
select "short timeout, should work", sleep(0.5);
17+
select "short timeout, over one second, should also work", sleep(1.1);
18+
--error 2066 # CR_NET_READ_INTERRUPTED
19+
select "long timeout, should 1500ms accuracy", sleep(3);
20+
21+
# 192.0.2.1 is in the TEST-NET range, defined by RFC to never be
22+
# reachable. Used to confirm connect timeouts.
23+
--disable_abort_on_error
24+
--replace_result (101) (101/110) (110) (101/110)
25+
connect (con3,192.0.2.1,root,,,,,TIMEOUT_1500MS);
26+
27+
--disconnect con1
28+
--disconnect con2
29+
connection default;
30+
--source include/wait_until_count_sessions.inc

plugin/semisync/semisync_master_ack_receiver.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ bool Ack_receiver::add_slave(THD *thd) {
142142
slave.is_leaving = false;
143143
slave.vio = thd->get_protocol_classic()->get_vio();
144144
slave.vio->mysql_socket.m_psi = NULL;
145-
slave.vio->read_timeout = 1;
145+
slave.vio->read_timeout = timeout_from_seconds(1);
146146

147147
/* push_back() may throw an exception */
148148
try {

plugin/x/client/xconnection_impl.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class Connection_state : public XConnection::State {
132132
bool res = m_vio->has_data(m_vio);
133133
if (res) return true;
134134

135-
return vio_io_wait(m_vio, VIO_IO_EVENT_READ, 0) != 0;
135+
return vio_io_wait(m_vio, VIO_IO_EVENT_READ, timeout_from_seconds(0)) != 0;
136136
}
137137

138138
Vio *m_vio;
@@ -408,10 +408,10 @@ XError Connection_impl::connect(sockaddr *addr, const std::size_t addr_size) {
408408
return XError(CR_SOCKET_CREATE_ERROR, ER_TEXT_INVALID_SOCKET);
409409

410410
auto vio = vio_new(s, type, 0);
411-
auto error =
412-
vio_socket_connect(vio, addr, static_cast<socklen_t>(addr_size), false,
413-
details::make_vio_timeout(
414-
m_context->m_connection_config.m_timeout_connect));
411+
auto error = vio_socket_connect(
412+
vio, addr, static_cast<socklen_t>(addr_size), false,
413+
timeout_from_seconds(details::make_vio_timeout(
414+
m_context->m_connection_config.m_timeout_connect)));
415415

416416
if (error) {
417417
err = socket_errno;
@@ -694,7 +694,7 @@ XError Connection_impl::set_read_timeout(const int deadline_seconds) {
694694
ER_TEXT_CANT_SET_TIMEOUT_WHEN_NOT_CONNECTED};
695695
}
696696

697-
vio_timeout(m_vio, 0, deadline_seconds);
697+
vio_timeout(m_vio, 0, timeout_from_seconds(deadline_seconds));
698698
return {};
699699
}
700700

@@ -704,7 +704,7 @@ XError Connection_impl::set_write_timeout(const int deadline_seconds) {
704704
ER_TEXT_CANT_SET_TIMEOUT_WHEN_NOT_CONNECTED};
705705
}
706706

707-
vio_timeout(m_vio, 1, deadline_seconds);
707+
vio_timeout(m_vio, 1, timeout_from_seconds(deadline_seconds));
708708
return {};
709709
}
710710

0 commit comments

Comments
 (0)