Fixed some race conditons and bugs related to killed queries

KILL now breaks locks inside InnoDB
Fixed possible deadlock when running INNODB STATUS
Added ha_kill_query() and kill_query() to send kill signal to all storage engines
Added reset_killed() to ensure we don't reset killed state while awake() is getting called


include/mysql/plugin.h:
  Added thd_mark_as_hard_kill()
include/mysql/plugin_audit.h.pp:
  Added thd_mark_as_hard_kill()
include/mysql/plugin_auth.h.pp:
  Added thd_mark_as_hard_kill()
include/mysql/plugin_ftparser.h.pp:
  Added thd_mark_as_hard_kill()
sql/handler.cc:
  Added ha_kill_query() to send kill signal to all storage engines
sql/handler.h:
  Added ha_kill_query() and kill_query() to send kill signal to all storage engines
sql/log_event.cc:
  Use reset_killed()
sql/mdl.cc:
  use thd->killed instead of thd_killed() to abort on soft kill
sql/sp_rcontext.cc:
  Use reset_killed()
sql/sql_class.cc:
  Fixed possible deadlock in INNODB STATUS by not getting thd->LOCK_thd_data if it's locked.
  Use reset_killed()
  Tell storge engines that KILL has been sent
sql/sql_class.h:
  Added reset_killed() to ensure we don't reset killed state while awake() is getting called.
  Added mark_as_hard_kill()
sql/sql_insert.cc:
  Use reset_killed()
sql/sql_parse.cc:
  Simplify detection of killed queries.
  Use reset_killed()
sql/sql_select.cc:
  Use reset_killed()
sql/sql_union.cc:
  Use reset_killed()
storage/innobase/handler/ha_innodb.cc:
  Added innobase_kill_query()
  Fixed error reporting for interrupted queries.
storage/xtradb/handler/ha_innodb.cc:
  Added innobase_kill_query()
  Fixed error reporting for interrupted queries.
This commit is contained in:
Michael Widenius 2013-01-11 00:22:14 +02:00
parent 396f4d62c6
commit 6e9a48b67f
17 changed files with 185 additions and 37 deletions

View file

@ -641,6 +641,17 @@ int mysql_tmpfile(const char *prefix);
*/
int thd_killed(const MYSQL_THD thd);
/**
Increase level of kill ; Ensures that thd_killed() returns true.
@param thd Thread connection handle
@details
Needed if storage engine wants to abort things because of a 'soft' (ie,
safe) kill but still uses thd_killed() to check if it's killed.
**/
void thd_mark_as_hard_kill(MYSQL_THD thd);
/**
Return the thread id of a user thread

View file

@ -227,6 +227,7 @@ char *thd_security_context(void* thd, char *buffer, unsigned int length,
void thd_inc_row_count(void* thd);
int mysql_tmpfile(const char *prefix);
int thd_killed(const void* thd);
void thd_mark_as_hard_kill(void* thd);
unsigned long thd_get_thread_id(const void* thd);
void thd_get_xid(const void* thd, MYSQL_XID *xid);
void mysql_query_cache_invalidate4(void* thd,

View file

@ -227,6 +227,7 @@ char *thd_security_context(void* thd, char *buffer, unsigned int length,
void thd_inc_row_count(void* thd);
int mysql_tmpfile(const char *prefix);
int thd_killed(const void* thd);
void thd_mark_as_hard_kill(void* thd);
unsigned long thd_get_thread_id(const void* thd);
void thd_get_xid(const void* thd, MYSQL_XID *xid);
void mysql_query_cache_invalidate4(void* thd,

View file

@ -180,6 +180,7 @@ char *thd_security_context(void* thd, char *buffer, unsigned int length,
void thd_inc_row_count(void* thd);
int mysql_tmpfile(const char *prefix);
int thd_killed(const void* thd);
void thd_mark_as_hard_kill(void* thd);
unsigned long thd_get_thread_id(const void* thd);
void thd_get_xid(const void* thd, MYSQL_XID *xid);
void mysql_query_cache_invalidate4(void* thd,

View file

@ -663,7 +663,6 @@ static my_bool closecon_handlerton(THD *thd, plugin_ref plugin,
return FALSE;
}
/**
@note
don't bother to rollback here, it's done already
@ -673,6 +672,26 @@ void ha_close_connection(THD* thd)
plugin_foreach(thd, closecon_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, 0);
}
static my_bool kill_handlerton(THD *thd, plugin_ref plugin,
void *hard_kill)
{
handlerton *hton= plugin_data(plugin, handlerton *);
if (hton->state == SHOW_OPTION_YES && hton->kill_query &&
thd_get_ha_data(thd, hton))
hton->kill_query(hton, thd, * (my_bool*) hard_kill);
return FALSE;
}
void ha_kill_query(THD* thd, my_bool hard_kill)
{
DBUG_ENTER("ha_kill_query");
plugin_foreach(thd, kill_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN,
&hard_kill);
DBUG_VOID_RETURN;
}
/* ========================================================================
======================= TRANSACTIONS ===================================*/

