Bug #98574 mysql_real_connect_nonblocking() blocks
Submitted: 12 Feb 2020 21:29 Modified: 13 Feb 2020 16:51
Reporter: Jay Edgar Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: C API (client library) Severity:S2 (Serious)
Version:8.0.17 OS:Any
Assigned to: CPU Architecture:Any

[12 Feb 2020 21:29] Jay Edgar
Description:
The function mysql_real_connect_nonblocking() never returns NET_ASYNC_NOT_READY.  It always loops as long as the state machine is not done or failed.  This defeats the purpose of having a non-blocking function.

How to repeat:
Inspect the code - https://github.com/mysql/mysql-server/blob/8.0/sql-common/client.cc#L5723-L5725

Suggested fix:
The following is a suggested fix.  It involves only staying in the loop when the state machine is in state STATE_MACHINE_CONTINUE.  If the state is STATE_MACHINE_WOULD_BLOCK it then returns NET_ASYNC_NOT_READY as would be expected.

In addition the check for compression is only done on the first invocation of the function as all the parameters (besides the MYSQL handle itself) should be ignored for subsequent calls.

The mysqltest.cc needs to validate that it has a valid NET_ASYNC* before attempting to use it as this can apparently be null when the state machine is in the STATE_MACHINE_WOULD_BLOCK state.

diff --git a/client/mysqltest.cc b/client/mysqltest.cc
index 4dfd743722c..ebbc0217b31 100644
--- a/client/mysqltest.cc
+++ b/client/mysqltest.cc
@@ -994,8 +994,10 @@ static MYSQL *async_mysql_real_connect_wrapper(
          NET_ASYNC_NOT_READY) {
     t.check();
     NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net));
-    socket_event_listen(net_async->async_blocking_state,
-                        mysql_get_socket_descriptor(mysql));
+    if (net_async) {
+      socket_event_listen(net_async->async_blocking_state,
+                          mysql_get_socket_descriptor(mysql));
+    }
   }
   if (status == NET_ASYNC_ERROR)
     return nullptr;
diff --git a/sql-common/client.cc b/sql-common/client.cc
index 8284f64f5d9..e4b24ec2663 100644
--- a/sql-common/client.cc
+++ b/sql-common/client.cc
@@ -5786,6 +5786,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host,

   @retval       NET_ASYNC_COMPLETE               Success.
   @retval       NET_ASYNC_ERROR                  Error.
