Skip to content

Commit

Permalink
MDEV-13179 main.errors fails with wrong errno
Browse files Browse the repository at this point in the history
The problem was that the introduction of max-thread-mem-used can cause
an allocation error very early, even before mysql_parse() is called.
As mysql_parse() calls thd->reset_for_next_command(), which called
clear_error(), the error number was lost.

Fixed by adding an option to have unique messages for each KILL
signal and change max-thread-mem-used to use this new feature.
This removes a lot of problems with the original approach, where
one could get errors signaled silenty almost any time.

ixed by moving clear_error() from reset_for_next_command() to
do_command(), before any memory allocation for the thread.

Related changes:
- reset_for_next_command() now have an optional parameter if we should
  call clear_error() or not. By default it's called, but not anymore from
  dispatch_command() which was the original problem.
- Added optional paramater to clear_error() to force calling of
  reset_diagnostics_area(). Before clear_error() only called
  reset_diagnostics_area() if there was no error, so we normally
  called reset_diagnostics_area() twice.
- This change removed several duplicated calls to clear_error()
  when starting a query.
- Reset max_mem_used on COM_QUIT, to protect against kill during
  quit.
- Use fatal_error() instead of setting is_fatal_error (cleanup)
- Set fatal_error if max_thead_mem_used is signaled.
  (Same logic we use for other places where we are out of resources)
  • Loading branch information
montywi committed Aug 7, 2017
1 parent 008786a commit 7454369
Show file tree
Hide file tree
Showing 24 changed files with 176 additions and 100 deletions.
3 changes: 1 addition & 2 deletions libmysqld/lib_sql.cc
Expand Up @@ -140,8 +140,7 @@ emb_advanced_command(MYSQL *mysql, enum enum_server_command command,
}