View file

@ -864,6 +864,13 @@ struct handlerton
this storage engine was accessed in this connection
*/
int (*close_connection)(handlerton *hton, THD *thd);
/*
Tell handler that query has been killed.
hard_kill is set in case of HARD KILL (abort query even if
it may corrupt table).
Return 1 if the handler wants to upgrade the kill to a hard kill
*/
void (*kill_query)(handlerton *hton, THD *thd, my_bool hard_kill);
/*
sv points to an uninitialized storage area of requested size
(see savepoint_offset description)
@ -2975,6 +2982,7 @@ int ha_finalize_handlerton(st_plugin_int *plugin);
TYPELIB *ha_known_exts(void);
int ha_panic(enum ha_panic_function flag);
void ha_close_connection(THD* thd);
void ha_kill_query(THD* thd, my_bool hard_kill);
bool ha_flush_logs(handlerton *db_type);
void ha_drop_database(char* path);
void ha_checkpoint_state(bool disable);

View file

@ -3884,7 +3884,7 @@ Default database: '%s'. Query: '%s'",
{
DBUG_PRINT("info",("error ignored"));
clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
thd->killed= NOT_KILLED;
thd->reset_killed();
}
/*
Other cases: mostly we expected no error and get one.

View file

@ -14,7 +14,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "mdl.h"
#include "sql_class.h"
#include "debug_sync.h"
#include <hash.h>
#include <mysqld_error.h>
@ -1185,7 +1185,7 @@ MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout,
wait_state_name);
thd_wait_begin(thd, THD_WAIT_META_DATA_LOCK);
while (!m_wait_status && !thd_killed(thd) &&
while (!m_wait_status && !thd->killed &&
wait_result != ETIMEDOUT && wait_result != ETIME)
{
wait_result= mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status,
@ -1207,7 +1207,7 @@ MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout,
false, which means that the caller intends to restart the
wait.
*/
if (thd_killed(thd))
if (thd->killed)
m_wait_status= KILLED;
else if (set_status_on_timeout)
m_wait_status= TIMEOUT;

View file

@ -419,7 +419,7 @@ sp_rcontext::activate_handler(THD *thd,
/* Reset error state. */
thd->clear_error();
thd->killed= NOT_KILLED; // Some errors set thd->killed
thd->reset_killed(); // Some errors set thd->killed
// (e.g. "bad data").
/* Return IP of the activated SQL handler. */

View file

@ -701,7 +701,7 @@ char *thd_security_context(THD *thd, char *buffer, unsigned int length,
values doesn't have to very accurate and the memory it points to is static,
but we need to attempt a snapshot on the pointer values to avoid using NULL
values. The pointer to thd->query however, doesn't point to static memory
and has to be protected by LOCK_thread_count or risk pointing to
and has to be protected by thd->LOCK_thd_data or risk pointing to
uninitialized memory.
*/
const char *proc_info= thd->proc_info;
@ -736,20 +736,21 @@ char *thd_security_context(THD *thd, char *buffer, unsigned int length,
str.append(proc_info);
}
mysql_mutex_lock(&thd->LOCK_thd_data);
if (thd->query())
/* Don't wait if LOCK_thd_data is used as this could cause a deadlock */
if (!mysql_mutex_trylock(&thd->LOCK_thd_data))
{
if (max_query_len < 1)
len= thd->query_length();
else
len= min(thd->query_length(), max_query_len);
str.append('\n');
str.append(thd->query(), len);
if (thd->query())
{
if (max_query_len < 1)
len= thd->query_length();
else
len= min(thd->query_length(), max_query_len);
str.append('\n');
str.append(thd->query(), len);
}
mysql_mutex_unlock(&thd->LOCK_thd_data);
}
mysql_mutex_unlock(&thd->LOCK_thd_data);
if (str.c_ptr_safe() == buffer)
return buffer;
@ -1355,7 +1356,7 @@ void THD::change_user(void)
mysql_mutex_unlock(&LOCK_status);
cleanup();
killed= NOT_KILLED;
reset_killed();
cleanup_done= 0;
init();
stmt_map.reset();
@ -1610,6 +1611,10 @@ void THD::awake(killed_state state_to_set)
MYSQL_CALLBACK(scheduler, post_kill_notification, (this));
}
/* Interrupt target waiting inside a storage engine. */
if (state_to_set != NOT_KILLED)
ha_kill_query(this, test(state_to_set & KILL_HARD_BIT));
/* Broadcast a condition to kick the target if it is waiting on it. */
if (mysys_var)
{
@ -3848,6 +3853,18 @@ extern "C" int thd_killed(const MYSQL_THD thd)
return thd->killed;
}
/**
Change kill level to hard.
This ensures that thd_killed() will return true.
This is important for storage engines that uses thd_killed() to
verify if thread is killed.
*/
extern "C" void thd_mark_as_hard_kill(MYSQL_THD thd)
{
thd->mark_as_hard_kill();
}
/**
Send an out-of-band progress report to the client
@ -5603,4 +5620,3 @@ bool Discrete_intervals_list::append(Discrete_interval *new_interval)
}
#endif /* !defined(MYSQL_CLIENT) */

View file

@ -2703,6 +2703,19 @@ public:
{
return ::killed_errno(killed);
}
inline void reset_killed()
{
/*
Resetting killed has to be done under a mutex to ensure
its not done during an awake() call.
*/
if (killed != NOT_KILLED)
{
mysql_mutex_lock(&LOCK_thd_data);
killed= NOT_KILLED;
mysql_mutex_unlock(&LOCK_thd_data);
}
}
inline void send_kill_message() const
{
int err= killed_errno();
@ -2716,6 +2729,17 @@ public:
(!transaction.stmt.modified_non_trans_table ||
(variables.sql_mode & MODE_STRICT_ALL_TABLES)));
}
/*
Increase level of kill ; Ensures that thd_killed() returns true.
Needed if storage engine wants to abort things because of a 'soft' (ie,
safe) kill but still uses thd_killed() to check if it's killed.
*/
inline void mark_as_hard_kill()
{
DBUG_ASSERT(killed != NOT_KILLED);
killed= (killed_state) (killed | KILL_HARD_BIT);
}
void set_status_var_init();
void reset_n_backup_open_tables_state(Open_tables_backup *backup);
void restore_backup_open_tables_state(Open_tables_backup *backup);