+  @retval       NET_ASYNC_NOT_READY              Connection not yet complete
+                                                 Keep retrying
 */
 net_async_status STDCALL mysql_real_connect_nonblocking(
     MYSQL *mysql, const char *host, const char *user, const char *passwd,
@@ -5795,11 +5797,12 @@ net_async_status STDCALL mysql_real_connect_nonblocking(
   mysql_state_machine_status status;
   mysql_async_connect *ctx = ASYNC_DATA(mysql)->connect_context;

-  if (client_flag & MYSQL_OPT_COMPRESS) {
-    set_mysql_error(mysql, CR_COMPRESSION_NOT_SUPPORTED, unknown_sqlstate);
-    DBUG_RETURN(NET_ASYNC_ERROR);
-  }
   if (!ctx) {
+    if (client_flag & MYSQL_OPT_COMPRESS) {
+      set_mysql_error(mysql, CR_COMPRESSION_NOT_SUPPORTED, unknown_sqlstate);
+      DBUG_RETURN(NET_ASYNC_ERROR);
+    }
+
     ctx = static_cast<mysql_async_connect *>(
         my_malloc(key_memory_MYSQL, sizeof(*ctx), MYF(MY_WME | MY_ZEROFILL)));
     if (!ctx) DBUG_RETURN(NET_ASYNC_ERROR);
@@ -5821,7 +5824,11 @@ net_async_status STDCALL mysql_real_connect_nonblocking(

   do {
     status = ctx->state_function(ctx);
-  } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE);
+  } while (status == STATE_MACHINE_CONTINUE);
+
+  if (status == STATE_MACHINE_WOULD_BLOCK) {
+    DBUG_RETURN(NET_ASYNC_NOT_READY);
+  }

   if (status == STATE_MACHINE_DONE) {
     my_free(ASYNC_DATA(mysql)->connect_context);
[13 Feb 2020 12:25] MySQL Verification Team
Hello Mr. Edgar,

Thank you for your bug report.

I agree with your analysis. I have studied the proposed patch, and with some minor changes, it is a truly welcome addition to our C API.

Verified as reported.
[13 Feb 2020 16:51] Jay Edgar
Found some more changes that need to be made to the mysql_client_test as it assumes that mysql_real_connect_nonblocking does not return NET_ASYNC_NOT_READY.

diff --git a/testclients/mysql_client_test.cc b/testclients/mysql_client_test.cc
index a22de6c6ae4..e2f7e48b866 100644
--- a/testclients/mysql_client_test.cc
+++ b/testclients/mysql_client_test.cc
@@ -20160,9 +20160,11 @@ static void test_wl11381() {
     myerror("mysql_client_init() failed");
     exit(1);
   }
-  status = mysql_real_connect_nonblocking(
-      mysql_local, opt_host, opt_user, opt_password, current_db, opt_port,
-      opt_unix_socket, CLIENT_MULTI_STATEMENTS);
+  do {
+    status = mysql_real_connect_nonblocking(
+        mysql_local, opt_host, opt_user, opt_password, current_db, opt_port,
+        opt_unix_socket, CLIENT_MULTI_STATEMENTS);
+  } while (status == NET_ASYNC_NOT_READY);
   if (status == NET_ASYNC_ERROR) {
     fprintf(stdout, "\n mysql_real_connect_nonblocking() failed");
     exit(1);
@@ -20339,8 +20341,8 @@ static void test_wl11381() {
 static void test_wl11381_qa() {
   MYSQL *mysql_con1;
   MYSQL *mysql_con2;
-  net_async_status mysql_con1_status;
-  net_async_status mysql_con2_status;
+  net_async_status mysql_con1_status = NET_ASYNC_NOT_READY;
+  net_async_status mysql_con2_status = NET_ASYNC_NOT_READY;
   const char *stmt_text;
   int counter = 0;

@@ -20356,13 +20358,20 @@ static void test_wl11381_qa() {
     exit(1);
   }

-  mysql_con1_status = (mysql_real_connect_nonblocking(
-      mysql_con1, opt_host, opt_user, opt_password, current_db, opt_port,
-      opt_unix_socket, CLIENT_MULTI_STATEMENTS));
+  do {
+    if (mysql_con1_status == NET_ASYNC_NOT_READY) {
+      mysql_con1_status = (mysql_real_connect_nonblocking(
+          mysql_con1, opt_host, opt_user, opt_password, current_db, opt_port,
+          opt_unix_socket, CLIENT_MULTI_STATEMENTS));
+    }

-  mysql_con2_status = (mysql_real_connect_nonblocking(
-      mysql_con2, opt_host, opt_user, opt_password, current_db, opt_port,
-      opt_unix_socket, CLIENT_MULTI_STATEMENTS));
+    if (mysql_con2_status == NET_ASYNC_NOT_READY) {
+      mysql_con2_status = (mysql_real_connect_nonblocking(
+          mysql_con2, opt_host, opt_user, opt_password, current_db, opt_port,
+          opt_unix_socket, CLIENT_MULTI_STATEMENTS));
+    }
+  } while (mysql_con1_status == NET_ASYNC_NOT_READY ||
+           mysql_con2_status == NET_ASYNC_NOT_READY);

   if (mysql_con1_status == NET_ASYNC_ERROR) {
     fprintf(stdout, "\n mysql_real_connect_nonblocking() failed");
[28 Feb 2020 14:16] Georgi Kodinov
Posted by developer:
 
Duplicate of Bug#30771233