Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit ad49def

Browse files
lthHerman Lee
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 Differential Revision: D7298713
1 parent 725529e commit ad49def

24 files changed

Lines changed: 404 additions & 162 deletions

‎client/mysqltest.cc‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6628,6 +6628,7 @@ static void do_connect(struct st_command *command) {
66286628
char *con_options;
66296629
bool con_ssl = false, con_compress = false;
66306630
bool con_pipe = false, con_shm = false, con_cleartext_enable = false;
6631+
bool con_timeout_1s = false, con_timeout_1500ms = false;
66316632
struct st_connection *con_slot;
66326633
uint save_opt_ssl_mode = opt_ssl_mode;
66336634

@@ -6717,13 +6718,20 @@ static void do_connect(struct st_command *command) {
67176718
while (*end && !my_isspace(charset_info, *end)) end++;
67186719

67196720
size_t con_option_len = end - con_options;
6720-
char cur_con_option[10] = {};
6721-
strmake(cur_con_option, con_options, con_option_len);
6721+
const size_t cur_con_option_len = 32;
6722+
char cur_con_option[cur_con_option_len + 1] = {};
6723+
assert(cur_con_option_len >= con_option_len);
6724+
strmake(cur_con_option, con_options,
6725+
std::min(cur_con_option_len, con_option_len));
67226726

67236727
if (!std::strcmp(cur_con_option, "SSL"))
67246728
con_ssl = true;
67256729
else if (!std::strcmp(cur_con_option, "COMPRESS"))
67266730
con_compress = true;
6731+
else if (!std::strcmp(cur_con_option, "TIMEOUT_1S"))
6732+
con_timeout_1s = true;
6733+
else if (!std::strcmp(cur_con_option, "TIMEOUT_1500MS"))
6734+
con_timeout_1500ms = true;
67276735
else if (!std::strcmp(cur_con_option, "PIPE"))
67286736
con_pipe = true;
67296737
else if (!std::strcmp(cur_con_option, "SHM"))
@@ -6762,6 +6770,18 @@ static void do_connect(struct st_command *command) {
67626770

67636771
if (opt_compress || con_compress)
67646772
mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESS, NullS);
6773+
6774+
if (con_timeout_1s) {
6775+
int timeout = 1;
6776+
mysql_options(&con_slot->mysql, MYSQL_OPT_READ_TIMEOUT, &timeout);
6777+
mysql_options(&con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout);
6778+
mysql_options(&con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
6779+
} else if (con_timeout_1500ms) {
6780+
int timeout = 1500;
6781+
mysql_options(&con_slot->mysql, MYSQL_OPT_READ_TIMEOUT_MS, &timeout);
6782+
mysql_options(&con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT_MS, &timeout);
6783+
mysql_options(&con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT_MS, &timeout);
6784+
}
67656785
mysql_options(&con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
67666786
mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_NAME, charset_info->csname);
67676787
if (opt_charsets_dir)

‎include/mysql.h‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,10 @@ enum mysql_option {
214214
MYSQL_OPT_USER_PASSWORD,
215215
MYSQL_OPT_SSL_SESSION_DATA,
216216
MYSQL_OPT_SSL_CONTEXT,
217-
MYSQL_OPT_NET_RECEIVE_BUFFER_SIZE
217+
MYSQL_OPT_NET_RECEIVE_BUFFER_SIZE,
218+
MYSQL_OPT_CONNECT_TIMEOUT_MS,
219+
MYSQL_OPT_READ_TIMEOUT_MS,
220+
MYSQL_OPT_WRITE_TIMEOUT_MS,
218221
};
219222

220223
/**
@@ -224,7 +227,7 @@ enum mysql_option {
224227
struct st_mysql_options_extention;
225228

226229
struct st_mysql_options {
227-
unsigned int connect_timeout, read_timeout, write_timeout;
230+
timeout_t connect_timeout, read_timeout, write_timeout;
228231
unsigned int port, protocol;
229232
unsigned long client_flag;
230233
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
@@ -137,14 +137,18 @@
137137
SERVER_SESSION_STATE_CHANGED = (1UL << 14)
138138
};
139139
struct Vio;
140+
typedef struct {
141+
uint value_ms_;
142+
} timeout_t;
140143
typedef struct NET {
141144
struct Vio * vio;
142145
unsigned char *buff, *buff_end, *write_pos, *read_pos;
143146
my_socket fd;
144147
unsigned long remain_in_buf, length, buf_length, where_b;
145148
unsigned long max_packet, max_packet_size;
146149
unsigned int pkt_nr, compress_pkt_nr;
147-
unsigned int write_timeout, read_timeout, retry_count;
150+
timeout_t write_timeout, read_timeout;
151+
unsigned int retry_count;
148152
int fcntl;
149153
unsigned int *return_status;
150154
unsigned char reading_or_writing;
@@ -204,9 +208,16 @@
204208
bool net_write_packet(struct NET *net, const unsigned char *packet,
205209
size_t length);
206210
unsigned long my_net_read(struct NET *net);
207-
void my_net_set_write_timeout(struct NET *net, unsigned int timeout);
208-
void my_net_set_read_timeout(struct NET *net, unsigned int timeout);
211+
void my_net_set_write_timeout(struct NET *net, const timeout_t timeout);
212+
void my_net_set_read_timeout(struct NET *net, const timeout_t timeout);
209213
void my_net_set_retry_count(struct NET *net, unsigned int retry_count);
214+
timeout_t timeout_from_seconds(uint seconds);
215+
timeout_t timeout_from_millis(uint ms);
216+
timeout_t timeout_infinite(void);
217+
bool timeout_is_infinite(const timeout_t t);
218+
int timeout_is_nonzero(const timeout_t t);
219+
unsigned int timeout_to_millis(const timeout_t t);
220+
unsigned int timeout_to_seconds(const timeout_t t);
210221
struct rand_struct {
211222
unsigned long seed1, seed2, max_value;
212223
double max_value_dbl;
@@ -463,11 +474,14 @@
463474
MYSQL_OPT_USER_PASSWORD,
464475
MYSQL_OPT_SSL_SESSION_DATA,
465476
MYSQL_OPT_SSL_CONTEXT,
466-
MYSQL_OPT_NET_RECEIVE_BUFFER_SIZE
477+
MYSQL_OPT_NET_RECEIVE_BUFFER_SIZE,
478+
MYSQL_OPT_CONNECT_TIMEOUT_MS,
479+
MYSQL_OPT_READ_TIMEOUT_MS,
480+
MYSQL_OPT_WRITE_TIMEOUT_MS,
467481
};
468482
struct st_mysql_options_extention;
469483
struct st_mysql_options {
470-
unsigned int connect_timeout, read_timeout, write_timeout;
484+
timeout_t connect_timeout, read_timeout, write_timeout;
471485
unsigned int port, protocol;
472486
unsigned long client_flag;
473487
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
@@ -909,6 +909,17 @@ struct Vio;
909909
#define NET_ERROR_SOCKET_NOT_READABLE 3 /**< Try write and close socket */
910910
#define NET_ERROR_SOCKET_NOT_WRITABLE 4 /**< Try read and close socket */
911911

912+
/*
913+
In order to avoid confusion about whether a timeout value is in
914+
seconds or milliseconds, a timeout_t struct is used. It simply tracks
915+
milliseconds but this helps ensure type safety and clear intention
916+
when converting for use in syscalls etc.
917+
*/
918+
919+
typedef struct {
920+
uint value_ms_;
921+
} timeout_t;
922+
912923
typedef struct NET {
913924
MYSQL_VIO vio;
914925
unsigned char *buff, *buff_end, *write_pos, *read_pos;
@@ -921,7 +932,8 @@ typedef struct NET {
921932
unsigned long remain_in_buf, length, buf_length, where_b;
922933
unsigned long max_packet, max_packet_size;
923934
unsigned int pkt_nr, compress_pkt_nr;
924-
unsigned int write_timeout, read_timeout, retry_count;
935+
timeout_t write_timeout, read_timeout;
936+
unsigned int retry_count;
925937
int fcntl;
926938
unsigned int *return_status;
927939
unsigned char reading_or_writing;
@@ -1101,10 +1113,19 @@ bool net_write_command(struct NET *net, unsigned char command,
11011113
bool net_write_packet(struct NET *net, const unsigned char *packet,
11021114
size_t length);
11031115
unsigned long my_net_read(struct NET *net);
1104-
void my_net_set_write_timeout(struct NET *net, unsigned int timeout);
1105-
void my_net_set_read_timeout(struct NET *net, unsigned int timeout);
1116+
void my_net_set_write_timeout(struct NET *net, const timeout_t timeout);
1117+
void my_net_set_read_timeout(struct NET *net, const timeout_t timeout);
11061118
void my_net_set_retry_count(struct NET *net, unsigned int retry_count);
11071119

1120+
timeout_t timeout_from_seconds(uint seconds);
1121+
timeout_t timeout_from_millis(uint ms);
1122+
timeout_t timeout_infinite(void);
1123+
bool timeout_is_infinite(const timeout_t t);
1124+
int timeout_is_nonzero(const timeout_t t);
1125+
unsigned int timeout_to_millis(const timeout_t t);
1126+
// toSeconds rounds down.
1127+
unsigned int timeout_to_seconds(const timeout_t t);
1128+
11081129
struct rand_struct {
11091130
unsigned long seed1, seed2, max_value;
11101131
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/bits/my_io_bits.h"
4545
#include "mysql/components/services/bits/my_thread_bits.h"
4646
#include "mysql/components/services/bits/mysql_socket_bits.h"
47+
#include "mysql_com.h"
4748

4849
#include "mysql/psi/mysql_socket.h"
4950

@@ -134,9 +135,11 @@ enum enum_vio_io_event {
134135
VIO_IO_EVENT_CONNECT
135136
};
136137

137-
#define VIO_SOCKET_ERROR ((size_t)-1)
138-
#define VIO_SOCKET_WANT_READ ((size_t)-2)
139-
#define VIO_SOCKET_WANT_WRITE ((size_t)-3)
138+
#define VIO_SOCKET_ERROR ((ssize_t)-1)
139+
#define VIO_SOCKET_WANT_READ ((ssize_t)-2)
140+
#define VIO_SOCKET_WANT_WRITE ((ssize_t)-3)
141+
#define VIO_SOCKET_READ_TIMEOUT ((ssize_t)-4)
142+
#define VIO_SOCKET_WRITE_TIMEOUT ((ssize_t)-5)
140143

141144
#define VIO_LOCALHOST 1 /* a localhost connection */
142145
#define VIO_BUFFERED_READ 2 /* use buffered read */
@@ -190,16 +193,16 @@ my_socket vio_fd(MYSQL_VIO vio);
190193
/* Remote peer's address and name in text form */
191194
bool vio_peer_addr(MYSQL_VIO vio, char *buf, uint16 *port, size_t buflen);
192195
/* Wait for an I/O event notification. */
193-
int vio_io_wait(MYSQL_VIO vio, enum enum_vio_io_event event, int timeout);
196+
int vio_io_wait(MYSQL_VIO vio, enum enum_vio_io_event event, timeout_t timeout);
194197
bool vio_is_connected(MYSQL_VIO vio);
195198
#ifndef NDEBUG
196199
ssize_t vio_pending(MYSQL_VIO vio);
197200
#endif
198201
/* Set timeout for a network operation. */
199-
int vio_timeout(MYSQL_VIO vio, uint which, int timeout_sec);
202+
int vio_timeout(MYSQL_VIO vio, uint which, timeout_t timeout);
200203
/* Connect to a peer. */
201204
bool vio_socket_connect(MYSQL_VIO vio, struct sockaddr *addr, socklen_t len,
202-
bool nonblocking, int timeout,
205+
bool nonblocking, timeout_t timeout,
203206
bool *connect_done = nullptr);
204207

205208
bool vio_get_normalized_ip_string(const struct sockaddr *addr,
@@ -330,10 +333,10 @@ struct Vio {
330333
bool localhost = {false}; /* Are we from localhost? */
331334
enum_vio_type type = {NO_VIO_TYPE}; /* Type of connection */
332335

333-
int read_timeout = {-1}; /* Timeout value (ms) for read ops. */
334-
int write_timeout = {-1}; /* Timeout value (ms) for write ops. */
335-
int retry_count = {1}; /* Retry count */
336-
bool inactive = {false}; /* Connection has been shutdown */
336+
timeout_t read_timeout = {UINT_MAX}; /* Timeout value (ms) for read ops. */
337+
timeout_t write_timeout = {UINT_MAX}; /* Timeout value (ms) for write ops. */
338+
int retry_count = {1}; /* Retry count */
339+
bool inactive = {false}; /* Connection has been shutdown */
337340

338341
struct sockaddr_storage local; /* Local internet address */
339342
struct sockaddr_storage remote; /* Remote internet address */
@@ -410,7 +413,7 @@ struct Vio {
410413
int (*vioshutdown)(MYSQL_VIO) = {nullptr};
411414
bool (*is_connected)(MYSQL_VIO) = {nullptr};
412415
bool (*has_data)(MYSQL_VIO) = {nullptr};
413-
int (*io_wait)(MYSQL_VIO, enum enum_vio_io_event, int) = {nullptr};
416+
int (*io_wait)(MYSQL_VIO, enum enum_vio_io_event, timeout_t) = {nullptr};
414417
bool (*connect)(MYSQL_VIO, struct sockaddr *, socklen_t, int) = {nullptr};
415418
#ifdef _WIN32
416419
#ifdef __clang__

‎libmysql/libmysql.cc‎

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

960960
net->max_packet = (uint)local_net_buffer_length;
961-
my_net_set_read_timeout(net, CLIENT_NET_READ_TIMEOUT);
962-
my_net_set_write_timeout(net, CLIENT_NET_WRITE_TIMEOUT);
961+
my_net_set_read_timeout(net, timeout_from_seconds(CLIENT_NET_READ_TIMEOUT));
962+
my_net_set_write_timeout(net, timeout_from_seconds(CLIENT_NET_WRITE_TIMEOUT));
963963
my_net_set_retry_count(net, CLIENT_NET_RETRY_COUNT);
964964
net->max_packet_size =
965965
std::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:PORT' (NUM)

‎mysql-test/suite/auth_sec/r/chained_ssl_certificates.result‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ Restart server and provide same ssl-ca at server startup
1010
Variable_name Value
1111
Ssl_cipher SSL_CIPHER
1212
Restart server and provide same ssl-ca at server startup
13-
ERROR 2026 (HY000): SSL connection error: error:ERRCODE:SSL routines:ERRMSG:certificate verify failed
13+
ERROR 2026 (HY000): SSL connection error: error:ERRCODE:SSL routines:ERRMSG:certificate verify failed (errno 71)
1414
DROP USER 'user1';
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 2200 # 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 2200 # 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_regex /:[0-9]+/:PORT/ /\([0-9]+\)/(NUM)/
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/x/client/xconnection_impl.cc‎

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

127-
return vio_io_wait(m_vio, VIO_IO_EVENT_READ, 0) != 0;
127+
return vio_io_wait(m_vio, VIO_IO_EVENT_READ, timeout_from_seconds(0)) != 0;
128128
}
129129

130130
Vio *m_vio;
@@ -510,10 +510,10 @@ XError Connection_impl::connect(sockaddr *addr, const std::size_t addr_size) {
510510
return XError(CR_SOCKET_CREATE_ERROR, ER_TEXT_INVALID_SOCKET);
511511

512512
auto vio = vio_new(s, type, 0);
513-
auto error =
514-
vio_socket_connect(vio, addr, static_cast<socklen_t>(addr_size), false,
515-
details::make_vio_timeout(
516-
m_context->m_connection_config.m_timeout_connect));
513+
auto error = vio_socket_connect(
514+
vio, addr, static_cast<socklen_t>(addr_size), false,
515+
timeout_from_seconds(details::make_vio_timeout(
516+
m_context->m_connection_config.m_timeout_connect)));
517517

518518
if (error) {
519519
err = socket_errno;
@@ -798,7 +798,7 @@ XError Connection_impl::set_read_timeout(const int deadline_seconds) {
798798
ER_TEXT_CANT_SET_TIMEOUT_WHEN_NOT_CONNECTED, true};
799799
}
800800

801-
vio_timeout(m_vio, 0, deadline_seconds);
801+
vio_timeout(m_vio, 0, timeout_from_seconds(deadline_seconds));
802802
return {};
803803
}
804804

@@ -809,7 +809,7 @@ XError Connection_impl::set_write_timeout(const int deadline_seconds) {
809809
}
810810

811811
m_write_timeout = deadline_seconds;
812-
vio_timeout(m_vio, 1, deadline_seconds);
812+
vio_timeout(m_vio, 1, timeout_from_seconds(deadline_seconds));
813813

814814
return {};
815815
}

0 commit comments

Comments
 (0)