Details
-
Bug
-
Status: Closed (View Workflow)
-
Major
-
Resolution: Fixed
-
2.1
-
None
-
None
-
All
Description
When using async, there is a logic error in the connect/close processing when using async but an error occurs during connection. Sample code:
MYSQL* pTest, *pRes;
int nRV, nRes;
// Create for async:
pTest = mysql_init(nullptr);
mysql_options(pTest, MYSQL_OPT_NONBLOCK, 0);
// Async connect:
nRV = mysql_real_connect_start(&pRes, pTest, "nonexist.example.com", "", "", "", 0, nullptr, 0);
while (nRV != 0)
if (pRes == nullptr) { printf("FAIL\n"); } else { printf("SUCC\n"); };
// Async close:
nRV = mysql_close_start(pTest);
while (nRV != 0) { nRV = mysql_close_cont(pTest, MYSQL_WAIT_WRITE | MYSQL_WAIT_READ); };
This will segfault in mysql_close_start. The problem is that when real_connect fails, it deletes the options object that indicates that the client is using async. This happens on Line 1989 of libmariadb.c. This does not occur if there is a connection/network failure in any other async function. For comparison, here is modified sample code:
MYSQL* pTest, *pRes;
int nRV, nRes;
// Create for async:
pTest = mysql_init(nullptr);
mysql_options(pTest, MYSQL_OPT_NONBLOCK, 0);
// Async connect:
nRV = mysql_real_connect_start(&pRes, pTest, nullptr, "", "", "", 0, nullptr, 0);
while (nRV != 0) { nRV = mysql_real_connect_cont(&pRes, pTest, MYSQL_WAIT_WRITE | MYSQL_WAIT_READ); }
;
if (pRes == nullptr)
// Async query:
nRV = mysql_query_start(&nRes, pTest, "show databases");
while (nRV != 0) { closesocket(mysql_get_socket(pTest)); nRV = mysql_query_cont(&nRes, pTest, MYSQL_WAIT_WRITE | MYSQL_WAIT_READ); };
if (nRes == 0) { printf("SUCCn"); } else { printf("FAILn"); }
;
// Async close:
nRV = mysql_close_start(pTest);
while (nRV != 0)
;
This will run successfully without any segfault, because when the mysql_query_cont() call fails, it does not delete the options object.
There are a few possible fixes:
1. Add note to the documentation that mysql_close_start cannot be used if an error occurs in mysql_real_connect_start/cont.
2. Remove the call to mysql_close_options() at the bottom of the mthd_my_real_connect() function. This way, the MYSQL* object will be in the same state regardless of whether a connection error occurs in mysql_connect or mysql_query.
3. Modify mysql_close_start() to first check if the socket is actually running. If it is not running, then it can divert to run the synchronous mysql_close() instead. This avoids the problem under all circumstances without modifying the current behavior. For this, change part of mysql_async.c from:
int STDCALL
mysql_close_start(MYSQL *sock)
{
int res;
/* It is legitimate to have NULL sock argument, which will do nothing. */
if (sock)
mysql_close(sock);
return 0;
}
To:
mysql_close_start(MYSQL *sock)
{
int res;
/* It is legitimate to have NULL sock argument, which will do nothing. */
if (sock && sock->net.vio)
{ res= mysql_close_slow_part_start(sock); /* If we need to block, return now and do the rest in mysql_close_cont(). */ if (res) return res; }
mysql_close(sock);
return 0;
}
Note the only change is the if conditional, from:
if (sock)
To:
if (sock && sock->net.vio)
An alternative that would work is:
if (sock && sock->options.extension)