440 lines
14 KiB
C++
440 lines
14 KiB
C++
/*
|
|
Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "connection_handler_impl.h"
|
|
|
|
#include "channel_info.h" // Channel_info
|
|
#include "connection_handler_manager.h" // Connection_handler_manager
|
|
#include "mysqld.h" // max_connections
|
|
#include "mysqld_error.h" // ER_*
|
|
#include "mysqld_thd_manager.h" // Global_THD_manager
|
|
#include "sql_audit.h" // mysql_audit_release
|
|
#include "sql_class.h" // THD
|
|
#include "sql_connect.h" // close_connection
|
|
#include "sql_parse.h" // do_command
|
|
#include "sql_thd_internal_api.h" // thd_set_thread_stack
|
|
#include "log.h" // Error_log_throttle
|
|
|
|
|
|
// Initialize static members
|
|
ulong Per_thread_connection_handler::blocked_pthread_count= 0;
|
|
ulong Per_thread_connection_handler::slow_launch_threads = 0;
|
|
ulong Per_thread_connection_handler::max_blocked_pthreads= 0;
|
|
std::list<Channel_info*> *Per_thread_connection_handler
|
|
::waiting_channel_info_list= NULL;
|
|
mysql_mutex_t Per_thread_connection_handler::LOCK_thread_cache;
|
|
mysql_cond_t Per_thread_connection_handler::COND_thread_cache;
|
|
mysql_cond_t Per_thread_connection_handler::COND_flush_thread_cache;
|
|
|
|
// Error log throttle for the thread creation failure in add_connection method.
|
|
static
|
|
Error_log_throttle create_thd_err_log_throttle(Log_throttle
|
|
::LOG_THROTTLE_WINDOW_SIZE,
|
|
sql_print_error,
|
|
"Error log throttle: %10lu"
|
|
" 'Can't create thread to"
|
|
" handle new connection'"
|
|
" error(s) suppressed");
|
|
|
|
/*
|
|
Number of pthreads currently being woken up to handle new connections.
|
|
Protected by LOCK_thread_cache.
|
|
*/
|
|
static uint wake_pthread= 0;
|
|
/*
|
|
Set if we are trying to kill of pthreads in the thread cache.
|
|
Protected by LOCK_thread_cache.
|
|
*/
|
|
static uint kill_blocked_pthreads_flag= 0;
|
|
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
static PSI_mutex_key key_LOCK_thread_cache;
|
|
|
|
static PSI_mutex_info all_per_thread_mutexes[]=
|
|
{
|
|
{ &key_LOCK_thread_cache, "LOCK_thread_cache", PSI_FLAG_GLOBAL}
|
|
};
|
|
|
|
static PSI_cond_key key_COND_thread_cache;
|
|
static PSI_cond_key key_COND_flush_thread_cache;
|
|
|
|
static PSI_cond_info all_per_thread_conds[]=
|
|
{
|
|
{ &key_COND_thread_cache, "COND_thread_cache", PSI_FLAG_GLOBAL},
|
|
{ &key_COND_flush_thread_cache, "COND_flush_thread_cache", PSI_FLAG_GLOBAL}
|
|
};
|
|
#endif
|
|
|
|
|
|
void Per_thread_connection_handler::init()
|
|
{
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
int count= array_elements(all_per_thread_mutexes);
|
|
mysql_mutex_register("sql", all_per_thread_mutexes, count);
|
|
|
|
count= array_elements(all_per_thread_conds);
|
|
mysql_cond_register("sql", all_per_thread_conds, count);
|
|
#endif
|
|
|
|
mysql_mutex_init(key_LOCK_thread_cache, &LOCK_thread_cache,
|
|
MY_MUTEX_INIT_FAST);
|
|
mysql_cond_init(key_COND_thread_cache, &COND_thread_cache);
|
|
mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache);
|
|
waiting_channel_info_list= new (std::nothrow) std::list<Channel_info*>;
|
|
DBUG_ASSERT(waiting_channel_info_list != NULL);
|
|
}
|
|
|
|
|
|
void Per_thread_connection_handler::destroy()
|
|
{
|
|
if (waiting_channel_info_list != NULL)
|
|
{
|
|
delete waiting_channel_info_list;
|
|
waiting_channel_info_list= NULL;
|
|
mysql_mutex_destroy(&LOCK_thread_cache);
|
|
mysql_cond_destroy(&COND_thread_cache);
|
|
mysql_cond_destroy(&COND_flush_thread_cache);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Block the current pthread for reuse by new connections.
|
|
|
|
@retval NULL Too many pthreads blocked already or shutdown in progress.
|
|
@retval !NULL Pointer to Channel_info object representing the new connection
|
|
to be served by this pthread.
|
|
*/
|
|
|
|
Channel_info* Per_thread_connection_handler::block_until_new_connection()
|
|
{
|
|
Channel_info *new_conn= NULL;
|
|
mysql_mutex_lock(&LOCK_thread_cache);
|
|
if (blocked_pthread_count < max_blocked_pthreads &&
|
|
!kill_blocked_pthreads_flag)
|
|
{
|
|
/* Don't kill the pthread, just block it for reuse */
|
|
DBUG_PRINT("info", ("Blocking pthread for reuse"));
|
|
|
|
/*
|
|
mysys_var is bound to the physical thread,
|
|
so make sure mysys_var->dbug is reset to a clean state
|
|
before picking another session in the thread cache.
|
|
*/
|
|
DBUG_POP();
|
|
DBUG_ASSERT( ! _db_is_pushed_());
|
|
|
|
// Block pthread
|
|
blocked_pthread_count++;
|
|
while (!abort_loop && !wake_pthread && !kill_blocked_pthreads_flag)
|
|
mysql_cond_wait(&COND_thread_cache, &LOCK_thread_cache);
|
|
blocked_pthread_count--;
|
|
|
|
if (kill_blocked_pthreads_flag)
|
|
mysql_cond_signal(&COND_flush_thread_cache);
|
|
else if (wake_pthread)
|
|
{
|
|
wake_pthread--;
|
|
if (!waiting_channel_info_list->empty())
|
|
{
|
|
new_conn = waiting_channel_info_list->front();
|
|
waiting_channel_info_list->pop_front();
|
|
DBUG_PRINT("info", ("waiting_channel_info_list->pop %p", new_conn));
|
|
}
|
|
else
|
|
{
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
}
|
|
mysql_mutex_unlock(&LOCK_thread_cache);
|
|
return new_conn;
|
|
}
|
|
|
|
|
|
/**
|
|
Construct and initialize a THD object for a new connection.
|
|
|
|
@param channel_info Channel_info object representing the new connection.
|
|
Will be destroyed by this function.
|
|
|
|
@retval NULL Initialization failed.
|
|
@retval !NULL Pointer to new THD object for the new connection.
|
|
*/
|
|
|
|
static THD* init_new_thd(Channel_info *channel_info)
|
|
{
|
|
THD *thd= channel_info->create_thd();
|
|
if (thd == NULL)
|
|
{
|
|
channel_info->send_error_and_close_channel(ER_OUT_OF_RESOURCES, 0, false);
|
|
delete channel_info;
|
|
return NULL;
|
|
}
|
|
|
|
thd->set_new_thread_id();
|
|
|
|
thd->start_utime= thd->thr_create_utime= my_micro_time();
|
|
if (channel_info->get_prior_thr_create_utime() != 0)
|
|
{
|
|
/*
|
|
A pthread was created to handle this connection:
|
|
increment slow_launch_threads counter if it took more than
|
|
slow_launch_time seconds to create the pthread.
|
|
*/
|
|
ulong launch_time= (ulong) (thd->thr_create_utime -
|
|
channel_info->get_prior_thr_create_utime());
|
|
if (launch_time >= slow_launch_time * 1000000L)
|
|
Per_thread_connection_handler::slow_launch_threads++;
|
|
}
|
|
delete channel_info;
|
|
|
|
/*
|
|
handle_one_connection() is normally the only way a thread would
|
|
start and would always be on the very high end of the stack ,
|
|
therefore, the thread stack always starts at the address of the
|
|
first local variable of handle_one_connection, which is thd. We
|
|
need to know the start of the stack so that we could check for
|
|
stack overruns.
|
|
*/
|
|
thd_set_thread_stack(thd, (char*) &thd);
|
|
if (thd->store_globals())
|
|
{
|
|
close_connection(thd, ER_OUT_OF_RESOURCES);
|
|
thd->release_resources();
|
|
delete thd;
|
|
return NULL;
|
|
}
|
|
|
|
return thd;
|
|
}
|
|
|
|
|
|
/**
|
|
Thread handler for a connection
|
|
|
|
@param arg Connection object (Channel_info)
|
|
|
|
This function (normally) does the following:
|
|
- Initialize thread
|
|
- Initialize THD to be used with this thread
|
|
- Authenticate user
|
|
- Execute all queries sent on the connection
|
|
- Take connection down
|
|
- End thread / Handle next connection using thread from thread cache
|
|
*/
|
|
|
|
extern "C" void *handle_connection(void *arg)
|
|
{
|
|
Global_THD_manager *thd_manager= Global_THD_manager::get_instance();
|
|
Connection_handler_manager *handler_manager=
|
|
Connection_handler_manager::get_instance();
|
|
Channel_info* channel_info= static_cast<Channel_info*>(arg);
|
|
bool pthread_reused MY_ATTRIBUTE((unused))= false;
|
|
|
|
if (my_thread_init())
|
|
{
|
|
connection_errors_internal++;
|
|
channel_info->send_error_and_close_channel(ER_OUT_OF_RESOURCES, 0, false);
|
|
handler_manager->inc_aborted_connects();
|
|
Connection_handler_manager::dec_connection_count();
|
|
delete channel_info;
|
|
my_thread_exit(0);
|
|
return NULL;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
THD *thd= init_new_thd(channel_info);
|
|
if (thd == NULL)
|
|
{
|
|
connection_errors_internal++;
|
|
handler_manager->inc_aborted_connects();
|
|
Connection_handler_manager::dec_connection_count();
|
|
break; // We are out of resources, no sense in continuing.
|
|
}
|
|
|
|
#ifdef HAVE_PSI_THREAD_INTERFACE
|
|
if (pthread_reused)
|
|
{
|
|
/*
|
|
Reusing existing pthread:
|
|
Create new instrumentation for the new THD job,
|
|
and attach it to this running pthread.
|
|
*/
|
|
PSI_thread *psi= PSI_THREAD_CALL(new_thread)
|
|
(key_thread_one_connection, thd, thd->thread_id());
|
|
PSI_THREAD_CALL(set_thread_os_id)(psi);
|
|
PSI_THREAD_CALL(set_thread)(psi);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PSI_THREAD_INTERFACE
|
|
/* Find the instrumented thread */
|
|
PSI_thread *psi= PSI_THREAD_CALL(get_thread)();
|
|
/* Save it within THD, so it can be inspected */
|
|
thd->set_psi(psi);
|
|
#endif /* HAVE_PSI_THREAD_INTERFACE */
|
|
mysql_thread_set_psi_id(thd->thread_id());
|
|
mysql_thread_set_psi_THD(thd);
|
|
mysql_socket_set_thread_owner(
|
|
thd->get_protocol_classic()->get_vio()->mysql_socket);
|
|
|
|
thd_manager->add_thd(thd);
|
|
|
|
if (thd_prepare_connection(thd))
|
|
handler_manager->inc_aborted_connects();
|
|
else
|
|
{
|
|
while (thd_connection_alive(thd))
|
|
{
|
|
if (do_command(thd))
|
|
break;
|
|
}
|
|
end_connection(thd);
|
|
}
|
|
close_connection(thd, 0, false, false);
|
|
|
|
thd->get_stmt_da()->reset_diagnostics_area();
|
|
thd->release_resources();
|
|
|
|
// Clean up errors now, before possibly waiting for a new connection.
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
ERR_remove_thread_state(0);
|
|
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
|
|
|
|
thd_manager->remove_thd(thd);
|
|
Connection_handler_manager::dec_connection_count();
|
|
|
|
#ifdef HAVE_PSI_THREAD_INTERFACE
|
|
/*
|
|
Delete the instrumentation for the job that just completed.
|
|
*/
|
|
thd->set_psi(NULL);
|
|
PSI_THREAD_CALL(delete_current_thread)();
|
|
#endif /* HAVE_PSI_THREAD_INTERFACE */
|
|
|
|
delete thd;
|
|
|
|
if (abort_loop) // Server is shutting down so end the pthread.
|
|
break;
|
|
|
|
channel_info= Per_thread_connection_handler::block_until_new_connection();
|
|
if (channel_info == NULL)
|
|
break;
|
|
pthread_reused= true;
|
|
if (abort_loop)
|
|
{
|
|
// Close the channel and exit as server is undergoing shutdown.
|
|
channel_info->send_error_and_close_channel(ER_SERVER_SHUTDOWN, 0, false);
|
|
delete channel_info;
|
|
channel_info = NULL;
|
|
Connection_handler_manager::dec_connection_count();
|
|
break;
|
|
}
|
|
}
|
|
|
|
my_thread_end();
|
|
my_thread_exit(0);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void Per_thread_connection_handler::kill_blocked_pthreads()
|
|
{
|
|
mysql_mutex_lock(&LOCK_thread_cache);
|
|
kill_blocked_pthreads_flag++;
|
|
while (Per_thread_connection_handler::blocked_pthread_count)
|
|
{
|
|
mysql_cond_broadcast(&COND_thread_cache);
|
|
mysql_cond_wait(&COND_flush_thread_cache, &LOCK_thread_cache);
|
|
}
|
|
kill_blocked_pthreads_flag--;
|
|
mysql_mutex_unlock(&LOCK_thread_cache);
|
|
}
|
|
|
|
|
|
bool Per_thread_connection_handler::check_idle_thread_and_enqueue_connection(
|
|
Channel_info* channel_info)
|
|
{
|
|
bool res= true;
|
|
|
|
mysql_mutex_lock(&LOCK_thread_cache);
|
|
if (Per_thread_connection_handler::blocked_pthread_count > wake_pthread)
|
|
{
|
|
DBUG_PRINT("info",("waiting_channel_info_list->push %p", channel_info));
|
|
waiting_channel_info_list->push_back(channel_info);
|
|
wake_pthread++;
|
|
mysql_cond_signal(&COND_thread_cache);
|
|
res= false;
|
|
}
|
|
mysql_mutex_unlock(&LOCK_thread_cache);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
bool Per_thread_connection_handler::add_connection(Channel_info* channel_info)
|
|
{
|
|
int error= 0;
|
|
my_thread_handle id;
|
|
|
|
DBUG_ENTER("Per_thread_connection_handler::add_connection");
|
|
|
|
// Simulate thread creation for test case before we check thread cache
|
|
DBUG_EXECUTE_IF("fail_thread_create", error= 1; goto handle_error;);
|
|
|
|
if (!check_idle_thread_and_enqueue_connection(channel_info))
|
|
DBUG_RETURN(false);
|
|
|
|
/*
|
|
There are no idle threads avaliable to take up the new
|
|
connection. Create a new thread to handle the connection
|
|
*/
|
|
channel_info->set_prior_thr_create_utime();
|
|
error= mysql_thread_create(key_thread_one_connection, &id,
|
|
&connection_attrib,
|
|
handle_connection,
|
|
(void*) channel_info);
|
|
#ifndef DBUG_OFF
|
|
handle_error:
|
|
#endif // !DBUG_OFF
|
|
|
|
if (error)
|
|
{
|
|
connection_errors_internal++;
|
|
if (!create_thd_err_log_throttle.log())
|
|
sql_print_error("Can't create thread to handle new connection(errno= %d)",
|
|
error);
|
|
channel_info->send_error_and_close_channel(ER_CANT_CREATE_THREAD,
|
|
error, true);
|
|
Connection_handler_manager::dec_connection_count();
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
Global_THD_manager::get_instance()->inc_thread_created();
|
|
DBUG_PRINT("info",("Thread created"));
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
uint Per_thread_connection_handler::get_max_threads() const
|
|
{
|
|
return max_connections;
|
|
}
|