MDEV-21101 unexpected wait_timeout with pool-of-threads
Due to restricted size of the threadpool, execution of client queries can be delayed (queued) for a while. This delay was interpreted as client inactivity, and connection is closed, if client idle time + queue time exceeds wait_timeout. But users did not expect queue time to be included into wait_timeout. This patch changes the behavior. We don't close connection anymore, if there is some unread data present on connection, even if wait_timeout is exceeded. Unread data means that client was not idle, it sent a query, which we did not have time to process yet.
This commit is contained in:
parent
34f2be3b29
commit
71015d844e
@ -110,9 +110,7 @@ my_bool vio_peer_addr(Vio *vio, char *buf, uint16 *port, size_t buflen);
|
|||||||
/* Wait for an I/O event notification. */
|
/* Wait for an I/O event notification. */
|
||||||
int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout);
|
int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout);
|
||||||
my_bool vio_is_connected(Vio *vio);
|
my_bool vio_is_connected(Vio *vio);
|
||||||
#ifndef DBUG_OFF
|
|
||||||
ssize_t vio_pending(Vio *vio);
|
ssize_t vio_pending(Vio *vio);
|
||||||
#endif
|
|
||||||
/* Set timeout for a network operation. */
|
/* Set timeout for a network operation. */
|
||||||
extern int vio_timeout(Vio *vio, uint which, int timeout_sec);
|
extern int vio_timeout(Vio *vio, uint which, int timeout_sec);
|
||||||
extern void vio_set_wait_callback(void (*before_wait)(void),
|
extern void vio_set_wait_callback(void (*before_wait)(void),
|
||||||
|
1
mysql-test/main/mdev-21101.opt
Normal file
1
mysql-test/main/mdev-21101.opt
Normal file
@ -0,0 +1 @@
|
|||||||
|
--thread-handling=pool-of-threads
|
43
mysql-test/main/mdev-21101.result
Normal file
43
mysql-test/main/mdev-21101.result
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
SELECT
|
||||||
|
@@global.wait_timeout, @@global.thread_pool_max_threads, @@global.thread_pool_size,
|
||||||
|
@@global.thread_pool_oversubscribe, @@global.thread_pool_stall_limit
|
||||||
|
INTO
|
||||||
|
@_wait_timeout,@_thread_pool_max_threads,@_thread_pool_size,
|
||||||
|
@_thread_pool_oversubscribe,@_thread_pool_stall_limit;
|
||||||
|
SET @@global.wait_timeout=1,
|
||||||
|
@@global.thread_pool_max_threads=2,
|
||||||
|
@@global.thread_pool_size=1,
|
||||||
|
@@global.thread_pool_oversubscribe=1,
|
||||||
|
@@global.thread_pool_stall_limit=10;
|
||||||
|
connect c1, localhost, root,,;
|
||||||
|
connect c2, localhost, root,,;
|
||||||
|
connect c3, localhost, root,,;
|
||||||
|
connection c1;
|
||||||
|
select sleep(1.1);
|
||||||
|
connection c2;
|
||||||
|
select sleep(1.1);
|
||||||
|
connection c3;
|
||||||
|
select sleep(1.1);
|
||||||
|
connection default;
|
||||||
|
select sleep(1.1);
|
||||||
|
connection c1;
|
||||||
|
sleep(1.1)
|
||||||
|
0
|
||||||
|
connection c2;
|
||||||
|
sleep(1.1)
|
||||||
|
0
|
||||||
|
connection c3;
|
||||||
|
sleep(1.1)
|
||||||
|
0
|
||||||
|
connection default;
|
||||||
|
sleep(1.1)
|
||||||
|
0
|
||||||
|
disconnect c1;
|
||||||
|
disconnect c2;
|
||||||
|
disconnect c3;
|
||||||
|
connection default;
|
||||||
|
SET @@global.wait_timeout=@_wait_timeout,
|
||||||
|
@@global.thread_pool_max_threads=@_thread_pool_max_threads,
|
||||||
|
@@global.thread_pool_size=@_thread_pool_size,
|
||||||
|
@@global.thread_pool_oversubscribe=@_thread_pool_oversubscribe,
|
||||||
|
@@global.thread_pool_stall_limit=@_thread_pool_stall_limit;
|
53
mysql-test/main/mdev-21101.test
Normal file
53
mysql-test/main/mdev-21101.test
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Test that wait_timeout does not cause connection to be closed, when connection is delayed due to
|
||||||
|
# threadpool internal problems, e.g misconfiguration - too few threads and queueing.
|
||||||
|
# So if client did not cause wait_timeout, do not report it either.
|
||||||
|
# See MDEV-21101 for details.
|
||||||
|
|
||||||
|
# Intentionally misconfigure threadpool to have at most 1 or 2 threads (
|
||||||
|
# depends on the implementation). Use minimal wait_timeout, do some slow queries from
|
||||||
|
# different connections simultaneously, to force queueing occurs.
|
||||||
|
# Verify connections are intact, even if queueing time exceeds wait_timeout
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
@@global.wait_timeout, @@global.thread_pool_max_threads, @@global.thread_pool_size,
|
||||||
|
@@global.thread_pool_oversubscribe, @@global.thread_pool_stall_limit
|
||||||
|
INTO
|
||||||
|
@_wait_timeout,@_thread_pool_max_threads,@_thread_pool_size,
|
||||||
|
@_thread_pool_oversubscribe,@_thread_pool_stall_limit;
|
||||||
|
|
||||||
|
SET @@global.wait_timeout=1,
|
||||||
|
@@global.thread_pool_max_threads=2,
|
||||||
|
@@global.thread_pool_size=1,
|
||||||
|
@@global.thread_pool_oversubscribe=1,
|
||||||
|
@@global.thread_pool_stall_limit=10;
|
||||||
|
|
||||||
|
--connect (c1, localhost, root,,)
|
||||||
|
--connect (c2, localhost, root,,)
|
||||||
|
--connect (c3, localhost, root,,)
|
||||||
|
--connection c1
|
||||||
|
--send select sleep(1.1)
|
||||||
|
--connection c2
|
||||||
|
--send select sleep(1.1)
|
||||||
|
--connection c3
|
||||||
|
--send select sleep(1.1)
|
||||||
|
--connection default
|
||||||
|
--send select sleep(1.1)
|
||||||
|
--connection c1
|
||||||
|
--reap
|
||||||
|
--connection c2
|
||||||
|
--reap
|
||||||
|
--connection c3
|
||||||
|
--reap
|
||||||
|
--connection default
|
||||||
|
--reap
|
||||||
|
--disconnect c1
|
||||||
|
--disconnect c2
|
||||||
|
--disconnect c3
|
||||||
|
--connection default
|
||||||
|
|
||||||
|
SET @@global.wait_timeout=@_wait_timeout,
|
||||||
|
@@global.thread_pool_max_threads=@_thread_pool_max_threads,
|
||||||
|
@@global.thread_pool_size=@_thread_pool_size,
|
||||||
|
@@global.thread_pool_oversubscribe=@_thread_pool_oversubscribe,
|
||||||
|
@@global.thread_pool_stall_limit=@_thread_pool_stall_limit;
|
||||||
|
|
@ -3609,6 +3609,12 @@ static bool fix_tp_min_threads(sys_var *, THD *, enum_var_type)
|
|||||||
|
|
||||||
static bool check_threadpool_size(sys_var *self, THD *thd, set_var *var)
|
static bool check_threadpool_size(sys_var *self, THD *thd, set_var *var)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (threadpool_mode != TP_MODE_GENERIC)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
ulonglong v= var->save_result.ulonglong_value;
|
ulonglong v= var->save_result.ulonglong_value;
|
||||||
if (v > threadpool_max_size)
|
if (v > threadpool_max_size)
|
||||||
{
|
{
|
||||||
|
@ -74,6 +74,7 @@ enum TP_STATE
|
|||||||
{
|
{
|
||||||
TP_STATE_IDLE,
|
TP_STATE_IDLE,
|
||||||
TP_STATE_RUNNING,
|
TP_STATE_RUNNING,
|
||||||
|
TP_STATE_PENDING
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -468,11 +468,25 @@ void tp_timeout_handler(TP_connection *c)
|
|||||||
{
|
{
|
||||||
if (c->state != TP_STATE_IDLE)
|
if (c->state != TP_STATE_IDLE)
|
||||||
return;
|
return;
|
||||||
THD *thd=c->thd;
|
THD *thd= c->thd;
|
||||||
mysql_mutex_lock(&thd->LOCK_thd_kill);
|
mysql_mutex_lock(&thd->LOCK_thd_kill);
|
||||||
|
Vio *vio= thd->net.vio;
|
||||||
|
if (vio && (vio_pending(vio) > 0 || vio->has_data(vio)) &&
|
||||||
|
c->state == TP_STATE_IDLE)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
There is some data on that connection, i.e
|
||||||
|
i.e there was no inactivity timeout.
|
||||||
|
Don't kill.
|
||||||
|
*/
|
||||||
|
c->state= TP_STATE_PENDING;
|
||||||
|
}
|
||||||
|
else if (c->state == TP_STATE_IDLE)
|
||||||
|
{
|
||||||
thd->set_killed_no_mutex(KILL_WAIT_TIMEOUT);
|
thd->set_killed_no_mutex(KILL_WAIT_TIMEOUT);
|
||||||
c->priority= TP_PRIORITY_HIGH;
|
c->priority= TP_PRIORITY_HIGH;
|
||||||
post_kill_notification(thd);
|
post_kill_notification(thd);
|
||||||
|
}
|
||||||
mysql_mutex_unlock(&thd->LOCK_thd_kill);
|
mysql_mutex_unlock(&thd->LOCK_thd_kill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,11 +591,8 @@ static void timeout_check(pool_timer_t *timer)
|
|||||||
THD *thd;
|
THD *thd;
|
||||||
while ((thd=it++))
|
while ((thd=it++))
|
||||||
{
|
{
|
||||||
if (thd->net.reading_or_writing != 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TP_connection_generic *connection= (TP_connection_generic *)thd->event_scheduler.data;
|
TP_connection_generic *connection= (TP_connection_generic *)thd->event_scheduler.data;
|
||||||
if (!connection)
|
if (!connection || connection->state != TP_STATE_IDLE)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Connection does not have scheduler data. This happens for example
|
Connection does not have scheduler data. This happens for example
|
||||||
|
@ -33,6 +33,7 @@ my_bool vio_is_connected_pipe(Vio *vio);
|
|||||||
int vio_close_pipe(Vio * vio);
|
int vio_close_pipe(Vio * vio);
|
||||||
int cancel_io(HANDLE handle, DWORD thread_id);
|
int cancel_io(HANDLE handle, DWORD thread_id);
|
||||||
int vio_shutdown_pipe(Vio *vio,int how);
|
int vio_shutdown_pipe(Vio *vio,int how);
|
||||||
|
uint vio_pending_pipe(Vio* vio);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_SMEM
|
#ifdef HAVE_SMEM
|
||||||
|
@ -141,5 +141,11 @@ int vio_close_pipe(Vio *vio)
|
|||||||
DBUG_RETURN(ret);
|
DBUG_RETURN(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* return number of bytes readable from pipe.*/
|
||||||
|
uint vio_pending_pipe(Vio *vio)
|
||||||
|
{
|
||||||
|
DWORD bytes;
|
||||||
|
return PeekNamedPipe(vio->hPipe, NULL, 0, NULL, &bytes, NULL) ? bytes : 0;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1214,7 +1214,6 @@ my_bool vio_is_connected(Vio *vio)
|
|||||||
DBUG_RETURN(bytes ? TRUE : FALSE);
|
DBUG_RETURN(bytes ? TRUE : FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef DBUG_OFF
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Number of bytes in the read or socket buffer
|
Number of bytes in the read or socket buffer
|
||||||
@ -1233,22 +1232,34 @@ ssize_t vio_pending(Vio *vio)
|
|||||||
return vio->read_end - vio->read_pos;
|
return vio->read_end - vio->read_pos;
|
||||||
|
|
||||||
/* Skip non-socket based transport types. */
|
/* Skip non-socket based transport types. */
|
||||||
if (vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET)
|
switch (vio->type)
|
||||||
{
|
{
|
||||||
|
case VIO_TYPE_TCPIP:
|
||||||
|
/* fallthrough */
|
||||||
|
case VIO_TYPE_SOCKET:
|
||||||
/* Obtain number of readable bytes in the socket buffer. */
|
/* Obtain number of readable bytes in the socket buffer. */
|
||||||
if (socket_peek_read(vio, &bytes))
|
if (socket_peek_read(vio, &bytes))
|
||||||
return -1;
|
return -1;
|
||||||
|
return bytes;
|
||||||
|
|
||||||
|
case VIO_TYPE_SSL:
|
||||||
|
bytes= (uint) SSL_pending(vio->ssl_arg);
|
||||||
|
if (bytes)
|
||||||
|
return bytes;
|
||||||
|
if (socket_peek_read(vio, &bytes))
|
||||||
|
return -1;
|
||||||
|
return bytes;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
case VIO_TYPE_NAMEDPIPE:
|
||||||
|
bytes= vio_pending_pipe(vio);
|
||||||
|
return bytes;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
SSL not checked due to a yaSSL bug in SSL_pending that
|
|
||||||
causes it to attempt to read from the socket.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return (ssize_t) bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* DBUG_OFF */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if the error code, returned by vio_getnameinfo(), means it was the
|
Checks if the error code, returned by vio_getnameinfo(), means it was the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user