mariadb/sql/scheduler.cc
Michael Widenius 22e793639a Added new options to KILL. New syntax is KILL [HARD|SOFT] [CONNECTION|QUERY] [ID | USER user_name]
- If USER is given, all threads for that user is signaled
- If SOFT is used then the KILL will not be sent to the handler. This can be used to not interrupt critical things in the handler like 'REPAIR'.

Internally added more kill signals. This gives us more information of why a query/connection was killed.
- KILL_SERVER is used when server is going down. In this case the users gets ER_SHUTDOWN as the reason connection was killed.
- Changed signals to number in correct order, which makes it easier to test how the signal should affect the code.
- New error message ER_CONNECTION_KILLED if connection was killed by 'KILL CONNECTION'. Before we got error ER_SHUTDOWN.

Changed names of not used parameters KILL_QUERY & KILL_CONNCTION to mysql_kill() to not conflict with defines in the server


include/mysql.h.pp:
  Updated file
include/mysql_com.h:
  Changed names of not used parameters KILL_QUERY & KILL_CONNCTION to mysql_kill() to not conflict with defines in the server
mysql-test/r/kill.result:
  Added test of KILL USER
mysql-test/suite/rpl/r/rpl_stm_000001.result:
  Updated error code
mysql-test/suite/rpl/t/rpl_stm_000001.test:
  Updated error codes
mysql-test/t/flush_read_lock_kill.test:
  Updated error codes
mysql-test/t/kill.test:
  Added test of KILL USER
plugin/handler_socket/handlersocket/database.cpp:
  Removed THD:: from KILL
sql/debug_sync.cc:
  Removed THD:: from KILL
sql/event_scheduler.cc:
  Removed THD:: from KILL
sql/filesort.cc:
  Removed THD:: from KILL
sql/ha_ndbcluster_binlog.cc:
  Removed THD:: from KILL
sql/handler.cc:
  Removed THD:: from KILL
  Simplify code.
sql/lex.h:
  Added new keywords HARD | SOFT
sql/log.cc:
  Removed THD:: from KILL
  Added testing of new error ER_CONNECTION_KILLED
sql/log_event.cc:
  Removed THD:: from KILL
  Added testing of new error ER_CONNECTION_KILLED
sql/mysql_priv.h:
  Added new prototypes
sql/mysqld.cc:
  Removed THD:: from KILL
  Use KILL_SERVER_HARD signal on shutdown.
sql/scheduler.cc:
  Removed THD:: from KILL
  Simplify test if connection should be killed
sql/share/errmsg.txt:
  New error message ER_CONNECTION_KILLED
sql/slave.cc:
  Removed THD:: from KILL
sql/sp_head.cc:
  Removed THD:: from KILL
sql/sql_base.cc:
  Removed THD:: from KILL
sql/sql_cache.cc:
  Removed THD:: from KILL
sql/sql_class.cc:
  Removed THD:: from KILL
  Added killed_errno()
  Only signal kill to storage engine if HARD bit is set.
sql/sql_class.h:
  Move KILL options out from THD to make them easier to use in sql_yacc.yy
sql/sql_connect.cc:
  Removed THD:: from KILL
sql/sql_delete.cc:
  Removed THD:: from KILL
sql/sql_error.cc:
  Removed THD:: from KILL
sql/sql_insert.cc:
  Removed THD:: from KILL
  Simplifed testing if thread is killed.
sql/sql_lex.h:
  Added kill options to st_lex
sql/sql_load.cc:
  Removed THD:: from KILL
sql/sql_parse.cc:
  Added kill options to st_lex
  Simplifed and optimzed testing of thd->killed at end of query
  Added support for KILL USER
  Extended sql_kill() to allow use of more kill signals.
sql/sql_repl.cc:
  Removed THD:: from KILL
sql/sql_show.cc:
  Removed THD:: from KILL
  Simplied testing if query/connection was killed
sql/sql_table.cc:
  Removed THD:: from KILL
sql/sql_update.cc:
  Removed THD:: from KILL
sql/sql_yacc.yy:
  Added support for new KILL syntax: KILL [HARD|SOFT] [CONNECTION|QUERY] [ID | USER user_name]
