diff --git a/mysql-8.0.19/include/mysql_com.h b/mysql-8.0.19/include/mysql_com.h index 0aea32e94..15324573a 100644 --- a/mysql-8.0.19/include/mysql_com.h +++ b/mysql-8.0.19/include/mysql_com.h @@ -835,6 +835,9 @@ enum SERVER_STATUS_flags_enum { */ #define ONLY_KILL_QUERY 1 +#define KILL_ALL_THREADS (1 << 1) +#define KILL_ALL_THREADS_FORCE (1 << 2) + #ifndef MYSQL_VIO struct Vio; #define MYSQL_VIO struct Vio * diff --git a/mysql-8.0.19/mysql-test/r/information_schema_keywords.result b/mysql-8.0.19/mysql-test/r/information_schema_keywords.result index 79fb46332..45fbdb2eb 100644 --- a/mysql-8.0.19/mysql-test/r/information_schema_keywords.result +++ b/mysql-8.0.19/mysql-test/r/information_schema_keywords.result @@ -530,6 +530,7 @@ ROW_COUNT 0 ROW_FORMAT 0 ROW_NUMBER 1 RTREE 0 +SAFE 0 SAVEPOINT 0 SCHEDULE 0 SCHEMA 1 @@ -624,6 +625,7 @@ TERMINATED 1 TEXT 0 THAN 0 THEN 1 +THREADS 0 THREAD_PRIORITY 0 TIES 0 TIME 0 diff --git a/mysql-8.0.19/mysql-test/r/kill_all_threads.result b/mysql-8.0.19/mysql-test/r/kill_all_threads.result new file mode 100644 index 000000000..b5be518a3 --- /dev/null +++ b/mysql-8.0.19/mysql-test/r/kill_all_threads.result @@ -0,0 +1,15 @@ +# +# Test KILL THREADS ALL statement. +# When KILL THREADS ALL is called, all user threads get killed. +# If a thread is idle, it is killed instantly; if it is executing +# a query or in transaction, it will get killed once it finishes. +# +begin; +KILL THREADS ALL; +SELECT @before - @after1; +@before - @after1 +1 +commit; +SELECT @before - @after2; +@before - @after2 +2 diff --git a/mysql-8.0.19/mysql-test/r/kill_thread_safe.result b/mysql-8.0.19/mysql-test/r/kill_thread_safe.result new file mode 100644 index 000000000..284e5cf6f --- /dev/null +++ b/mysql-8.0.19/mysql-test/r/kill_thread_safe.result @@ -0,0 +1,12 @@ +# +# Test KILL SAFE statement. +# When a thread to kill is executing a query or in transaction, +# KILL SAFE does not kill it and reports an error. +# +SET autocommit=OFF; +KILL @id SAFE; +SET autocommit=ON; +BEGIN; +KILL @id SAFE; +COMMIT; +KILL @id SAFE; diff --git a/mysql-8.0.19/mysql-test/t/kill_all_threads.test b/mysql-8.0.19/mysql-test/t/kill_all_threads.test new file mode 100644 index 000000000..6ebf1f9ad --- /dev/null +++ b/mysql-8.0.19/mysql-test/t/kill_all_threads.test @@ -0,0 +1,27 @@ +--echo # +--echo # Test KILL THREADS ALL statement. +--echo # When KILL THREADS ALL is called, all user threads get killed. +--echo # If a thread is idle, it is killed instantly; if it is executing +--echo # a query or in transaction, it will get killed once it finishes. +--echo # + +connect (killer, localhost, root,,); +connect (killee, localhost, root,,); + +connection killee; +begin; + +connection killer; +let $before = `SELECT @before:= (SELECT COUNT(*) FROM information_schema.processlist)`; +KILL THREADS ALL; +let $after1 = `SELECT @after1:= (SELECT COUNT(*) FROM information_schema.processlist)`; + +# test thread is killed +SELECT @before - @after1; + +connection killee; +commit; + +connection killer; +let $after2 = `SELECT @after2:= (SELECT COUNT(*) FROM information_schema.processlist)`; +SELECT @before - @after2; \ No newline at end of file diff --git a/mysql-8.0.19/mysql-test/t/kill_thread_safe.test b/mysql-8.0.19/mysql-test/t/kill_thread_safe.test new file mode 100644 index 000000000..b2f704021 --- /dev/null +++ b/mysql-8.0.19/mysql-test/t/kill_thread_safe.test @@ -0,0 +1,43 @@ +--echo # +--echo # Test KILL SAFE statement. +--echo # When a thread to kill is executing a query or in transaction, +--echo # KILL SAFE does not kill it and reports an error. +--echo # + +connect (con1, localhost, root,,); +connect (con2, localhost, root,,); + +--connection con2 +let $ID = `SELECT @id:= CONNECTION_ID()`; +SET autocommit=OFF; + +# con2's autocommit is off. +# KILL SAFE should fail. +--connection con1 +--disable_warnings +let @ignore = `SELECT @id := $ID`; +--enable_warnings +--disable_result_log +--error 3930 +KILL @id SAFE; +--enable_result_log + +--connection con2 +SET autocommit=ON; +BEGIN; + +# con2 is in transaction. +# KILL SAFE should fail. +--connection con1 +--disable_result_log +--error 3930 +KILL @id SAFE; +--enable_result_log + +--connection con2 +COMMIT; + +# con2's transaction is done. +# KILL SAFE should succeed. +--connection con1 +KILL @id SAFE; \ No newline at end of file diff --git a/mysql-8.0.19/share/messages_to_clients.txt b/mysql-8.0.19/share/messages_to_clients.txt index 957d31c6c..78d796d9d 100644 --- a/mysql-8.0.19/share/messages_to_clients.txt +++ b/mysql-8.0.19/share/messages_to_clients.txt @@ -9192,6 +9192,9 @@ ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV ER_WARN_DA_PRIVILEGE_NOT_REGISTERED eng "Dynamic privilege '%s' is not registered with the server." +ER_THREAD_IN_USING_OR_TRANSACTION + eng "thread %lu is in using or in transaction, can't be killed safe" + ER_CLIENT_KEYRING_UDF_KEY_INVALID eng "Function '%s' failed because key is invalid." diff --git a/mysql-8.0.19/sql/conn_handler/connection_handler_per_thread.cc b/mysql-8.0.19/sql/conn_handler/connection_handler_per_thread.cc index b56c32c9f..7b54edc13 100644 --- a/mysql-8.0.19/sql/conn_handler/connection_handler_per_thread.cc +++ b/mysql-8.0.19/sql/conn_handler/connection_handler_per_thread.cc @@ -306,6 +306,10 @@ static void *handle_connection(void *arg) { else { while (thd_connection_alive(thd)) { if (do_command(thd)) break; + if (thd->kill_self && !thd->in_multi_stmt_transaction_mode()) { + thd->kill_self = false; + break; + } } end_connection(thd); } diff --git a/mysql-8.0.19/sql/gen_lex_token.cc b/mysql-8.0.19/sql/gen_lex_token.cc index 0b1403d74..e2831989f 100644 --- a/mysql-8.0.19/sql/gen_lex_token.cc +++ b/mysql-8.0.19/sql/gen_lex_token.cc @@ -85,7 +85,7 @@ - likewise for sql/sql_hints.yy */ -int start_token_range_for_sql_hints = 1000; +int start_token_range_for_sql_hints = 1002; int start_token_range_for_digests = 1100; /* This is a tool used during build only, diff --git a/mysql-8.0.19/sql/lex.h b/mysql-8.0.19/sql/lex.h index c7741c023..62e3d7dc4 100644 --- a/mysql-8.0.19/sql/lex.h +++ b/mysql-8.0.19/sql/lex.h @@ -596,6 +596,7 @@ static const SYMBOL symbols[] = { {SYM("ROWS", ROWS_SYM)}, {SYM("ROW_FORMAT", ROW_FORMAT_SYM)}, {SYM("RTREE", RTREE_SYM)}, + {SYM("SAFE", SAFE_SYM)}, {SYM("SAVEPOINT", SAVEPOINT_SYM)}, {SYM("SCHEDULE", SCHEDULE_SYM)}, {SYM("SCHEMA", DATABASE)}, @@ -693,6 +694,7 @@ static const SYMBOL symbols[] = { {SYM("TEXT", TEXT_SYM)}, {SYM("THAN", THAN_SYM)}, {SYM("THEN", THEN_SYM)}, + {SYM("THREADS", THREADS_SYM)}, {SYM("THREAD_PRIORITY", THREAD_PRIORITY_SYM)}, {SYM("TIES", TIES_SYM)}, {SYM("TIME", TIME_SYM)}, diff --git a/mysql-8.0.19/sql/mysqld.cc b/mysql-8.0.19/sql/mysqld.cc index f9e8c4522..21d3cc707 100644 --- a/mysql-8.0.19/sql/mysqld.cc +++ b/mysql-8.0.19/sql/mysqld.cc @@ -8916,6 +8916,9 @@ SHOW_VAR status_vars[] = { {"Max_execution_time_set_failed", (char *)offsetof(System_status_var, max_execution_time_set_failed), SHOW_LONGLONG_STATUS, SHOW_SCOPE_ALL}, + {"Max_thread_id_on_kill", + (char *)offsetof(System_status_var, max_thread_id_on_kill), + SHOW_LONG_STATUS, SHOW_SCOPE_ALL}, {"Max_used_connections", (char *)&Connection_handler_manager::max_used_connections, SHOW_LONG, SHOW_SCOPE_GLOBAL}, diff --git a/mysql-8.0.19/sql/sql_class.cc b/mysql-8.0.19/sql/sql_class.cc index adf9876b5..837a45224 100644 --- a/mysql-8.0.19/sql/sql_class.cc +++ b/mysql-8.0.19/sql/sql_class.cc @@ -493,6 +493,7 @@ THD::THD(bool enable_plugins) query_name_consts = 0; db_charset = global_system_variables.collation_database; is_killable = false; + kill_self = false, binlog_evt_union.do_union = false; enable_slow_log = false; commit_error = CE_NONE; diff --git a/mysql-8.0.19/sql/sql_class.h b/mysql-8.0.19/sql/sql_class.h index e57aa7460..0554abc64 100644 --- a/mysql-8.0.19/sql/sql_class.h +++ b/mysql-8.0.19/sql/sql_class.h @@ -1037,6 +1037,14 @@ class THD : public MDL_context_owner, */ mysql_mutex_t LOCK_thd_protocol; + /** + When KILL THREADS ALL is called and a thread is executing a query or in + transaction, kill_self is set TRUE so that the thread can be killed once it + is ready (i.e., execution is complete and not in transaction). + */ + public: + bool kill_self; + /** Protects query plan (SELECT/UPDATE/DELETE's) from being freed/changed while another thread explains it. Following structures are protected by diff --git a/mysql-8.0.19/sql/sql_lex.h b/mysql-8.0.19/sql/sql_lex.h index 9172be7e8..1016dffad 100644 --- a/mysql-8.0.19/sql/sql_lex.h +++ b/mysql-8.0.19/sql/sql_lex.h @@ -3475,6 +3475,12 @@ struct LEX : public Query_tables_list { bool safe_to_cache_query; bool subqueries; + /** + Whether a KILL THREAD query includes the SAFE syntax. + When SAFE is included, a thread will only be killed when it is idle. + */ + bool is_kill_safe; + private: bool ignore; diff --git a/mysql-8.0.19/sql/sql_parse.cc b/mysql-8.0.19/sql/sql_parse.cc index 6392108fc..7997618a3 100644 --- a/mysql-8.0.19/sql/sql_parse.cc +++ b/mysql-8.0.19/sql/sql_parse.cc @@ -206,7 +206,13 @@ using std::max; ? "FUNCTION" \ : "PROCEDURE") -static void sql_kill(THD *thd, my_thread_id id, bool only_kill_query); +static uint kill_one_thread(THD *thd, my_thread_id id, bool only_kill_query, + bool is_kill_safe); + +static void sql_kill(THD *thd, my_thread_id id, bool only_kill_query, + bool is_kill_safe); + +static void sql_kill_all_threads(THD *thd, bool force); const LEX_CSTRING command_name[] = { {STRING_WITH_LEN("Sleep")}, @@ -2180,7 +2186,7 @@ bool dispatch_command(THD *thd, const COM_DATA *com_data, my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "thread_id", "mysql_kill()"); else { thd->status_var.com_stat[SQLCOM_KILL]++; - sql_kill(thd, com_data->com_kill.id, false); + sql_kill(thd, com_data->com_kill.id, false, false); } break; } @@ -4107,8 +4113,6 @@ int mysql_execute_command(THD *thd, bool first_level) { break; } case SQLCOM_KILL: { - Item *it = lex->kill_value_list.head(); - if (lex->table_or_sp_used()) { my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " @@ -4116,15 +4120,26 @@ int mysql_execute_command(THD *thd, bool first_level) { goto error; } - if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1)) { - my_error(ER_SET_CONSTANTS_ONLY, MYF(0)); - goto error; - } - - my_thread_id thread_id = static_cast(it->val_int()); if (thd->is_error()) goto error; - sql_kill(thd, thread_id, lex->type & ONLY_KILL_QUERY); + if (lex->type & KILL_ALL_THREADS) { + sql_kill_all_threads(thd, /* force= */ false); + } else if (lex->type & KILL_ALL_THREADS_FORCE) { + sql_kill_all_threads(thd, /* force= */ true); + } else { + Item *it = lex->kill_value_list.head(); + if ((!it->fixed && it->fix_fields(lex->thd, &it)) || + it->check_cols(1)) { + my_error(ER_SET_CONSTANTS_ONLY, MYF(0)); + goto error; + } + + my_thread_id thread_id = static_cast(it->val_int()); + + sql_kill(thd, thread_id, lex->type & ONLY_KILL_QUERY, + lex->is_kill_safe); + } + break; } case SQLCOM_SHOW_PRIVILEGES: { @@ -6550,12 +6565,14 @@ const CHARSET_INFO *get_bin_collation(const CHARSET_INFO *cs) { @param thd Thread class @param id Thread id @param only_kill_query Should it kill the query or the connection + @param is_kill_safe Whether the kill is safe @note This is written such that we have a short lock on LOCK_thd_list */ -static uint kill_one_thread(THD *thd, my_thread_id id, bool only_kill_query) { +static uint kill_one_thread(THD *thd, my_thread_id id, bool only_kill_query, + bool is_kill_safe) { THD *tmp = NULL; uint error = ER_NO_SUCH_THREAD; Find_thd_with_id find_thd_with_id(id); @@ -6593,6 +6610,13 @@ static uint kill_one_thread(THD *thd, my_thread_id id, bool only_kill_query) { if (tmp->killed != THD::KILL_CONNECTION) { if (tmp->is_system_user() && !thd->is_system_user()) { error = ER_KILL_DENIED_ERROR; + } else if (is_kill_safe && ((tmp->get_command() != COM_SLEEP) || + tmp->in_multi_stmt_transaction_mode())) { + /* + is_kill_safe=true, and the thread to kill is exectuing a query or in + transaction. Do not kill. + */ + error = ER_THREAD_IN_USING_OR_TRANSACTION; } else { tmp->awake(only_kill_query ? THD::KILL_QUERY : THD::KILL_CONNECTION); error = 0; @@ -6616,16 +6640,118 @@ static uint kill_one_thread(THD *thd, my_thread_id id, bool only_kill_query) { thd Thread class id Thread id only_kill_query Should it kill the query or the connection + is_kill_safe Whether the kill is safe */ -static void sql_kill(THD *thd, my_thread_id id, bool only_kill_query) { +static void sql_kill(THD *thd, my_thread_id id, bool only_kill_query, + bool is_kill_safe) { uint error; - if (!(error = kill_one_thread(thd, id, only_kill_query))) { + if (!(error = kill_one_thread(thd, id, only_kill_query, is_kill_safe))) { if (!thd->killed) my_ok(thd); } else my_error(error, MYF(0), id); } + +/** + kill all threads. + + @param thd Thread class + @param force Whether to force the kill +*/ + +static uint kill_all_threads(THD *thd, bool force) { + uint error; + + /* + To be able to acquire all (killable) threads to kill, + we need to define a custom class inheritating Do_THD_Impl and override its + operator(), then pass it to Global_THD_manager's public interface to iterate + and collect those threads. + */ + class Get_thds : public Do_THD_Impl { + public: + Get_thds(THD *thd, bool force) + : curr(thd), is_force(force), max_thread_id(thd->thread_id()) {} + + void operator()(THD *tmp) { + if (tmp->get_command() == COM_DAEMON) return; + if (tmp == curr) return; + + if (tmp->killed != THD::KILL_CONNECTION) { + if (is_force || ((tmp->get_command() == COM_SLEEP) && + !tmp->in_multi_stmt_transaction_mode())) { + if (!threads_to_kill.push_back(tmp, curr->mem_root)) + mysql_mutex_lock(&tmp->LOCK_thd_data); + } else { + tmp->kill_self = true; + } + if (max_thread_id < tmp->thread_id()) max_thread_id = tmp->thread_id(); + } + } + + List threads_to_kill; + uint64 max_thread_id; + + private: + THD *curr; + bool is_force; + }; + + DBUG_TRACE; + DBUG_PRINT("enter", ("kill all threads, force=%d", force)); + if (thd->security_context()->check_access(SUPER_ACL)) { + Get_thds get_thds(thd, force); + Global_THD_manager::get_instance()->do_for_all_thd(&get_thds); + List &threads_to_kill = get_thds.threads_to_kill; + + thd->status_var.max_thread_id_on_kill = get_thds.max_thread_id; + + if (!threads_to_kill.is_empty()) { + List_iterator_fast iter(threads_to_kill); + THD *next_to_kill; + THD *to_kill = iter++; + do { + to_kill->awake(THD::KILL_CONNECTION); + /* + Careful here: The list nodes are allocated on the memroots of the + THDs to be awakened. + But those THDs may be terminated and deleted as soon as we release + LOCK_thd_data, which will make the list nodes invalid. + Since the operation "iter++" dereferences the "next" pointer of the + previous list node, we need to do this while holding LOCK_thd_data. + */ + next_to_kill = iter++; + mysql_mutex_unlock(&to_kill->LOCK_thd_data); + } while (to_kill = next_to_kill); + } + error = 0; + } else { + error = ER_KILL_DENIED_ERROR; + } + + DEBUG_SYNC(thd, "kill_thd_end"); + DBUG_PRINT("exit", ("%d", error)); + return error; +} + +/* + kills all threads and sends response. + + @param thd Thread class + @param force Whether to force the kill +*/ + +static void sql_kill_all_threads(THD *thd, bool force) { + uint error; + if (!(error = kill_all_threads(thd, force))) { + if (!thd->killed) my_ok(thd); + } else { + my_error(error, MYF(0)); + } +} + + /** This class implements callback function used by killall_non_super_threads to kill all threads that do not have the SUPER privilege diff --git a/mysql-8.0.19/sql/sql_yacc.yy b/mysql-8.0.19/sql/sql_yacc.yy index 73ffb1582..eef067d8f 100644 --- a/mysql-8.0.19/sql/sql_yacc.yy +++ b/mysql-8.0.19/sql/sql_yacc.yy @@ -443,7 +443,7 @@ void warn_about_deprecated_binary(THD *thd) 1. We do not accept any reduce/reduce conflicts 2. We should not introduce new shift/reduce conflicts any more. */ -%expect 91 +%expect 92 /* MAINTAINER: @@ -1245,7 +1245,8 @@ void warn_about_deprecated_binary(THD *thd) %token SLAVE_PASSWORD_SYM %token SLAVE_PORT_SYM %token SLAVE_USER_SYM - +%token SAFE_SYM +%token THREADS_SYM /* Precedence rules used to resolve the ambiguity when using keywords as idents in the case e.g.: @@ -13559,7 +13560,7 @@ purge_option: /* kill threads */ kill: - KILL_SYM kill_option expr + KILL_SYM kill_option expr kill_safe { ITEMIZE($3, &$3); @@ -13568,6 +13569,20 @@ kill: lex->kill_value_list.push_front($3); lex->sql_command= SQLCOM_KILL; } + | KILL_SYM THREADS_SYM ALL + { + Lex->type= KILL_ALL_THREADS; + + LEX *lex=Lex; + lex->sql_command= SQLCOM_KILL; + } + | KILL_SYM THREADS_SYM ALL FORCE_SYM + { + Lex->type= KILL_ALL_THREADS_FORCE; + + LEX *lex=Lex; + lex->sql_command= SQLCOM_KILL; + } ; kill_option: @@ -13576,6 +13591,11 @@ kill_option: | QUERY_SYM { Lex->type= ONLY_KILL_QUERY; } ; +kill_safe: + /* empty */ { Lex->is_kill_safe= 0; } + | SAFE_SYM { Lex->is_kill_safe= 1; } + ; + /* change database */ use: @@ -14722,6 +14742,7 @@ ident_keywords_unambiguous: | ROW_COUNT_SYM | ROW_FORMAT_SYM | RTREE_SYM + | SAFE_SYM | SCHEDULE_SYM | SCHEMA_NAME_SYM | SECONDARY_ENGINE_SYM @@ -14777,6 +14798,7 @@ ident_keywords_unambiguous: | TEMPTABLE_SYM | TEXT_SYM | THAN_SYM + | THREADS_SYM | THREAD_PRIORITY_SYM | TIES_SYM | TIMESTAMP_ADD diff --git a/mysql-8.0.19/sql/system_variables.h b/mysql-8.0.19/sql/system_variables.h index 498f50194..8893ca9e1 100644 --- a/mysql-8.0.19/sql/system_variables.h +++ b/mysql-8.0.19/sql/system_variables.h @@ -484,6 +484,8 @@ struct System_status_var { /// How many queries have been executed on a secondary storage engine. ulonglong secondary_engine_execution_count; + ulonglong max_thread_id_on_kill; + ulong com_other; ulong com_stat[(uint)SQLCOM_END];