From 14508bbb1790697c28659dd051fbc855cd3b5da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20=C3=84lvring?= Date: Thu, 19 Nov 2020 09:51:53 +0100 Subject: [PATCH] WL#12999 Make the client get a better error message on wait_timeout When the server terminates a client connection because of timeout, it tries to send an error message to the client. This message is later caught by the client on a best effort as the client discover that the write socket is terminated. Patch contributed by Mattias Jonsson @ booking. RB: 25049 --- client/mysql.cc | 25 ++++-- include/mysql_com.h | 6 ++ .../include/wait_until_connected_again.inc | 2 +- .../include/wait_until_disconnected.inc | 2 +- mysql-test/r/mysqltest.result | 2 +- mysql-test/r/ssl.result | 2 +- mysql-test/r/wait_timeout.result | 22 ++++- mysql-test/t/mysqltest.test | 2 +- mysql-test/t/ssl.test | 4 +- mysql-test/t/wait_timeout.test | 49 +++++++++-- share/messages_to_clients.txt | 3 + sql-common/client.cc | 71 +++++++++++----- sql-common/net_serv.cc | 82 ++++++++++++++----- sql/protocol_classic.cc | 4 +- sql/sql_parse.cc | 6 +- 15 files changed, 214 insertions(+), 68 deletions(-) diff --git a/client/mysql.cc b/client/mysql.cc index 38bc1c321620..d165c68d2c07 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -3042,11 +3042,19 @@ static int reconnect(void) { return 0; } -static void get_current_db() { +/** + Checks the current DB and updates the global variable current_db + If the command fails hen he current_db is set to nullptr. + + @return Error state + @retval true An error occurred + @retval false Success; current_db is updated +*/ +static bool get_current_db() { MYSQL_RES *res; /* If one_database is set, current_db is not supposed to change. */ - if (one_database) return; + if (one_database) return false; my_free(current_db); current_db = nullptr; @@ -3057,7 +3065,11 @@ static void get_current_db() { if (row && row[0]) current_db = my_strdup(PSI_NOT_INSTRUMENTED, row[0], MYF(MY_WME)); mysql_free_result(res); + } else { + /* We failed to issue the command and we likely lost connection */ + return true; } + return false; } /*************************************************************************** @@ -3073,8 +3085,10 @@ static int mysql_real_query_for_lazy(const char *buf, size_t length, if (set_params && global_attrs->set_params(&mysql)) break; if (!mysql_real_query(&mysql, buf, (ulong)length)) break; error = put_error(&mysql); - if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1 || - !opt_reconnect) + if ((mysql_errno(&mysql) != CR_SERVER_GONE_ERROR && + mysql_errno(&mysql) != CR_SERVER_LOST && + mysql.net.error != NET_ERROR_SOCKET_UNUSABLE) || + retry > 1 || !opt_reconnect) break; if (reconnect()) break; } @@ -4270,8 +4284,9 @@ static int com_use(String *buffer MY_ATTRIBUTE((unused)), char *line) { We need to recheck the current database, because it may change under our feet, for example if DROP DATABASE or RENAME DATABASE (latter one not yet available by the time the comment was written) + If this command fails we assume we lost connection. */ - get_current_db(); + if (get_current_db()) connected = false; if (!current_db || cmp_database(charset_info, current_db, tmp)) { if (one_database) { diff --git a/include/mysql_com.h b/include/mysql_com.h index d0c37a599b3c..5ccb097cc027 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -876,6 +876,12 @@ struct Vio; /// Default width for blob in bytes @todo - align this with sizes from field.h #define MAX_BLOB_WIDTH 16777216 +#define NET_ERROR_UNSET 0 /**< No error has occurred yet */ +#define NET_ERROR_SOCKET_RECOVERABLE 1 /**< Socket still usable */ +#define NET_ERROR_SOCKET_UNUSABLE 2 /**< Do not use the socket */ +#define NET_ERROR_SOCKET_NOT_READABLE 3 /**< Try write and close socket */ +#define NET_ERROR_SOCKET_NOT_WRITABLE 4 /**< Try read and close socket */ + typedef struct NET { MYSQL_VIO vio; unsigned char *buff, *buff_end, *write_pos, *read_pos; diff --git a/mysql-test/include/wait_until_connected_again.inc b/mysql-test/include/wait_until_connected_again.inc index 7ee97f680450..0e3441bec2e7 100644 --- a/mysql-test/include/wait_until_connected_again.inc +++ b/mysql-test/include/wait_until_connected_again.inc @@ -40,7 +40,7 @@ while ($mysql_errno) # Strangely enough, the server might return "Too many connections" # while being shutdown, thus 1040 is an "allowed" error # See BUG#36228 - --error 0,1040,1053,2002,2003,2006,2013,1045,ER_SECURE_TRANSPORT_REQUIRED,2016,2017 + --error 0,1040,1053,2002,2003,2006,2013,1045,ER_SECURE_TRANSPORT_REQUIRED,2016,2017,ER_CLIENT_INTERACTION_TIMEOUT show session status; if ($mysql_errno == 1045){ --let mysql_errno=0 diff --git a/mysql-test/include/wait_until_disconnected.inc b/mysql-test/include/wait_until_disconnected.inc index c82b677525eb..575216028ee4 100644 --- a/mysql-test/include/wait_until_disconnected.inc +++ b/mysql-test/include/wait_until_disconnected.inc @@ -11,7 +11,7 @@ if ($disconnect_timeout) let $mysql_errno= 0; while (!$mysql_errno) { - --error 0,1040,1053,2002,2003,2006,2013,2016,2017 + --error 0,1040,1053,2002,2003,2006,2013,2016,2017,ER_CLIENT_INTERACTION_TIMEOUT show session status; dec $counter; diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index ebd7bdb587b9..944eb59435af 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -1315,7 +1315,7 @@ SELECT 1 AS res; res 1 SELECT 1 AS res; -ERROR HY000: MySQL server has gone away +Got one of the listed errors # # Bug#23743035: ADD A COPY_FILES_WILDCARD COMMAND # diff --git a/mysql-test/r/ssl.result b/mysql-test/r/ssl.result index f7158e273262..6bcc19924957 100644 --- a/mysql-test/r/ssl.result +++ b/mysql-test/r/ssl.result @@ -2275,6 +2275,6 @@ Ssl_cipher SSL_CIPHER SET @@SESSION.wait_timeout = 2; # Wait for ssl_con to be disconnected. # Check that ssl_con has been disconnected. -# CR_SERVER_LOST, CR_SERVER_GONE_ERROR +# Different behavior depending on how the plattform implements the SELECT 1; Got one of the listed errors diff --git a/mysql-test/r/wait_timeout.result b/mysql-test/r/wait_timeout.result index fe73ef0fb21f..0f46d0373437 100644 --- a/mysql-test/r/wait_timeout.result +++ b/mysql-test/r/wait_timeout.result @@ -42,7 +42,6 @@ disconnection con1; SET @@SESSION.wait_timeout = 2; # Wait for con1 to be disconnected. # Check that con1 has been disconnected. -# CR_SERVER_LOST, CR_SERVER_GONE_ERROR SELECT 1; Got one of the listed errors # @@ -52,8 +51,27 @@ Got one of the listed errors SET @@SESSION.wait_timeout = 2; # Wait for con1 to be disconnected. # Check that con1 has been disconnected. -# CR_SERVER_LOST, CR_SERVER_GONE_ERROR +# Client interaction timeout SELECT 1; Got one of the listed errors +SELECT "Check that we don't reconnect with reconnection disabled."; +Got one of the listed errors +# +# Test UNIX domain sockets timeout with reconnect. +# +# Open con2 and set a timeout. +--enable_reconnect; +SET @is_old_connection = 1; +SELECT @is_old_connection; +@is_old_connection +1 +SET @@SESSION.wait_timeout = 2; +# Wait for con2 to be disconnected. +# Check that con2 has been reconnected. +SELECT "Unix domain socket will hit wait_timeout with reconnect"; +Got one of the listed errors +SELECT @is_old_connection; +@is_old_connection +NULL # must find the pattern Pattern "The wait_timeout period was exceeded, the idle time since last command was too long." found diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index d826772527e1..d9ef7f03256a 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -3287,7 +3287,7 @@ SELECT 1 AS res; disconnect con1; --source include/wait_until_disconnected.inc ---error CR_SERVER_GONE_ERROR +--error CR_SERVER_GONE_ERROR,CR_SERVER_LOST SELECT 1 AS res; connection default; diff --git a/mysql-test/t/ssl.test b/mysql-test/t/ssl.test index fc9efc226efd..e8d9cf83780f 100644 --- a/mysql-test/t/ssl.test +++ b/mysql-test/t/ssl.test @@ -48,8 +48,8 @@ let $wait_condition= --echo # Check that ssl_con has been disconnected. connection ssl_con; ---echo # CR_SERVER_LOST, CR_SERVER_GONE_ERROR ---error 2006,2013 +--echo # Different behavior depending on how the plattform implements the +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST SELECT 1; connection default; diff --git a/mysql-test/t/wait_timeout.test b/mysql-test/t/wait_timeout.test index 40052bd84238..43018aceeb65 100644 --- a/mysql-test/t/wait_timeout.test +++ b/mysql-test/t/wait_timeout.test @@ -66,7 +66,7 @@ connection default; # When the connection is closed in this way, the error code should # be consistent see Bug#2845 for an explanation # depending on platform/client, either errno 2006 or 2013 can occur below ---error 2006,2013 +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST SELECT 2; --echo --enable_reconnect; --enable_reconnect @@ -123,7 +123,7 @@ connection con1; # When the connection is closed in this way, the error code should # be consistent see Bug#2845 for an explanation # depending on platform/client, either errno 2006 or 2013 can occur below ---error 2006,2013 +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST SELECT 2; --echo --enable_reconnect; --enable_reconnect @@ -148,6 +148,7 @@ connect (default,localhost,root,,test,,); --echo # Open con1 and set a timeout. connect(con1,localhost,root,,); +--disable_reconnect LET $ID= `SELECT connection_id()`; SET @@SESSION.wait_timeout = 2; @@ -160,8 +161,7 @@ let $wait_condition= --echo # Check that con1 has been disconnected. connection con1; ---echo # CR_SERVER_LOST, CR_SERVER_GONE_ERROR ---error 2006,2013 +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST SELECT 1; disconnect con1; @@ -172,7 +172,7 @@ connection default; --echo # --echo # Open con1 and set a timeout. -connect(con1,127.0.0.1,root,,); +connect(con1,127.0.0.1,root,,,,,TCP,,); LET $ID= `SELECT connection_id()`; SET @@SESSION.wait_timeout = 2; @@ -186,16 +186,47 @@ let $wait_condition= --echo # Check that con1 has been disconnected. connection con1; ---echo # CR_SERVER_LOST, CR_SERVER_GONE_ERROR ---error 2006,2013 +--disable_reconnect +--echo # Client interaction timeout +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST SELECT 1; -disconnect con1; +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST +SELECT "Check that we don't reconnect with reconnection disabled."; + +--echo # +--echo # Test UNIX domain sockets timeout with reconnect. +--echo # + +--echo # Open con2 and set a timeout. +connect(con2,localhost,root,,); + +--echo --enable_reconnect; +--enable_reconnect +SET @is_old_connection = 1; +SELECT @is_old_connection; + +LET $ID= `SELECT connection_id()`; +SET @@SESSION.wait_timeout = 2; + +--echo # Wait for con2 to be disconnected. connection default; +let $wait_condition= + SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE ID = $ID; +--source include/wait_condition.inc +--echo # Check that con2 has been reconnected. +connection con2; +--error ER_CLIENT_INTERACTION_TIMEOUT,CR_SERVER_LOST +SELECT "Unix domain socket will hit wait_timeout with reconnect"; +SELECT @is_old_connection; +connection default; +--disable_reconnect +disconnect con2; +disconnect con1; # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc - # # Bug #28940167 WAIT_TIMEOUT ERROR NOT CLEAR AND NOT SENT TO CLIENT BEFORE CLOSING CONNECTION # diff --git a/share/messages_to_clients.txt b/share/messages_to_clients.txt index cd4d375eff32..966cf631633b 100644 --- a/share/messages_to_clients.txt +++ b/share/messages_to_clients.txt @@ -9502,6 +9502,9 @@ ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN ER_WARN_IMPORT_COMPRESSION_FAIL eng "Compression failed while doing import with the following error : %s" +ER_CLIENT_INTERACTION_TIMEOUT + eng "The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior." + # # End of 8.0 error messages (server-to-client). # Do NOT add messages intended for the error log above! diff --git a/sql-common/client.cc b/sql-common/client.cc index 50047457472b..fff03bffb50a 100644 --- a/sql-common/client.cc +++ b/sql-common/client.cc @@ -43,13 +43,12 @@ server. */ -#include "my_config.h" - #include #include #include "m_ctype.h" #include "m_string.h" +#include "my_config.h" #include "my_sys.h" #include "mysys_err.h" #ifndef _WIN32 @@ -59,9 +58,9 @@ #include #endif #include -#include #include +#include #include "client_async_authentication.h" #include "compression.h" // validate_compression_attributes @@ -115,9 +114,9 @@ #define SOCKET_ERROR -1 #endif +#include #include -#include #include #include "../libmysql/init_commands_array.h" @@ -1285,9 +1284,14 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, bool stmt_skip = stmt ? stmt->state != MYSQL_STMT_INIT_DONE : false; DBUG_TRACE; - if (mysql->net.vio == nullptr) { /* Do reconnect if possible */ - if (mysql_reconnect(mysql) || stmt_skip) return true; + if (mysql->net.vio == nullptr || net->error == NET_ERROR_SOCKET_UNUSABLE) { + /* Do reconnect if possible */ + if (!mysql->reconnect) return true; + if (mysql_reconnect(mysql) || stmt_skip) return true; // reconnect failed + /* reconnect succeeded */ + DBUG_ASSERT(mysql->net.vio != nullptr); } + /* turn off non blocking operations */ if (!vio_is_blocking(mysql->net.vio)) vio_set_blocking_flag(mysql->net.vio, true); @@ -1303,12 +1307,13 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, mysql->info = nullptr; mysql->affected_rows = ~(my_ulonglong)0; /* - Do not check the socket/protocol buffer on COM_QUIT as the - result of a previous command might not have been read. This - can happen if a client sends a query but does not reap the - result before attempting to close the connection. + Do not check the socket/protocol buffer as the + result/error/timeout of a previous command might not have been read. + This can happen if a client sends a query but does not reap the result + before attempting to close the connection or wait_timeout occurs on + the server. */ - net_clear(&mysql->net, (command != COM_QUIT)); + net_clear(&mysql->net, 0); MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); MYSQL_TRACE(SEND_COMMAND, mysql, @@ -1324,7 +1329,7 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, server. But this should be rare. */ if ((command != COM_QUIT) && mysql->reconnect && !vio_is_connected(net->vio)) - net->error = 2; + net->error = NET_ERROR_SOCKET_UNUSABLE; if (net_write_command(net, (uchar)command, header, header_length, arg, arg_length)) { @@ -1334,6 +1339,24 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, set_mysql_error(mysql, CR_NET_PACKET_TOO_LARGE, unknown_sqlstate); goto end; } + if (net->last_errno == ER_NET_ERROR_ON_WRITE) { + /* + Write error, try to read and see if the server gave an error + before closing the connection. Most likely Unix Domain Socket. + */ + if (net->vio) { + my_net_set_read_timeout(net, 1); + /* + cli_safe_read will also set error variables in net, + and we are already in error state. + */ + if (cli_safe_read(mysql, NULL) == packet_error) { + goto end; + } + /* Can this happen in any other case than COM_QUIT? */ + DBUG_ASSERT(command == COM_QUIT); + } + } end_server(mysql); if (mysql_reconnect(mysql) || stmt_skip) goto end; @@ -1476,7 +1499,7 @@ net_async_status cli_advanced_command_nonblocking( the result before attempting to close the connection. */ DBUG_ASSERT(command <= COM_END); - net_clear(&mysql->net, (command != COM_QUIT)); + net_clear(&mysql->net, 0); net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_WRITE_COMMAND; } @@ -6562,11 +6585,11 @@ bool mysql_reconnect(MYSQL *mysql) { DBUG_ASSERT(mysql); DBUG_PRINT("enter", ("mysql->reconnect: %d", mysql->reconnect)); - if (!mysql->reconnect || (mysql->server_status & SERVER_STATUS_IN_TRANS) || - !mysql->host_info) { + if ((mysql->server_status & SERVER_STATUS_IN_TRANS) || !mysql->host_info) { /* Allow reconnect next time */ mysql->server_status &= ~SERVER_STATUS_IN_TRANS; - set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + if (mysql->net.last_errno == 0) + set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); return true; } mysql_init(&tmp_mysql); @@ -6585,7 +6608,6 @@ bool mysql_reconnect(MYSQL *mysql) { MYSQL_EXTENSION_PTR(mysql)->server_extn = server_extn; #endif memset(&tmp_mysql.options, 0, sizeof(tmp_mysql.options)); - mysql_close(&tmp_mysql); mysql->net.last_errno = tmp_mysql.net.last_errno; my_stpcpy(mysql->net.last_error, tmp_mysql.net.last_error); my_stpcpy(mysql->net.sqlstate, tmp_mysql.net.sqlstate); @@ -6989,9 +7011,13 @@ void STDCALL mysql_close(MYSQL *mysql) { if (mysql) /* Some simple safety */ { /* If connection is still up, send a QUIT message */ - if (mysql->net.vio != nullptr) { + if (mysql->net.vio != nullptr && + mysql->net.last_errno != NET_ERROR_SOCKET_UNUSABLE && + mysql->net.last_errno != NET_ERROR_SOCKET_NOT_WRITABLE) { free_old_query(mysql); mysql->status = MYSQL_STATUS_READY; /* Force command */ + bool old_reconnect = mysql->reconnect; + mysql->reconnect = false; // avoid recursion if (vio_is_blocking(mysql->net.vio)) { simple_command(mysql, COM_QUIT, (uchar *)nullptr, 0, 1); } else { @@ -7003,8 +7029,7 @@ void STDCALL mysql_close(MYSQL *mysql) { simple_command_nonblocking(mysql, COM_QUIT, (uchar *)nullptr, 0, 1, &err); } - - mysql->reconnect = false; + mysql->reconnect = old_reconnect; end_server(mysql); /* Sets mysql->net.vio= 0 */ } mysql_close_free(mysql); @@ -7225,6 +7250,12 @@ static int mysql_prepare_com_query_parameters(MYSQL *mysql, } if (mysql->net.vio == nullptr) { /* Do reconnect if possible */ + if (!mysql->reconnect) { + /* If we don't have any vio there must be an error */ + if (mysql->net.last_errno == 0) + set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); + return 1; + } if (mysql_reconnect(mysql)) return 1; /* mysql has a new ext at this point, take it again */ ext = MYSQL_EXTENSION_PTR(mysql); diff --git a/sql-common/net_serv.cc b/sql-common/net_serv.cc index 79a9f8f99933..640fb6b854d0 100644 --- a/sql-common/net_serv.cc +++ b/sql-common/net_serv.cc @@ -152,7 +152,7 @@ bool my_net_init(NET *net, Vio *vio) { MYF(MY_WME)))) return true; net->buff_end = net->buff + net->max_packet; - net->error = 0; + net->error = NET_ERROR_UNSET; net->return_status = nullptr; net->pkt_nr = net->compress_pkt_nr = 0; net->write_pos = net->read_pos = net->buff; @@ -212,8 +212,8 @@ bool net_realloc(NET *net, size_t length) { if (length >= net->max_packet_size) { DBUG_PRINT("error", ("Packet too large. Max size: %lu", net->max_packet_size)); - /* @todo: 1 and 2 codes are identical. */ - net->error = 1; + /* Error, but no need to stop using the socket. */ + net->error = NET_ERROR_SOCKET_RECOVERABLE; net->last_errno = ER_NET_PACKET_TOO_LARGE; #ifdef MYSQL_SERVER my_error(ER_NET_PACKET_TOO_LARGE, MYF(0)); @@ -230,8 +230,8 @@ bool net_realloc(NET *net, size_t length) { if (!(buff = (uchar *)my_realloc( key_memory_NET_buff, (char *)net->buff, pkt_length + NET_HEADER_SIZE + COMP_HEADER_SIZE, MYF(MY_WME)))) { - /* @todo: 1 and 2 codes are identical. */ - net->error = 1; + /* Error, but no need to stop using the socket. */ + net->error = NET_ERROR_SOCKET_RECOVERABLE; net->last_errno = ER_OUT_OF_RESOURCES; /* In the server the error is reported by MY_WME flag. */ return true; @@ -259,6 +259,7 @@ bool net_realloc(NET *net, size_t length) { void net_clear(NET *net, bool check_buffer MY_ATTRIBUTE((unused))) { DBUG_TRACE; + DBUG_EXECUTE_IF("simulate_bad_field_length_1", { net->pkt_nr = net->compress_pkt_nr = 0; net->write_pos = net->buff; @@ -269,8 +270,6 @@ void net_clear(NET *net, bool check_buffer MY_ATTRIBUTE((unused))) { net->write_pos = net->buff; return; }); - /* Ensure the socket buffer is empty, except for an EOF (at least 1). */ - DBUG_ASSERT(!check_buffer || (vio_pending(net->vio) <= 1)); /* Ready for new command */ net->pkt_nr = net->compress_pkt_nr = 0; @@ -1009,9 +1008,13 @@ static bool net_write_raw_loop(NET *net, const uchar *buf, size_t count) { /* On failure, propagate the error code. */ if (count) { +#ifdef MYSQL_SERVER /* Socket should be closed. */ - net->error = 2; - + net->error = NET_ERROR_SOCKET_UNUSABLE; +#else + /* Socket has failed for writing but it might still work for reading. */ + net->error = NET_ERROR_SOCKET_NOT_WRITABLE; +#endif /* Interrupted by a timeout? */ if (vio_was_timeout(net->vio)) net->last_errno = ER_NET_WRITE_INTERRUPTED; @@ -1285,14 +1288,16 @@ bool net_write_packet(NET *net, const uchar *packet, size_t length) { DBUG_TRACE; /* Socket can't be used */ - if (net->error == 2) return true; + if (net->error == NET_ERROR_SOCKET_UNUSABLE || + net->error == NET_ERROR_SOCKET_NOT_WRITABLE) + return true; net->reading_or_writing = 2; const bool do_compress = net->compress; if (do_compress) { if ((packet = compress_packet(net, packet, &length)) == nullptr) { - net->error = 2; + net->error = NET_ERROR_SOCKET_UNUSABLE; net->last_errno = ER_OUT_OF_RESOURCES; /* In the server, allocation failure raises a error. */ net->reading_or_writing = 0; @@ -1310,6 +1315,12 @@ bool net_write_packet(NET *net, const uchar *packet, size_t length) { net->reading_or_writing = 0; + /* Socket can't be used any more */ + if (net->error == NET_ERROR_SOCKET_NOT_READABLE) { + net->error = NET_ERROR_SOCKET_UNUSABLE; + return true; + } + return res; } @@ -1358,9 +1369,6 @@ static bool net_read_raw_loop(NET *net, size_t count) { /* On failure, propagate the error code. */ if (count) { - /* Socket should be closed. */ - net->error = 2; - /* Interrupted by a timeout? */ if (!eof && vio_was_timeout(net->vio)) net->last_errno = ER_NET_READ_INTERRUPTED; @@ -1368,13 +1376,22 @@ static bool net_read_raw_loop(NET *net, size_t count) { net->last_errno = ER_NET_READ_ERROR; #ifdef MYSQL_SERVER - my_error(net->last_errno, MYF(0)); /* First packet always wait for net_wait_timeout */ if (net->pkt_nr == 0 && vio_was_timeout(net->vio)) { - net->last_errno = ER_NET_WAIT_ERROR; + net->last_errno = ER_CLIENT_INTERACTION_TIMEOUT; /* Socket should be closed after trying to write/send error. */ - LogErr(INFORMATION_LEVEL, net->last_errno); + LogErr(INFORMATION_LEVEL, ER_NET_WAIT_ERROR); } + net->error = NET_ERROR_SOCKET_NOT_READABLE; + /* + Attempt to send error message to client although the client won't be + expecting messages. If later the client tries to send a command and fail + it will instead check if it can read an error message. + */ + my_error(net->last_errno, MYF(0)); +#else + /* Socket should be closed. */ + net->error = NET_ERROR_SOCKET_UNUSABLE; #endif } @@ -1436,6 +1453,27 @@ static bool net_read_packet_header(NET *net) { */ if (pkt_nr != (uchar)net->pkt_nr) { /* Not a NET error on the client. XXX: why? */ +#if !defined(MYSQL_SERVER) + DBUG_PRINT("info", ("pkt_nr %u net->pkt_nr %u", pkt_nr, net->pkt_nr)); + if (net->pkt_nr == 1) { + DBUG_ASSERT(net->where_b == 0); + /* + Server may have sent an error before it received our new command. + Perhaps due to wait_timeout. + Only use what is already read and then close the socket. + */ + net->error = NET_ERROR_SOCKET_UNUSABLE; + net->last_errno = ER_NET_PACKETS_OUT_OF_ORDER; + net->pkt_nr = pkt_nr + 1; + + /* + The caller should handle the error code in the packet + and the socket are blocked from further usage, + so reading the packet header was OK. + */ + return false; + } +#endif #if defined(MYSQL_SERVER) my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0)); #elif defined(EXTRA_DEBUG) @@ -1514,7 +1552,7 @@ static ulong net_read_available(NET *net, size_t count) { } /* EOF or hard failure; socket should be closed. */ - net->error = 2; + net->error = NET_ERROR_SOCKET_UNUSABLE; net->last_errno = ER_NET_READ_ERROR; return packet_error; } @@ -1704,7 +1742,7 @@ static net_async_status net_read_packet_nonblocking(NET *net, ulong *ret) { #ifdef MYSQL_SERVER my_error(ER_NET_UNCOMPRESS_ERROR, MYF(0)); #else - net->error = 2; // caller will close socket + net->error = NET_ERROR_SOCKET_UNUSABLE; // caller will close socket net->last_errno = ER_NET_UNCOMPRESS_ERROR; #endif goto error; @@ -2042,11 +2080,15 @@ static size_t net_read_packet(NET *net, size_t *complen) { if (net_read_raw_loop(net, pkt_len)) goto error; end: + if (net->error == NET_ERROR_SOCKET_NOT_WRITABLE) + net->error = NET_ERROR_SOCKET_UNUSABLE; DBUG_DUMP("net read", net->buff + net->where_b, pkt_len); net->reading_or_writing = 0; return pkt_len; error: + if (net->error == NET_ERROR_SOCKET_NOT_WRITABLE) + net->error = NET_ERROR_SOCKET_UNUSABLE; net->reading_or_writing = 0; return packet_error; } @@ -2120,7 +2162,7 @@ static void net_read_compressed_packet(NET *net, size_t &len) { mysql_compress_context *mysql_compress_ctx = compress_context(net); if (my_uncompress(mysql_compress_ctx, net->buff + net->where_b, len, &complen)) { - net->error = 2; /* caller will close socket */ + net->error = NET_ERROR_SOCKET_UNUSABLE; /* caller will close socket */ net->last_errno = ER_NET_UNCOMPRESS_ERROR; #ifdef MYSQL_SERVER my_error(ER_NET_UNCOMPRESS_ERROR, MYF(0)); diff --git a/sql/protocol_classic.cc b/sql/protocol_classic.cc index b104e43deecf..42962fc515c9 100644 --- a/sql/protocol_classic.cc +++ b/sql/protocol_classic.cc @@ -954,7 +954,7 @@ static bool net_send_ok(THD *thd, uint server_status, uint statement_warn_count, /* OK packet length will be restricted to 16777215 bytes */ if (((size_t)(pos - start)) > MAX_PACKET_LENGTH) { - net->error = 1; + net->error = NET_ERROR_SOCKET_RECOVERABLE; net->last_errno = ER_NET_OK_PACKET_TOO_LARGE; my_error(ER_NET_OK_PACKET_TOO_LARGE, MYF(0)); DBUG_PRINT("info", ("OK packet too large")); @@ -1410,7 +1410,7 @@ int Protocol_classic::read_packet() { } bad_packet = true; - return m_thd->net.error == 3 ? 1 : -1; + return m_thd->net.error == NET_ERROR_SOCKET_UNUSABLE ? 1 : -1; } /* clang-format off */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9894069b317a..0eb3f090734e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1292,7 +1292,7 @@ bool do_command(THD *thd) { return_value = true; // We have to close it. goto out; } - net->error = 0; + net->error = NET_ERROR_UNSET; return_value = false; goto out; } @@ -2039,7 +2039,7 @@ bool dispatch_command(THD *thd, const COM_DATA *com_data, // Don't give 'abort' message // TODO: access of protocol_classic should be removed if (thd->is_classic_protocol()) - thd->get_protocol_classic()->get_net()->error = 0; + thd->get_protocol_classic()->get_net()->error = NET_ERROR_UNSET; thd->get_stmt_da()->disable_status(); // Don't send anything back error = true; // End server break; @@ -4946,7 +4946,7 @@ void dispatch_sql_command(THD *thd, Parser_state *parser_state) { if (mqh_used && thd->get_user_connect() && check_mqh(thd, lex->sql_command)) { if (thd->is_classic_protocol()) - thd->get_protocol_classic()->get_net()->error = 0; + thd->get_protocol_classic()->get_net()->error = NET_ERROR_UNSET; } else { if (!thd->is_error()) { /*