View file

@ -2724,7 +2724,10 @@ pthread_handler_t handle_delayed_insert(void *arg)
thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
thd->set_current_time();
threads.append(thd);
thd->killed=abort_loop ? KILL_CONNECTION : NOT_KILLED;
if (abort_loop)
thd->killed= KILL_CONNECTION;
else
thd->reset_killed();
mysql_mutex_unlock(&LOCK_thread_count);
mysql_thread_set_psi_id(thd->thread_id);

View file

@ -4495,16 +4495,20 @@ finish:
if (! thd->in_sub_stmt)
{
/* report error issued during command execution */
if (thd->killed_errno())
if (thd->killed != NOT_KILLED)
{
if (! thd->stmt_da->is_set())
thd->send_kill_message();
}
if (thd->killed < KILL_CONNECTION)
{
thd->killed= NOT_KILLED;
thd->mysys_var->abort= 0;
/* report error issued during command execution */
if (thd->killed_errno())
{
/* If we already sent 'ok', we can ignore any kill query statements */
if (! thd->stmt_da->is_set())
thd->send_kill_message();
}
if (thd->killed < KILL_CONNECTION)
{
thd->reset_killed();
thd->mysys_var->abort= 0;
}
}
if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR))
trans_rollback_stmt(thd);

View file

@ -331,7 +331,7 @@ bool handle_select(THD *thd, LEX *lex, select_result *result,
ER(ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT),
thd->accessed_rows_and_keys,
thd->lex->limit_rows_examined->val_uint());
thd->killed= NOT_KILLED;
thd->reset_killed();
}
/* Disable LIMIT ROWS EXAMINED after query execution. */
thd->lex->limit_rows_examined_cnt= ULONGLONG_MAX;
@ -19213,7 +19213,7 @@ remove_duplicates(JOIN *join, TABLE *entry,List<Item> &fields, Item *having)
*/
thd->lex->limit_rows_examined_cnt= ULONGLONG_MAX;
if (thd->killed == ABORT_QUERY)
thd->killed= NOT_KILLED;
thd->reset_killed();
free_io_cache(entry); // Safety
entry->file->info(HA_STATUS_VARIABLE);
if (entry->s->db_type() == heap_hton ||

View file

@ -716,7 +716,7 @@ bool st_select_lex_unit::exec()
ER(ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT),
thd->accessed_rows_and_keys,
thd->lex->limit_rows_examined->val_uint());
thd->killed= NOT_KILLED;
thd->reset_killed();
break;
}
}

View file

