1
0
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:
holzboote@googlemail.com
2013-08-01 09:56:36 +02:00
parent 077afd8e10
commit b5db6c127f
4 changed files with 104 additions and 79 deletions

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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}
}; };

View File

@@ -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);