Bug #98177 CR_CONN_HOST_ERROR never returned by async client
Submitted: 10 Jan 3:14 Modified: 10 Jan 5:43
Reporter: Manuel Ung Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: C API (client library) Severity:S3 (Non-critical)
Version:8.0.18 OS:Any
Assigned to: CPU Architecture:Any

[10 Jan 3:14] Manuel Ung
Description:
During connection, the initial tcp connect is started by calling vio_socket_connect in csm_begin_connect.

In blocking mode, vio_socket_connect blocks until the connection finishes (either with success or failure), and its return code can be checked immediately in csm_begin_connect (and CR_CONN_HOST_ERROR is returned if a error was hit).

However, in nonblocking mode, vio_socket_connect returns right away, and would not necessarily return an error if the network was not ready yet. There is no call to vio_io_wait to check for connection status, so instead, we error when attempt to write to an disconnected socket.

This is the reason why the mtr test main.ipv4_as_ipv6 fails in the async client.

How to repeat:
./mtr --async-client main.ipv4_as_ipv6

Suggested fix:
Call vio_io_wait in csm_complete_connect to verify if connection has succeeded or not, and return not ready if needed.

Additionally, there is error checking code in vio_socket_connect that is not executed in nonblocking mode (ie. the call to mysql_socket_getsockopt to collect errno). This code will have to run after vio_io_wait indicates that network is ready.
[10 Jan 5:43] MySQL Verification Team
Hello Manuel Ung,

Thank you for the bug report and feedback.

regards,
Umesh
[25 Mar 18:40] Jay Edgar
In addition, once #98574 and #98980 are fixed, you may need to add a check in cli_advanced_command_nonblocking() to make sure that net_async is not null.  This is because with this check added, it is possible to never get net_async assigned (via my_net_init).  If that happens then when mysql_close() is called, it will call cli_advanced_command_nonblocking() with net_async (via NET_ASYNC_DATA(mysql)) having never been initialized:

diff --git a/sql-common/client.cc b/sql-common/client.cc
index a44e9f4ec17..8d66cbdbb0c 100644
--- a/sql-common/client.cc
+++ b/sql-common/client.cc
@@ -1445,7 +1445,7 @@ net_async_status cli_advanced_command_nonblocking(
     DBUG_DUMP("sending arg", arg, arg_length);
   }

-  if (mysql->net.vio == 0) {
+  if (mysql->net.vio == 0 || !net_async) {
     set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate);
     goto end;
   }

I was able to reproduce the problem by attempting to connect to an invalid IP address, fail (because mysql_real_connect_nonblocking will always return NET_ASYNC_NOT_READY) and the call mysql_close():

diff --git a/testclients/mysql_client_test.cc b/testclients/mysql_client_test.cc
index 9e558664abf..c36b79b03db 100644
--- a/testclients/mysql_client_test.cc
+++ b/testclients/mysql_client_test.cc
@@ -20165,6 +20165,26 @@ static void test_bug27443252() {

 void perform_arithmatic() { fprintf(stdout, "\n Do some other stuff."); }

+static void test_crash() {
+  MYSQL *mysql_local;
+  net_async_status status;
+
+  /*make new non blocking connection to do asynchronous operations */
+  if (!(mysql_local = mysql_client_init(NULL))) {
+    myerror("mysql_client_init() failed");
+    exit(1);
+  }
+
+  status = mysql_real_connect_nonblocking(mysql_local, "0100::", opt_user, opt_password, current_db, opt_port, opt_unix_socket, CLIENT_MULTI_STATEMENTS);
+  if (status != NET_ASYNC_NOT_READY) {
+    myerror("Unexpected result from mysql_real_connect_nonblocking()");
+    exit(1);
+  }
+
+  // See if we can close without crashing
+  mysql_close(mysql_local);
+}
+
 static void test_wl11381() {
   MYSQL *mysql_local;
   MYSQL_RES *result;
@@ -20874,6 +20894,7 @@ static struct my_tests_st my_tests[] = {
     {"test_wl11772", test_wl11772},
     {"test_wl12475", test_wl12475},
     {"test_bug30032302", test_bug30032302},
+    {"test_crash", test_crash},
     {0, 0}};

 static struct my_tests_st *get_my_tests() { return my_tests; }