@ -339,6 +339,7 @@ static PSI_file_info all_innodb_files[] = {
static INNOBASE_SHARE *get_share(const char *table_name);
static void free_share(INNOBASE_SHARE *share);
static int innobase_close_connection(handlerton *hton, THD* thd);
static void innobase_kill_query(handlerton *hton, THD* thd, my_bool hard_kill);
static void innobase_commit_ordered(handlerton *hton, THD* thd, bool all);
static int innobase_commit(handlerton *hton, THD* thd, bool all);
static int innobase_rollback(handlerton *hton, THD* thd, bool all);
@ -917,8 +918,7 @@ convert_error_code_to_mysql(
return(0);
case DB_INTERRUPTED:
my_error(ER_QUERY_INTERRUPTED, MYF(0));
/* fall through */
return(HA_ERR_ABORTED_BY_USER);
case DB_FOREIGN_EXCEED_MAX_CASCADE:
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
@ -2271,6 +2271,7 @@ innobase_init(
innobase_hton->flags=HTON_NO_FLAGS;
innobase_hton->release_temporary_latches=innobase_release_temporary_latches;
innobase_hton->alter_table_flags = innobase_alter_table_flags;
innobase_hton->kill_query = innobase_kill_query;
ut_a(DATA_MYSQL_TRUE_VARCHAR == (ulint)MYSQL_TYPE_VARCHAR);
@ -3174,6 +3175,36 @@ innobase_close_connection(
}
/*****************************************************************//**
Cancel any pending lock request associated with the current THD. */
static
void
innobase_kill_query(
/*======================*/
handlerton* hton, /*!< in: innobase handlerton */
THD* thd, /*!< in: handle to the MySQL thread being killed */
my_bool hard_kill) /*!< in: If hard kill */
{
trx_t* trx;
DBUG_ENTER("innobase_kill_query");
DBUG_ASSERT(hton == innodb_hton_ptr);
mutex_enter(&kernel_mutex);
trx = thd_to_trx(thd);
/* Cancel a pending lock request. */
if (trx && trx->wait_lock) {
thd_mark_as_hard_kill(thd);
lock_cancel_waiting_and_release(trx->wait_lock);
}
mutex_exit(&kernel_mutex);
DBUG_VOID_RETURN;
}
/*************************************************************************//**
** InnoDB database tables
*****************************************************************************/

View file

@ -378,6 +378,7 @@ static PSI_file_info all_innodb_files[] = {
static INNOBASE_SHARE *get_share(const char *table_name);
static void free_share(INNOBASE_SHARE *share);
static int innobase_close_connection(handlerton *hton, THD* thd);
static void innobase_kill_query(handlerton *hton, THD* thd, my_bool hard_kill);
static void innobase_commit_ordered(handlerton *hton, THD* thd, bool all);
static int innobase_commit(handlerton *hton, THD* thd, bool all);
static int innobase_rollback(handlerton *hton, THD* thd, bool all);
@ -1106,8 +1107,7 @@ convert_error_code_to_mysql(
return(0);
case DB_INTERRUPTED:
my_error(ER_QUERY_INTERRUPTED, MYF(0));
/* fall through */
return(HA_ERR_ABORTED_BY_USER);
case DB_FOREIGN_EXCEED_MAX_CASCADE:
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
@ -2627,6 +2627,7 @@ innobase_init(
innobase_hton->flags=HTON_NO_FLAGS;
innobase_hton->release_temporary_latches=innobase_release_temporary_latches;
innobase_hton->alter_table_flags = innobase_alter_table_flags;
innobase_hton->kill_query = innobase_kill_query;
ut_a(DATA_MYSQL_TRUE_VARCHAR == (ulint)MYSQL_TYPE_VARCHAR);
@ -3758,6 +3759,34 @@ innobase_close_connection(
DBUG_RETURN(0);
}
/*****************************************************************//**
Cancel any pending lock request associated with the current THD. */
static
void
innobase_kill_query(
/*======================*/
handlerton* hton, /*!< in: innobase handlerton */
THD* thd, /*!< in: handle to the MySQL thread being killed */
my_bool hard_kill) /*!< in: If hard kill */
{
trx_t* trx;
DBUG_ENTER("innobase_kill_query");
DBUG_ASSERT(hton == innodb_hton_ptr);
mutex_enter(&kernel_mutex);
trx = thd_to_trx(thd);
/* Cancel a pending lock request. */
if (trx && trx->wait_lock) {
thd_mark_as_hard_kill(thd);
lock_cancel_waiting_and_release(trx->wait_lock);
}
mutex_exit(&kernel_mutex);
DBUG_VOID_RETURN;
}
/*************************************************************************//**
** InnoDB database tables