You've already forked mariadb-connector-c
mirror of
https://github.com/mariadb-corporation/mariadb-connector-c.git
synced 2025-08-08 14:02:17 +03:00
Fixed crash/undefined behaviour when running large amount of threads:
replaced select() with poll() Added conneciton timeout support for windows platforms
This commit is contained in:
@@ -63,6 +63,9 @@
|
|||||||
#ifdef HAVE_OPENSSL
|
#ifdef HAVE_OPENSSL
|
||||||
#include <ma_secure.h>
|
#include <ma_secure.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
static my_bool mysql_client_init=0;
|
static my_bool mysql_client_init=0;
|
||||||
static void mysql_close_options(MYSQL *mysql);
|
static void mysql_close_options(MYSQL *mysql);
|
||||||
@@ -145,93 +148,81 @@ extern void mysql_client_plugin_deinit();
|
|||||||
* Base version coded by Steve Bernacki, Jr. <steve@navinet.net>
|
* Base version coded by Steve Bernacki, Jr. <steve@navinet.net>
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
int socket_block(my_socket s,my_bool blocked)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
unsigned long socket_blocked= blocked ? 0 : 1;
|
||||||
|
return ioctlsocket(s, FIONBIO, &socket_blocked);
|
||||||
|
#else
|
||||||
|
int flags= fcntl(s, F_GETFL, 0);
|
||||||
|
if (blocked)
|
||||||
|
flags&= ~O_NONBLOCK;
|
||||||
|
else
|
||||||
|
flags|= O_NONBLOCK;
|
||||||
|
return fcntl(s, F_SETFL, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static int connect2(my_socket s, const struct sockaddr *name, uint namelen,
|
static int connect2(my_socket s, const struct sockaddr *name, uint namelen,
|
||||||
uint timeout)
|
uint timeout)
|
||||||
{
|
{
|
||||||
#if defined(_WIN32) || defined(OS2)
|
int res, s_err;
|
||||||
return connect(s, (struct sockaddr*) name, namelen);
|
|
||||||
#else
|
|
||||||
int flags, res, s_err;
|
|
||||||
socklen_t s_err_size = sizeof(uint);
|
socklen_t s_err_size = sizeof(uint);
|
||||||
fd_set sfds;
|
#ifndef _WIN32
|
||||||
|
struct pollfd poll_fd;
|
||||||
|
#else
|
||||||
|
FD_SET sfds, efds;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
time_t start_time, now_time;
|
#endif
|
||||||
|
|
||||||
/*
|
if (!timeout)
|
||||||
If they passed us a timeout of zero, we should behave
|
|
||||||
exactly like the normal connect() call does.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (timeout == 0)
|
|
||||||
return connect(s, (struct sockaddr*) name, namelen);
|
return connect(s, (struct sockaddr*) name, namelen);
|
||||||
|
|
||||||
flags = fcntl(s, F_GETFL, 0); /* Set socket to not block */
|
/* set socket to non blocking */
|
||||||
#ifdef O_NONBLOCK
|
if (socket_block(s, 0) == SOCKET_ERROR)
|
||||||
fcntl(s, F_SETFL, flags | O_NONBLOCK); /* and save the flags.. */
|
return -1;
|
||||||
#endif
|
|
||||||
|
|
||||||
res = connect(s, (struct sockaddr*) name, namelen);
|
res= connect(s, (struct sockaddr*) name, namelen);
|
||||||
s_err = errno; /* Save the error... */
|
if (res == 0)
|
||||||
fcntl(s, F_SETFL, flags);
|
return res;
|
||||||
if ((res != 0) && (s_err != EINPROGRESS))
|
|
||||||
{
|
|
||||||
errno = s_err; /* Restore it */
|
|
||||||
return(-1);
|
|
||||||
}
|
|
||||||
if (res == 0) /* Connected quickly! */
|
|
||||||
return(0);
|
|
||||||
|
|
||||||
/*
|
#ifdef _WIN32
|
||||||
Otherwise, our connection is "in progress." We can use
|
if (GetLastError() != WSAEWOULDBLOCK &&
|
||||||
the select() call to wait up to a specified period of time
|
GetLastError() != WSAEINPROGRESS)
|
||||||
for the connection to suceed. If select() returns 0
|
|
||||||
(after waiting howevermany seconds), our socket never became
|
|
||||||
writable (host is probably unreachable.) Otherwise, if
|
|
||||||
select() returns 1, then one of two conditions exist:
|
|
||||||
|
|
||||||
1. An error occured. We use getsockopt() to check for this.
|
|
||||||
2. The connection was set up sucessfully: getsockopt() will
|
|
||||||
return 0 as an error.
|
|
||||||
|
|
||||||
Thanks goes to Andrew Gierth <andrew@erlenstar.demon.co.uk>
|
|
||||||
who posted this method of timing out a connect() in
|
|
||||||
comp.unix.programmer on August 15th, 1997.
|
|
||||||
*/
|
|
||||||
|
|
||||||
FD_ZERO(&sfds);
|
|
||||||
FD_SET(s, &sfds);
|
|
||||||
/*
|
|
||||||
select could be interrupted by a signal, and if it is,
|
|
||||||
the timeout should be adjusted and the select restarted
|
|
||||||
to work around OSes that don't restart select and
|
|
||||||
implementations of select that don't adjust tv upon
|
|
||||||
failure to reflect the time remaining
|
|
||||||
*/
|
|
||||||
start_time = time(NULL);
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
tv.tv_sec = (long) timeout;
|
|
||||||
tv.tv_usec = 0;
|
|
||||||
#if defined(HPUX) && defined(THREAD)
|
|
||||||
if ((res = select(s+1, NULL, (int*) &sfds, NULL, &tv)) > 0)
|
|
||||||
break;
|
|
||||||
#else
|
#else
|
||||||
if ((res = select(s+1, NULL, &sfds, NULL, &tv)) > 0)
|
if (errno != EINPROGRESS)
|
||||||
break;
|
|
||||||
#endif
|
#endif
|
||||||
if (res == 0) /* timeout */
|
return -1;
|
||||||
return -1;
|
#ifndef _WIN32
|
||||||
now_time=time(NULL);
|
memset(&poll_fd, 0, sizeof(struct pollfd));
|
||||||
timeout-= (uint) (now_time - start_time);
|
poll_fd.events= POLLOUT | POLLERR;
|
||||||
if (errno != EINTR || (int) timeout <= 0)
|
poll_fd.fd= s;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* connection timeout in milliseconds */
|
||||||
select() returned something more interesting than zero, let's
|
res= poll(&poll_fd, 1, (timeout > -1) ? timeout * 1000 : timeout);
|
||||||
see if we have any errors. If the next two statements pass,
|
|
||||||
we've got an open socket!
|
switch(res)
|
||||||
*/
|
{
|
||||||
|
/* Error= - 1, timeout = 0 */
|
||||||
|
case -1:
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
errno= ETIMEDOUT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
FD_ZERO(&sfds);
|
||||||
|
FD_ZERO(&efds);
|
||||||
|
FD_SET(s, &sfds);
|
||||||
|
FD_SET(s, &efds);
|
||||||
|
|
||||||
|
memset(&tv, 0, sizeof(struct timeval));
|
||||||
|
tv.tv_sec= timeout;
|
||||||
|
|
||||||
|
res= select(s+1, NULL, &sfds, &efds, &tv);
|
||||||
|
if (res < 1)
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
s_err=0;
|
s_err=0;
|
||||||
if (getsockopt(s, SOL_SOCKET, SO_ERROR, (char*) &s_err, &s_err_size) != 0)
|
if (getsockopt(s, SOL_SOCKET, SO_ERROR, (char*) &s_err, &s_err_size) != 0)
|
||||||
@@ -243,8 +234,6 @@ static int connect2(my_socket s, const struct sockaddr *name, uint namelen,
|
|||||||
return(-1); /* but return an error... */
|
return(-1); /* but return an error... */
|
||||||
}
|
}
|
||||||
return (0); /* ok */
|
return (0); /* ok */
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1521,6 +1510,8 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user,
|
|||||||
unix_socket, socket_errno);
|
unix_socket, socket_errno);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
if (socket_block(sock, 1) == SOCKET_ERROR)
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
@@ -1610,7 +1601,14 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user,
|
|||||||
}
|
}
|
||||||
if (!(rc= connect2(sock, save_res->ai_addr, save_res->ai_addrlen,
|
if (!(rc= connect2(sock, save_res->ai_addr, save_res->ai_addrlen,
|
||||||
mysql->options.connect_timeout)))
|
mysql->options.connect_timeout)))
|
||||||
|
{
|
||||||
|
if (socket_block(sock, 1) == SOCKET_ERROR)
|
||||||
|
{
|
||||||
|
closesocket(sock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break; /* success! */
|
break; /* success! */
|
||||||
|
}
|
||||||
|
|
||||||
vio_delete(mysql->net.vio);
|
vio_delete(mysql->net.vio);
|
||||||
mysql->net.vio= NULL;
|
mysql->net.vio= NULL;
|
||||||
|
@@ -33,6 +33,7 @@ pthread_cond_init (pthread_cond_t *cv, const pthread_condattr_t *attr)
|
|||||||
{
|
{
|
||||||
DBUG_ENTER("pthread_cond_init");
|
DBUG_ENTER("pthread_cond_init");
|
||||||
/* Initialize the count to 0 */
|
/* Initialize the count to 0 */
|
||||||
|
InitializeCriticalSection(&cv->waiters_count_lock);
|
||||||
cv->waiting = 0;
|
cv->waiting = 0;
|
||||||
|
|
||||||
/* Create an auto-reset and manual-reset event */
|
/* Create an auto-reset and manual-reset event */
|
||||||
|
@@ -522,7 +522,10 @@ static int test_reconnect(MYSQL *mysql)
|
|||||||
|
|
||||||
diag("Thread_id before kill: %lu", mysql_thread_id(mysql1));
|
diag("Thread_id before kill: %lu", mysql_thread_id(mysql1));
|
||||||
mysql_kill(mysql, mysql_thread_id(mysql1));
|
mysql_kill(mysql, mysql_thread_id(mysql1));
|
||||||
|
sleep(2);
|
||||||
|
|
||||||
|
rc= mysql_query(mysql1, "SELECT 1 FROM DUAL LIMIT 0");
|
||||||
|
FAIL_IF(rc == 0, "error expected");
|
||||||
rc= mysql_query(mysql1, "SELECT 1 FROM DUAL LIMIT 0");
|
rc= mysql_query(mysql1, "SELECT 1 FROM DUAL LIMIT 0");
|
||||||
check_mysql_rc(rc, mysql1);
|
check_mysql_rc(rc, mysql1);
|
||||||
diag("Thread_id after kill: %lu", mysql_thread_id(mysql1));
|
diag("Thread_id after kill: %lu", mysql_thread_id(mysql1));
|
||||||
@@ -581,6 +584,25 @@ int test_conc26(MYSQL *my)
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int test_connection_timeout(MYSQL *my)
|
||||||
|
{
|
||||||
|
unsigned int timeout= 5;
|
||||||
|
time_t start, elapsed;
|
||||||
|
MYSQL *mysql= mysql_init(NULL);
|
||||||
|
mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (unsigned int *)&timeout);
|
||||||
|
start= time(NULL);
|
||||||
|
if (mysql_real_connect(mysql, "192.168.1.101", "notexistinguser", "password", schema, port, NULL, CLIENT_REMEMBER_OPTIONS))
|
||||||
|
{
|
||||||
|
diag("Error expected - maybe you have to change hostname");
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
elapsed= time(NULL) - start;
|
||||||
|
diag("elapsed: %d", elapsed);
|
||||||
|
mysql_close(mysql);
|
||||||
|
FAIL_IF(elapsed +1 > timeout, "timeout ignored")
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
struct my_tests_st my_tests[] = {
|
struct my_tests_st my_tests[] = {
|
||||||
{"test_bug20023", test_bug20023, TEST_CONNECTION_NEW, 0, NULL, NULL},
|
{"test_bug20023", test_bug20023, TEST_CONNECTION_NEW, 0, NULL, NULL},
|
||||||
{"test_bug31669", test_bug31669, TEST_CONNECTION_NEW, 0, NULL, NULL},
|
{"test_bug31669", test_bug31669, TEST_CONNECTION_NEW, 0, NULL, NULL},
|
||||||
@@ -591,6 +613,7 @@ struct my_tests_st my_tests[] = {
|
|||||||
{"test_reconnect", test_reconnect, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},
|
{"test_reconnect", test_reconnect, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},
|
||||||
{"test_conc21", test_conc21, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},
|
{"test_conc21", test_conc21, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},
|
||||||
{"test_conc26", test_conc26, TEST_CONNECTION_NONE, 0, NULL, NULL},
|
{"test_conc26", test_conc26, TEST_CONNECTION_NONE, 0, NULL, NULL},
|
||||||
|
{"test_connection_timeout", test_connection_timeout, TEST_CONNECTION_NONE, 0, NULL, NULL},
|
||||||
{NULL, NULL, 0, 0, NULL, NULL}
|
{NULL, NULL, 0, 0, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -45,8 +45,9 @@ int thread_conc27(void);
|
|||||||
DWORD WINAPI thread_conc27(void);
|
DWORD WINAPI thread_conc27(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define THREAD_NUM 150
|
#define THREAD_NUM 1800
|
||||||
|
|
||||||
|
/* run this test as root and increase the number of handles (ulimit -n) */
|
||||||
static int test_conc_27(MYSQL *mysql)
|
static int test_conc_27(MYSQL *mysql)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -70,6 +71,9 @@ static int test_conc_27(MYSQL *mysql)
|
|||||||
rc= mysql_query(mysql, "INSERT INTO t_conc27 VALUES(0)");
|
rc= mysql_query(mysql, "INSERT INTO t_conc27 VALUES(0)");
|
||||||
check_mysql_rc(rc, mysql);
|
check_mysql_rc(rc, mysql);
|
||||||
|
|
||||||
|
rc= mysql_query(mysql, "SET GLOBAL max_connections=100000");
|
||||||
|
check_mysql_rc(rc, mysql);
|
||||||
|
|
||||||
pthread_mutex_init(&LOCK_test, NULL);
|
pthread_mutex_init(&LOCK_test, NULL);
|
||||||
for (i=0; i < THREAD_NUM; i++)
|
for (i=0; i < THREAD_NUM; i++)
|
||||||
{
|
{
|
||||||
@@ -121,7 +125,7 @@ DWORD WINAPI thread_conc27(void)
|
|||||||
if(!mysql_real_connect(mysql, hostname, username, password, schema,
|
if(!mysql_real_connect(mysql, hostname, username, password, schema,
|
||||||
port, socketname, 0))
|
port, socketname, 0))
|
||||||
{
|
{
|
||||||
diag("Error: %s", mysql_error(mysql));
|
diag(">Error: %s", mysql_error(mysql));
|
||||||
mysql_close(mysql);
|
mysql_close(mysql);
|
||||||
mysql_thread_end();
|
mysql_thread_end();
|
||||||
goto end;
|
goto end;
|
||||||
@@ -130,7 +134,6 @@ DWORD WINAPI thread_conc27(void)
|
|||||||
rc= mysql_query(mysql, "UPDATE t_conc27 SET a=a+1");
|
rc= mysql_query(mysql, "UPDATE t_conc27 SET a=a+1");
|
||||||
check_mysql_rc(rc, mysql);
|
check_mysql_rc(rc, mysql);
|
||||||
pthread_mutex_unlock(&LOCK_test);
|
pthread_mutex_unlock(&LOCK_test);
|
||||||
rc= mysql_query(mysql, "SELECT SLEEP(5)");
|
|
||||||
check_mysql_rc(rc, mysql);
|
check_mysql_rc(rc, mysql);
|
||||||
if (res= mysql_store_result(mysql))
|
if (res= mysql_store_result(mysql))
|
||||||
mysql_free_result(res);
|
mysql_free_result(res);
|
||||||
|
Reference in New Issue
Block a user