/* Clear result variables */
thd->clear_error();
thd->get_stmt_da()->reset_diagnostics_area();
thd->clear_error(1);
mysql->affected_rows= ~(my_ulonglong) 0;
mysql->field_count= 0;
net_clear_error(net);
Expand Down
4 changes: 2 additions & 2 deletions mysql-test/r/errors.result
Expand Up @@ -168,7 +168,7 @@ UPDATE t1 SET a = 'new'
WHERE COLUMN_CREATE( 1, 'v', 1, 'w' ) IS NULL;
ERROR 22007: Illegal value used as argument of dynamic column function
drop table t1;
set max_session_mem_used = 50000;
select * from seq_1_to_1000;
set max_session_mem_used = 8192;
select * from seq_1_to_1000;
Got one of the listed errors
set global max_session_mem_used = default;
10 changes: 8 additions & 2 deletions mysql-test/t/errors.test
Expand Up @@ -203,7 +203,13 @@ drop table t1;
#
# errors caused by max_session_mem_used
#
--disable_result_log
set max_session_mem_used = 50000;
--error 0,ER_OPTION_PREVENTS_STATEMENT
select * from seq_1_to_1000;
set max_session_mem_used = 8192;
--error ER_SQL_DISCOVER_ERROR,ER_OPTION_PREVENTS_STATEMENT
--error 0,ER_OPTION_PREVENTS_STATEMENT
select * from seq_1_to_1000;
set global max_session_mem_used = default;
--enable_result_log
# We may not be able to execute any more queries with this connection
# because of too little memory#
2 changes: 1 addition & 1 deletion sql/debug_sync.cc
Expand Up @@ -1502,7 +1502,7 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action)
{
if (!--action->hit_limit)
{
thd->killed= KILL_QUERY;
thd->set_killed(KILL_QUERY);
my_error(ER_DEBUG_SYNC_HIT_LIMIT, MYF(0));
}
DBUG_PRINT("debug_sync_exec", ("hit_limit: %lu at: '%s'",
Expand Down
21 changes: 7 additions & 14 deletions sql/log_event.cc
Expand Up @@ -371,12 +371,6 @@ static void pretty_print_str(IO_CACHE* cache, const char* str, int len)

#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)

static void clear_all_errors(THD *thd, Relay_log_info *rli)
{
thd->is_slave_error = 0;
thd->clear_error();
}

inline int idempotent_error_code(int err_code)
{
int ret= 0;
Expand Down Expand Up @@ -4255,7 +4249,7 @@ int Query_log_event::do_apply_event(rpl_group_info *rgi,

DBUG_PRINT("info", ("log_pos: %lu", (ulong) log_pos));

clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
thd->clear_error(1);
current_stmt_is_commit= is_commit();

DBUG_ASSERT(!current_stmt_is_commit || !rgi->tables_to_lock);
Expand Down Expand Up @@ -4475,7 +4469,7 @@ int Query_log_event::do_apply_event(rpl_group_info *rgi,
to check/fix it.
*/
if (mysql_test_parse_for_slave(thd, thd->query(), thd->query_length()))
clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); /* Can ignore query */
thd->clear_error(1);
else
{
rli->report(ERROR_LEVEL, expected_error, rgi->gtid_info(),
Expand Down Expand Up @@ -4556,7 +4550,7 @@ START SLAVE; . Query: '%s'", expected_error, thd->query());
ignored_error_code(actual_error))
{
DBUG_PRINT("info",("error ignored"));
clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
thd->clear_error(1);
if (actual_error == ER_QUERY_INTERRUPTED ||
actual_error == ER_CONNECTION_KILLED)
thd->reset_killed();
Expand Down Expand Up @@ -6025,8 +6019,7 @@ int Load_log_event::do_apply_event(NET* net, rpl_group_info *rgi,
new_db.str= (char *) rpl_filter->get_rewrite_db(db, &new_db.length);
thd->set_db(new_db.str, new_db.length);
DBUG_ASSERT(thd->query() == 0);
thd->is_slave_error= 0;
clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
thd->clear_error(1);

/* see Query_log_event::do_apply_event() and BUG#13360 */
DBUG_ASSERT(!rgi->m_table_map.count());
Expand All @@ -6036,7 +6029,7 @@ int Load_log_event::do_apply_event(NET* net, rpl_group_info *rgi,
*/
lex_start(thd);
thd->lex->local_file= local_fname;
thd->reset_for_next_command();
thd->reset_for_next_command(0); // Errors are cleared above

/*
We test replicate_*_db rules. Note that we have already prepared
Expand Down Expand Up @@ -10091,7 +10084,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
slave_rows_error_report(WARNING_LEVEL, error, rgi, thd, table,
get_type_str(),
RPL_LOG_NAME, (ulong) log_pos);
clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
thd->clear_error(1);
error= 0;
if (idempotent_error == 0)
break;
Expand Down Expand Up @@ -10143,7 +10136,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
slave_rows_error_report(WARNING_LEVEL, error, rgi, thd, table,
get_type_str(),
RPL_LOG_NAME, (ulong) log_pos);
clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
thd->clear_error(1);
error= 0;
}
} // if (table)
Expand Down
20 changes: 13 additions & 7 deletions sql/mysqld.cc
Expand Up @@ -878,7 +878,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list,
key_LOCK_prepared_stmt_count,
key_LOCK_rpl_status, key_LOCK_server_started,
key_LOCK_status, key_LOCK_show_status,
key_LOCK_system_variables_hash, key_LOCK_thd_data,
key_LOCK_system_variables_hash, key_LOCK_thd_data, key_LOCK_thd_kill,
key_LOCK_user_conn, key_LOCK_uuid_short_generator, key_LOG_LOCK_log,
key_master_info_data_lock, key_master_info_run_lock,
key_master_info_sleep_lock, key_master_info_start_stop_lock,
Expand Down Expand Up @@ -949,6 +949,7 @@ static PSI_mutex_info all_server_mutexes[]=
{ &key_LOCK_wait_commit, "wait_for_commit::LOCK_wait_commit", 0},
{ &key_LOCK_gtid_waiting, "gtid_waiting::LOCK_gtid_waiting", 0},
{ &key_LOCK_thd_data, "THD::LOCK_thd_data", 0},
{ &key_LOCK_thd_kill, "THD::LOCK_thd_kill", 0},
{ &key_LOCK_user_conn, "LOCK_user_conn", PSI_FLAG_GLOBAL},
{ &key_LOCK_uuid_short_generator, "LOCK_uuid_short_generator", PSI_FLAG_GLOBAL},
{ &key_LOG_LOCK_log, "LOG::LOCK_log", 0},
Expand Down Expand Up @@ -1650,7 +1651,7 @@ static void close_connections(void)
if (WSREP(tmp) && (tmp->wsrep_exec_mode==REPL_RECV || tmp->wsrep_applier))
continue;
#endif
tmp->killed= KILL_SERVER_HARD;
tmp->set_killed(KILL_SERVER_HARD);
MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (tmp));
mysql_mutex_lock(&tmp->LOCK_thd_data);
if (tmp->mysys_var)
Expand Down Expand Up @@ -1738,7 +1739,7 @@ static void close_connections(void)
if (WSREP(tmp) && tmp->wsrep_exec_mode==REPL_RECV)
{
sql_print_information("closing wsrep system thread");
tmp->killed= KILL_CONNECTION;
tmp->set_killed(KILL_CONNECTION);
MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (tmp));
if (tmp->mysys_var)
{
Expand Down Expand Up @@ -3943,11 +3944,16 @@ static void my_malloc_size_cb_func(long long size, my_bool is_thread_specific)
thd->status_var.local_memory_used > (int64)thd->variables.max_mem_used &&
!thd->killed && !thd->get_stmt_da()->is_set())
{
char buf[1024];
thd->killed= KILL_QUERY;
/* Ensure we don't get called here again */
char buf[50], *buf2;
thd->set_killed(KILL_QUERY);
my_snprintf(buf, sizeof(buf), "--max-thread-mem-used=%llu",
thd->variables.max_mem_used);
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), buf);
if ((buf2= (char*) thd->alloc(256)))
{
my_snprintf(buf2, 256, ER_THD(thd, ER_OPTION_PREVENTS_STATEMENT), buf);
thd->set_killed(KILL_QUERY, ER_OPTION_PREVENTS_STATEMENT, buf2);
}
}
DBUG_ASSERT((longlong) thd->status_var.local_memory_used >= 0);
}
Expand Down Expand Up @@ -6318,7 +6324,7 @@ void create_thread_to_handle_connection(THD *thd)
DBUG_PRINT("error",
("Can't create thread to handle request (error %d)",
error));
thd->killed= KILL_CONNECTION; // Safety
thd->set_killed(KILL_CONNECTION); // Safety
mysql_mutex_unlock(&LOCK_thread_count);

mysql_mutex_lock(&LOCK_connection_count);
Expand Down
2 changes: 1 addition & 1 deletion sql/mysqld.h
Expand Up @@ -290,7 +290,7 @@ extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list,
key_LOCK_prepared_stmt_count,
key_LOCK_rpl_status, key_LOCK_server_started,
key_LOCK_status, key_LOCK_show_status,
key_LOCK_thd_data,
key_LOCK_thd_data, key_LOCK_thd_kill,
key_LOCK_user_conn, key_LOG_LOCK_log,
key_master_info_data_lock, key_master_info_run_lock,
key_master_info_sleep_lock, key_master_info_start_stop_lock,
Expand Down
8 changes: 2 additions & 6 deletions sql/rpl_parallel.cc
Expand Up @@ -714,9 +714,7 @@ retry_event_group(rpl_group_info *rgi, rpl_parallel_thread *rpt,
DBUG_EXECUTE_IF("inject_mdev8031", {
/* Simulate that we get deadlock killed at this exact point. */
rgi->killed_for_retry= rpl_group_info::RETRY_KILL_KILLED;
mysql_mutex_lock(&thd->LOCK_thd_data);
thd->killed= KILL_CONNECTION;
mysql_mutex_unlock(&thd->LOCK_thd_data);
thd->set_killed(KILL_CONNECTION);
});
rgi->cleanup_context(thd, 1);
wait_for_pending_deadlock_kill(thd, rgi);
Expand Down Expand Up @@ -862,9 +860,7 @@ retry_event_group(rpl_group_info *rgi, rpl_parallel_thread *rpt,
/* Simulate that we get deadlock killed during open_binlog(). */
thd->reset_for_next_command();
rgi->killed_for_retry= rpl_group_info::RETRY_KILL_KILLED;
mysql_mutex_lock(&thd->LOCK_thd_data);
thd->killed= KILL_CONNECTION;
mysql_mutex_unlock(&thd->LOCK_thd_data);
thd->set_killed(KILL_CONNECTION);
thd->send_kill_message();
fd= (File)-1;
err= 1;
Expand Down
3 changes: 1 addition & 2 deletions sql/sp_rcontext.cc
Expand Up @@ -331,8 +331,7 @@ bool sp_rcontext::handle_sql_condition(THD *thd,

/* Reset error state. */
thd->clear_error();
thd->killed= NOT_KILLED; // Some errors set thd->killed
// (e.g. "bad data").
thd->reset_killed(); // Some errors set thd->killed, (e.g. "bad data").

/* Add a frame to handler-call-stack. */
Sql_condition_info *cond_info=
Expand Down
4 changes: 2 additions & 2 deletions sql/sql_base.cc
Expand Up @@ -400,7 +400,7 @@ void kill_delayed_threads_for_table(TDC_element *element)
if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
! in_use->killed)
{
in_use->killed= KILL_SYSTEM_THREAD;
in_use->set_killed(KILL_SYSTEM_THREAD);
mysql_mutex_lock(&in_use->mysys_var->mutex);
if (in_use->mysys_var->current_cond)
{
Expand Down Expand Up @@ -9136,7 +9136,7 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
!in_use->killed)
{
in_use->killed= KILL_SYSTEM_THREAD;
in_use->set_killed(KILL_SYSTEM_THREAD);
mysql_mutex_lock(&in_use->mysys_var->mutex);
if (in_use->mysys_var->current_cond)
{
Expand Down
5 changes: 2 additions & 3 deletions sql/sql_cache.cc
Expand Up @@ -2160,8 +2160,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
response, we can't handle it anyway.
*/
(void) trans_commit_stmt(thd);
if (!thd->get_stmt_da()->is_set())
thd->get_stmt_da()->disable_status();
thd->get_stmt_da()->disable_status();

BLOCK_UNLOCK_RD(query_block);
MYSQL_QUERY_CACHE_HIT(thd->query(), (ulong) thd->limit_found_rows);
Expand Down Expand Up @@ -4615,7 +4614,7 @@ void Query_cache::wreck(uint line, const char *message)
DBUG_PRINT("warning", ("%5d QUERY CACHE WRECK => DISABLED",line));
DBUG_PRINT("warning", ("=================================="));
if (thd)
thd->killed= KILL_CONNECTION;
thd->set_killed(KILL_CONNECTION);
cache_dump();
/* check_integrity(0); */ /* Can't call it here because of locks */
bins_dump();
Expand Down
31 changes: 22 additions & 9 deletions sql/sql_class.cc
Expand Up @@ -333,7 +333,7 @@ void thd_set_psi(THD *thd, PSI_thread *psi)
*/
void thd_set_killed(THD *thd)
{
thd->killed= KILL_CONNECTION;
thd->set_killed(KILL_CONNECTION);
}

/**
Expand Down Expand Up @@ -935,6 +935,7 @@ THD::THD(bool is_wsrep_applier)
query_start_used= query_start_sec_part_used= 0;
count_cuted_fields= CHECK_FIELD_IGNORE;
killed= NOT_KILLED;
killed_err= 0;
col_access=0;
is_slave_error= thread_specific_used= FALSE;
my_hash_clear(&handler_tables_hash);
Expand Down Expand Up @@ -992,6 +993,7 @@ THD::THD(bool is_wsrep_applier)
#endif
mysql_mutex_init(key_LOCK_thd_data, &LOCK_thd_data, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_wakeup_ready, &LOCK_wakeup_ready, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_thd_kill, &LOCK_thd_kill, MY_MUTEX_INIT_FAST);
mysql_cond_init(key_COND_wakeup_ready, &COND_wakeup_ready, 0);
/*
LOCK_thread_count goes before LOCK_thd_data - the former is called around
Expand Down Expand Up @@ -1256,7 +1258,7 @@ Sql_condition* THD::raise_condition(uint sql_errno,
push_warning and strict SQL_MODE case.
*/
level= Sql_condition::WARN_LEVEL_ERROR;
killed= KILL_BAD_DATA;
set_killed(KILL_BAD_DATA);
}

switch (level)
Expand Down Expand Up @@ -1564,7 +1566,7 @@ void THD::cleanup(void)
DBUG_ENTER("THD::cleanup");
DBUG_ASSERT(cleanup_done == 0);

killed= KILL_CONNECTION;
set_killed(KILL_CONNECTION);
#ifdef ENABLE_WHEN_BINLOG_WILL_BE_ABLE_TO_PREPARE
if (transaction.xid_state.xa_state == XA_PREPARED)
{
Expand Down Expand Up @@ -1667,6 +1669,7 @@ THD::~THD()
mysql_cond_destroy(&COND_wakeup_ready);
mysql_mutex_destroy(&LOCK_wakeup_ready);
mysql_mutex_destroy(&LOCK_thd_data);
mysql_mutex_destroy(&LOCK_thd_kill);
#ifndef DBUG_OFF
dbug_sentry= THD_SENTRY_GONE;
#endif
Expand Down Expand Up @@ -1839,7 +1842,8 @@ void THD::awake(killed_state state_to_set)
state_to_set= killed;

/* Set the 'killed' flag of 'this', which is the target THD object. */
killed= state_to_set;
mysql_mutex_lock(&LOCK_thd_kill);
set_killed_no_mutex(state_to_set);

if (state_to_set >= KILL_CONNECTION || state_to_set == NOT_KILLED)
{
Expand Down Expand Up @@ -1925,6 +1929,7 @@ void THD::awake(killed_state state_to_set)
}
mysql_mutex_unlock(&mysys_var->mutex);
}
mysql_mutex_unlock(&LOCK_thd_kill);
DBUG_VOID_RETURN;
}

Expand All @@ -1942,7 +1947,7 @@ void THD::disconnect()

mysql_mutex_lock(&LOCK_thd_data);

killed= KILL_CONNECTION;
set_killed(KILL_CONNECTION);

#ifdef SIGNAL_WITH_VIO_CLOSE
/*
Expand Down Expand Up @@ -1978,7 +1983,7 @@ bool THD::notify_shared_lock(MDL_context_owner *ctx_in_use,
DBUG_PRINT("info", ("kill delayed thread"));
mysql_mutex_lock(&in_use->LOCK_thd_data);
if (in_use->killed < KILL_CONNECTION)
in_use->killed= KILL_CONNECTION;
in_use->set_killed(KILL_CONNECTION);
if (in_use->mysys_var)
{
mysql_mutex_lock(&in_use->mysys_var->mutex);
Expand Down Expand Up @@ -2031,13 +2036,21 @@ bool THD::notify_shared_lock(MDL_context_owner *ctx_in_use,
/*
Get error number for killed state
Note that the error message can't have any parameters.
If one needs parameters, one should use THD::killed_err_msg
See thd::kill_message()
*/

int killed_errno(killed_state killed)
int THD::killed_errno()
{
DBUG_ENTER("killed_errno");
DBUG_PRINT("enter", ("killed: %d", killed));
DBUG_PRINT("enter", ("killed: %d killed_errno: %d",
killed, killed_err ? killed_err->no: 0));

/* Ensure that killed_err is not set if we are not killed */
DBUG_ASSERT(!killed_err || killed != NOT_KILLED);

if (killed_err)
DBUG_RETURN(killed_err->no);

switch (killed) {
case NOT_KILLED:
Expand Down Expand Up @@ -2478,7 +2491,7 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length)
{
my_error(EE_OUTOFMEMORY, MYF(ME_BELL+ME_FATALERROR),
ALIGN_SIZE(sizeof(TABLE_LIST)) + key_length + 1);
killed= KILL_CONNECTION;
set_killed(KILL_CONNECTION);
return 0;
}

Expand Down

0 comments on commit 7454369

Please sign in to comment.