mirror of https://github.com/sysown/proxysql
Added reproduction test isolating and documenting issue described in #3404
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…
Reference in new issue