storage/archive/ha_archive.cc:
  Simplify compilation
storage/maria/ha_maria.cc:
  Removed THD:: from KILL
2011-09-23 01:13:38 +03:00

756 lines
20 KiB
C++

/* Copyright (C) 2007 MySQL AB
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
Implementation for the thread scheduler
*/
#ifdef USE_PRAGMA_INTERFACE
#pragma implementation
#endif
#include <mysql_priv.h>
#if MYSQL_VERSION_ID >= 60000
#include "sql_audit.h"
#endif
/*
'Dummy' functions to be used when we don't need any handling for a scheduler
event
*/
static bool init_dummy(void) {return 0;}
static void post_kill_dummy(THD *thd) {}
static void end_dummy(void) {}
static bool end_thread_dummy(THD *thd, bool cache_thread) { return 0; }
/*
Initialize default scheduler with dummy functions so that setup functions
only need to declare those that are relvant for their usage
*/
scheduler_functions::scheduler_functions()
:init(init_dummy),
init_new_connection_thread(init_new_connection_handler_thread),
add_connection(0), // Must be defined
post_kill_notification(post_kill_dummy),
end_thread(end_thread_dummy), end(end_dummy)
{}
/*
End connection, in case when we are using 'no-threads'
*/
static bool no_threads_end(THD *thd, bool put_in_cache)
{
unlink_thd(thd);
pthread_mutex_unlock(&LOCK_thread_count);
return 1; // Abort handle_one_connection
}
/*
Initailize scheduler for --thread-handling=no-threads
*/
void one_thread_scheduler(scheduler_functions *func)
{
func->max_threads= 1;
max_connections= 1;
func->max_connections= &max_connections;
func->connection_count= &connection_count;
#ifndef EMBEDDED_LIBRARY
func->add_connection= handle_connection_in_main_thread;
#endif
func->init_new_connection_thread= init_dummy;
func->end_thread= no_threads_end;
}
/*
Initialize scheduler for --thread-handling=one-thread-per-connection
*/
#ifndef EMBEDDED_LIBRARY
void one_thread_per_connection_scheduler(scheduler_functions *func,
ulong *arg_max_connections,
uint *arg_connection_count)
{
func->max_threads= *arg_max_connections + 1;
func->max_connections= arg_max_connections;
func->connection_count= arg_connection_count;
func->add_connection= create_thread_to_handle_connection;
func->end_thread= one_thread_per_connection_end;
}
#endif /* EMBEDDED_LIBRARY */
#if defined(HAVE_LIBEVENT) && HAVE_POOL_OF_THREADS == 1
#include "event.h"
static struct event_base *base;
static uint created_threads, killed_threads;
static bool kill_pool_threads;
static struct event thd_add_event;
static struct event thd_kill_event;
static pthread_mutex_t LOCK_thd_add; /* protects thds_need_adding */
static LIST *thds_need_adding; /* list of thds to add to libevent queue */
static int thd_add_pair[2]; /* pipe to signal add a connection to libevent*/
static int thd_kill_pair[2]; /* pipe to signal kill a connection in libevent */
/*
LOCK_event_loop protects the non-thread safe libevent calls (event_add and
event_del) and thds_need_processing and thds_waiting_for_io.
*/
static pthread_mutex_t LOCK_event_loop;
static LIST *thds_need_processing; /* list of thds that needs some processing */
static LIST *thds_waiting_for_io; /* list of thds with added events */
pthread_handler_t libevent_thread_proc(void *arg);
static void libevent_end();
static bool libevent_needs_immediate_processing(THD *thd);
static void libevent_connection_close(THD *thd);
static bool libevent_should_close_connection(THD* thd);
static void libevent_thd_add(THD* thd);
void libevent_io_callback(int Fd, short Operation, void *ctx);
void libevent_add_thd_callback(int Fd, short Operation, void *ctx);
void libevent_kill_thd_callback(int Fd, short Operation, void *ctx);
/*
Create a pipe and set to non-blocking.
Returns TRUE if there is an error.
*/
static bool init_socketpair(int sock_pair[])
{
sock_pair[0]= sock_pair[1]= -1;
return (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) < 0 ||
evutil_make_socket_nonblocking(sock_pair[0]) == -1 ||
evutil_make_socket_nonblocking(sock_pair[1]) == -1);
}
static void close_socketpair(int sock_pair[])
{
if (sock_pair[0] != -1)
EVUTIL_CLOSESOCKET(sock_pair[0]);
if (sock_pair[1] != -1)
EVUTIL_CLOSESOCKET(sock_pair[1]);
}
/*
thd_scheduler keeps the link between THD and events.
It's embedded in the THD class.
*/
thd_scheduler::thd_scheduler()
: logged_in(FALSE), io_event(NULL), thread_attached(FALSE)
{
#ifndef DBUG_OFF
dbug_explain[0]= '\0';
set_explain= FALSE;
#endif
}
thd_scheduler::~thd_scheduler()
{
my_free(io_event, MYF(MY_ALLOW_ZERO_PTR));
}
bool thd_scheduler::init(THD *parent_thd)
{
io_event=
(struct event*)my_malloc(sizeof(*io_event),MYF(MY_ZEROFILL|MY_WME));
if (!io_event)
{
sql_print_error("Memory allocation error in thd_scheduler::init\n");
return TRUE;
}
event_set(io_event, (int)parent_thd->net.vio->sd, EV_READ,
libevent_io_callback, (void*)parent_thd);
list.data= parent_thd;
return FALSE;
}
/*
Attach/associate the connection with the OS thread, for command processing.
*/
bool thd_scheduler::thread_attach()
{
DBUG_ASSERT(!thread_attached);
THD* thd = (THD*)list.data;
if (libevent_should_close_connection(thd) ||
setup_connection_thread_globals(thd))
{
return TRUE;
}
my_errno= 0;
thd->mysys_var->abort= 0;
thread_attached= TRUE;
#ifndef DBUG_OFF
/*
When we attach the thread for a connection for the first time,
we know that there is no session value set yet. Thus
the initial setting of set_explain to FALSE is OK.
*/
if (set_explain)
DBUG_SET(dbug_explain);
#endif
return FALSE;
}
/*
Detach/disassociate the connection with the OS thread.
*/
void thd_scheduler::thread_detach()
{
if (thread_attached)
{
THD* thd = (THD*)list.data;
thd->reset_globals();
thread_attached= FALSE;
#ifndef DBUG_OFF
/*
If during the session @@session.dbug was assigned, the
dbug options/state has been pushed. Check if this is the
case, to be able to restore the state when we attach this
logical connection to a physical thread.
*/
if (_db_is_pushed_())
{
set_explain= TRUE;
if (DBUG_EXPLAIN(dbug_explain, sizeof(dbug_explain)))
sql_print_error("thd_scheduler: DBUG_EXPLAIN buffer is too small");
}
/* DBUG_POP() is a no-op in case there is no session state */
DBUG_POP();
#endif
}
}
/**
Create all threads for the thread pool
NOTES
After threads are created we wait until all threads has signaled that
they have started before we return
RETURN
0 ok
1 We got an error creating the thread pool
In this case we will abort all created threads
*/
static bool libevent_init(void)
{
uint i;
DBUG_ENTER("libevent_init");
base= (struct event_base *) event_init();
created_threads= 0;
killed_threads= 0;
kill_pool_threads= FALSE;
pthread_mutex_init(&LOCK_event_loop, MY_MUTEX_INIT_FAST);
pthread_mutex_init(&LOCK_thd_add, MY_MUTEX_INIT_FAST);
/* set up sockets used to add new thds to the event pool */
if (init_socketpair(thd_add_pair))
{
sql_print_error("init_socketpair(thd_add_spair) error in libevent_init");
DBUG_RETURN(1);
}
/* set up sockets used to kill thds in the event queue */
if (init_socketpair(thd_kill_pair))
{
sql_print_error("init_socketpair(thd_kill_pair) error in libevent_init");
close_socketpair(thd_add_pair);
DBUG_RETURN(1);
}
event_set(&thd_add_event, thd_add_pair[0], EV_READ|EV_PERSIST,
libevent_add_thd_callback, NULL);
event_set(&thd_kill_event, thd_kill_pair[0], EV_READ|EV_PERSIST,
libevent_kill_thd_callback, NULL);
if (event_add(&thd_add_event, NULL) || event_add(&thd_kill_event, NULL))
{
sql_print_error("thd_add_event event_add error in libevent_init");
libevent_end();
DBUG_RETURN(1);
}
/* Set up the thread pool */
created_threads= killed_threads= 0;
pthread_mutex_lock(&LOCK_thread_count);
for (i= 0; i < thread_pool_size; i++)
{
pthread_t thread;
int error;
if ((error= pthread_create(&thread, &connection_attrib,
libevent_thread_proc, 0)))
{
sql_print_error("Can't create completion port thread (error %d)",
error);
pthread_mutex_unlock(&LOCK_thread_count);
libevent_end(); // Cleanup
DBUG_RETURN(TRUE);
}
}
/* Wait until all threads are created */
while (created_threads != thread_pool_size)
pthread_cond_wait(&COND_thread_count,&LOCK_thread_count);
pthread_mutex_unlock(&LOCK_thread_count);
DBUG_PRINT("info", ("%u threads created", (uint) thread_pool_size));
DBUG_RETURN(FALSE);
}
/*
This is called when data is ready on the socket.
NOTES
This is only called by the thread that owns LOCK_event_loop.
We add the thd that got the data to thds_need_processing, and
cause the libevent event_loop() to terminate. Then this same thread will
return from event_loop and pick the thd value back up for processing.
*/
void libevent_io_callback(int, short, void *ctx)
{
safe_mutex_assert_owner(&LOCK_event_loop);
THD *thd= (THD*)ctx;
thds_waiting_for_io= list_delete(thds_waiting_for_io, &thd->event_scheduler.list);
thds_need_processing= list_add(thds_need_processing, &thd->event_scheduler.list);
}
/*
This is called when we have a thread we want to be killed.
NOTES
This is only called by the thread that owns LOCK_event_loop.
*/
void libevent_kill_thd_callback(int Fd, short, void*)
{
safe_mutex_assert_owner(&LOCK_event_loop);
/* clear the pending events */
char c;
while (recv(Fd, &c, sizeof(c), 0) == sizeof(c))
{}
LIST* list= thds_waiting_for_io;
while (list)
{
THD *thd= (THD*)list->data;
list= list_rest(list);
if ((int) thd->killed >= (int) KILL_CONNECTION)
{
/*
Delete from libevent and add to the processing queue.
*/
event_del(thd->event_scheduler.io_event);
thds_waiting_for_io= list_delete(thds_waiting_for_io,
&thd->event_scheduler.list);
thds_need_processing= list_add(thds_need_processing,
&thd->event_scheduler.list);
}
}
}
/*
This is used to add connections to the pool. This callback is invoked from
the libevent event_loop() call whenever the thd_add_pair[1] has a byte
written to it.
NOTES
This is only called by the thread that owns LOCK_event_loop.
*/
void libevent_add_thd_callback(int Fd, short, void *)
{
safe_mutex_assert_owner(&LOCK_event_loop);
/* clear the pending events */
char c;
while (recv(Fd, &c, sizeof(c), 0) == sizeof(c))
{}
pthread_mutex_lock(&LOCK_thd_add);
while (thds_need_adding)
{
/* pop the first thd off the list */
THD* thd= (THD*)thds_need_adding->data;
thds_need_adding= list_delete(thds_need_adding, thds_need_adding);
pthread_mutex_unlock(&LOCK_thd_add);
if (!thd->event_scheduler.logged_in || libevent_should_close_connection(thd))
{
/*
Add thd to thds_need_processing list. If it needs closing we'll close
it outside of event_loop().
*/
thds_need_processing= list_add(thds_need_processing,
&thd->event_scheduler.list);
}
else
{
/* Add to libevent */
if (event_add(thd->event_scheduler.io_event, NULL))
{
sql_print_error("event_add error in libevent_add_thd_callback");
libevent_connection_close(thd);
}
else
{
thds_waiting_for_io= list_add(thds_waiting_for_io,
&thd->event_scheduler.list);
}
}
pthread_mutex_lock(&LOCK_thd_add);
}
pthread_mutex_unlock(&LOCK_thd_add);
}
/**
Notify the thread pool about a new connection
NOTES
LOCK_thread_count is locked on entry. This function MUST unlock it!
*/
static void libevent_add_connection(THD *thd)
{
DBUG_ENTER("libevent_add_connection");
DBUG_PRINT("enter", ("thd: %p thread_id: %lu",
thd, thd->thread_id));
if (thd->event_scheduler.init(thd))
{
sql_print_error("Scheduler init error in libevent_add_new_connection");
pthread_mutex_unlock(&LOCK_thread_count);
libevent_connection_close(thd);
DBUG_VOID_RETURN;
}
threads.append(thd);
libevent_thd_add(thd);
pthread_mutex_unlock(&LOCK_thread_count);
DBUG_VOID_RETURN;
}
/**
@brief Signal a waiting connection it's time to die.
@details This function will signal libevent the THD should be killed.
Either the global LOCK_thd_count or the THD's LOCK_thd_data must be locked
upon entry.
@param[in] thd The connection to kill
*/
static void libevent_post_kill_notification(THD *)
{
/*
Note, we just wake up libevent with an event that a THD should be killed,
It will search its list of thds for thd->killed == KILL_CONNECTION to
find the THDs it should kill.
So we don't actually tell it which one and we don't actually use the
THD being passed to us, but that's just a design detail that could change
later.
*/
char c= 0;
send(thd_kill_pair[1], &c, sizeof(c), 0);
}
/*
Close and delete a connection.
*/
static void libevent_connection_close(THD *thd)
{
DBUG_ENTER("libevent_connection_close");
DBUG_PRINT("enter", ("thd: %p", thd));
thd->killed= KILL_CONNECTION; // Avoid error messages
if (thd->net.vio->sd >= 0) // not already closed
{
end_connection(thd);
close_connection(thd, 0, 1);
}
thd->event_scheduler.thread_detach();
unlink_thd(thd); /* locks LOCK_thread_count and deletes thd */
pthread_mutex_unlock(&LOCK_thread_count);
DBUG_VOID_RETURN;
}
/*
Returns true if we should close and delete a THD connection.
*/
static bool libevent_should_close_connection(THD* thd)
{
return (thd->net.error ||
thd->net.vio == 0 ||
(int) thd->killed >= (int) KILL_CONNECTION);
}
/*
libevent_thread_proc is the outer loop of each thread in the thread pool.
These procs only return/terminate on shutdown (kill_pool_threads == true).
*/
pthread_handler_t libevent_thread_proc(void *arg)
{
if (init_new_connection_handler_thread())
{
my_thread_global_end();
sql_print_error("libevent_thread_proc: my_thread_init() failed");
exit(1);
}
DBUG_ENTER("libevent_thread_proc");
/*
Signal libevent_init() when all threads has been created and are ready to
receive events.
*/
(void) pthread_mutex_lock(&LOCK_thread_count);
created_threads++;
thread_created++;
if (created_threads == thread_pool_size)
(void) pthread_cond_signal(&COND_thread_count);
(void) pthread_mutex_unlock(&LOCK_thread_count);
for (;;)
{
THD *thd= NULL;
(void) pthread_mutex_lock(&LOCK_event_loop);
/* get thd(s) to process */
while (!thds_need_processing)
{
if (kill_pool_threads)
{
/* the flag that we should die has been set */
(void) pthread_mutex_unlock(&LOCK_event_loop);
goto thread_exit;
}
event_loop(EVLOOP_ONCE);
}
/* pop the first thd off the list */
thd= (THD*)thds_need_processing->data;
thds_need_processing= list_delete(thds_need_processing,
thds_need_processing);
(void) pthread_mutex_unlock(&LOCK_event_loop);
/* now we process the connection (thd) */
/* set up the thd<->thread links. */
thd->thread_stack= (char*) &thd;
if (thd->event_scheduler.thread_attach())
{
libevent_connection_close(thd);
continue;
}
/* is the connection logged in yet? */
if (!thd->event_scheduler.logged_in)
{
DBUG_PRINT("info", ("init new connection. sd: %d",
thd->net.vio->sd));
lex_start(thd);
if (login_connection(thd))
{
/* Failed to log in */
libevent_connection_close(thd);
continue;
}
else
{
/* login successful */
#if MYSQL_VERSION_ID >= 60000
MYSQL_CONNECTION_START(thd->thread_id, thd->security_ctx->priv_user,
(char *) thd->security_ctx->host_or_ip);
#endif
thd->event_scheduler.logged_in= TRUE;
prepare_new_connection_state(thd);
if (!libevent_needs_immediate_processing(thd))
continue; /* New connection is now waiting for data in libevent*/
}
}
do
{
/* Process a query */
if (do_command(thd))
{
libevent_connection_close(thd);
break;
}
} while (libevent_needs_immediate_processing(thd));
}
thread_exit:
DBUG_PRINT("exit", ("ending thread"));
(void) pthread_mutex_lock(&LOCK_thread_count);
killed_threads++;
pthread_cond_broadcast(&COND_thread_count);
(void) pthread_mutex_unlock(&LOCK_thread_count);
my_thread_end();
pthread_exit(0);
DBUG_RETURN(0); /* purify: deadcode */
}
/*
Returns TRUE if the connection needs immediate processing and FALSE if
instead it's queued for libevent processing or closed,
*/
static bool libevent_needs_immediate_processing(THD *thd)
{
if (libevent_should_close_connection(thd))
{
libevent_connection_close(thd);
return FALSE;
}
/*
If more data in the socket buffer, return TRUE to process another command.
Note: we cannot add for event processing because the whole request might
already be buffered and we wouldn't receive an event.
*/
if (vio_pending(thd->net.vio) > 0)
return TRUE;
thd->event_scheduler.thread_detach();
libevent_thd_add(thd);
return FALSE;
}
/*
Adds a THD to queued for libevent processing.
This call does not actually register the event with libevent.
Instead, it places the THD onto a queue and signals libevent by writing
a byte into thd_add_pair, which will cause our libevent_add_thd_callback to
be invoked which will find the THD on the queue and add it to libevent.
*/
static void libevent_thd_add(THD* thd)
{
char c= 0;
/* release any audit resources, this thd is going to sleep */
#if MYSQL_VERSION_ID >= 60000
mysql_audit_release(thd);
#endif
pthread_mutex_lock(&LOCK_thd_add);
/* queue for libevent */
thds_need_adding= list_add(thds_need_adding, &thd->event_scheduler.list);
/* notify libevent */
send(thd_add_pair[1], &c, sizeof(c), 0);
pthread_mutex_unlock(&LOCK_thd_add);
}
/**
Wait until all pool threads have been deleted for clean shutdown
*/
static void libevent_end()
{
DBUG_ENTER("libevent_end");
DBUG_PRINT("enter", ("created_threads: %d killed_threads: %u",
created_threads, killed_threads));
/*
check if initialized. This may not be the case if get an error at
startup
*/
if (!base)
DBUG_VOID_RETURN;
(void) pthread_mutex_lock(&LOCK_thread_count);
kill_pool_threads= TRUE;
while (killed_threads != created_threads)
{
/* wake up the event loop */
char c= 0;
send(thd_add_pair[1], &c, sizeof(c), 0);
pthread_cond_wait(&COND_thread_count, &LOCK_thread_count);
}
(void) pthread_mutex_unlock(&LOCK_thread_count);
event_del(&thd_add_event);
close_socketpair(thd_add_pair);
event_del(&thd_kill_event);
close_socketpair(thd_kill_pair);
event_base_free(base);
base= 0;
(void) pthread_mutex_destroy(&LOCK_event_loop);
(void) pthread_mutex_destroy(&LOCK_thd_add);
DBUG_VOID_RETURN;
}
void pool_of_threads_scheduler(scheduler_functions* func)
{
func->max_threads= thread_pool_size;
func->max_connections= &max_connections;
func->connection_count= &connection_count;
func->init= libevent_init;
func->end= libevent_end;
func->post_kill_notification= libevent_post_kill_notification;
func->add_connection= libevent_add_connection;
}
#endif