Added reproduction test isolating and documenting issue described in #3404

pull/3440/head
Javier Jaramago Fernández 5 years ago
parent f403402755
commit 879974897d

@ -0,0 +1,185 @@
/**
* @file res_3404-mysql_close_fd_leak.cpp
* @brief This file is not a test to test to be executed,
* it contains an isolated reproduction of issue #3404.
* @details This test contains and isolated reproduction of
* issue #3404. For this, it uses the async 'libmariadbclient'
* API in a fashion in which a file descriptor leak is created.
*
* # Test usage
*
* As the rest of tap tests this file requires serveral constants to
* be supplied, for making use of this file it's required to execute
* the following commands:
*
* ```
* $ source constants
* $ sudo iptables -A OUTPUT -p tcp --dport $TAP_PORT -j DROP
* ```
*
* This way we make sure that the connection is going to try to
* be performed and that the MySQL connection attempt is going
* to fail with a timeout. Finally we launch the test file,
* supplying the 'leak' parameter, to instruct the test to
* use the flow that creates the leak:
*
* ```
* $ ./tests/repro_3404-mysql_close_fd_leak-t leak
* ```
*
* Check that the file descriptors are being leak:
*
* ```
* sudo lsof -p $(pgrep -f res_3404-socket_fd_not_closed) | wc -l
* ```
*
* We can also verify that nothing is being leak if we don't supply
* the 'leak' parameter to the executable.
*
* # Implementation details
*
* For creating the leak this test creates the following flow:
*
* 1. Initialize the 'MYSQL' object using 'mysql_init'.
* 2. Attempt to start a connection with 'mysql_real_connect_start'.
* 3. Connection should timeout due to the server being unreachable.
* Check usage.
* 4. We immediately call 'mysql_close'. Leaking a file descriptor.
*
*/
#include <cstring>
#include <vector>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <mysql.h>
#include <thread>
#include "tap.h"
#include "command_line.h"
#include "utils.h"
/* Helper function to do the waiting for events on the socket. */
static int wait_for_mysql(MYSQL *mysql, int status) {
struct pollfd pfd;
int timeout, res;
pfd.fd = mysql_get_socket(mysql);
pfd.events =
(status & MYSQL_WAIT_READ ? POLLIN : 0) |
(status & MYSQL_WAIT_WRITE ? POLLOUT : 0) |
(status & MYSQL_WAIT_EXCEPT ? POLLPRI : 0);
if (status & MYSQL_WAIT_TIMEOUT)
timeout = 1000*mysql_get_timeout_value(mysql);
else
timeout = -1;
res = poll(&pfd, 1, timeout);
if (res == 0)
return MYSQL_WAIT_TIMEOUT;
else if (res < 0)
return MYSQL_WAIT_TIMEOUT;
else {
int status = 0;
if (pfd.revents & POLLIN) status |= MYSQL_WAIT_READ;
if (pfd.revents & POLLOUT) status |= MYSQL_WAIT_WRITE;
if (pfd.revents & POLLPRI) status |= MYSQL_WAIT_EXCEPT;
return status;
}
}
int main(int argc, char** argv) {
CommandLine cl;
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return -1;
}
bool create_leak = false;
if (argc == 2) {
std::string param { argv[1] };
if (param == "leak") {
create_leak = true;
}
}
for (int i = 0; i < 1000; i++) {
MYSQL* proxysql = mysql_init(NULL);
MYSQL* ret = NULL;
unsigned int timeout = 1;
mysql_options(proxysql, MYSQL_OPT_NONBLOCK, 0);
mysql_options(proxysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
mysql_ssl_set(proxysql, NULL, NULL, NULL, NULL, NULL);
int status = 0;
diag("Openning connection number: %d", i);
status =
mysql_real_connect_start(
&ret,
proxysql,
cl.host,
cl.username,
cl.password,
NULL,
cl.port,
NULL,
CLIENT_SSL
);
if (status == 0) {
fprintf(
stderr, "File %s, line %d, Error: %s\n",
__FILE__, __LINE__, mysql_error(proxysql)
);
return -1;
}
my_socket sockt = mysql_get_socket(proxysql);
int state = 0;
MYSQL* ret_mysql = NULL;
while (status) {
diag(":: Waiting for MySQL server on connection '%d'", i);
status = wait_for_mysql(proxysql, status);
// don't do finalize the connect! We directly call 'mysql_close'
// creating a leak in the already internally initalized 'fd'
// created by 'libmariadbclient'.
if (status == MYSQL_WAIT_TIMEOUT && create_leak) {
diag(":: Premature close in connection '%d', leaking 'fd'...", i);
break;
}
// even if we timeout, we call continue
diag(":: Calling 'mysql_real_connect_cont' with status: %d", status);
status = mysql_real_connect_cont(&ret_mysql, proxysql, status);
}
if (ret_mysql) {
diag(":: This is not expected, make sure the server is not reachable.");
} else {
// now we close
diag(
":: Calling 'mysql_close' after 'mysql_real_connect_cont' returned status: %d",
status
);
mysql_close(proxysql);
}
sleep(1);
}
return exit_status();
}
Loading…
Cancel
Save