diff --git a/mysql-test/r/plugin.result b/mysql-test/r/plugin.result index 2b96dc4f3ac..03a1ab70278 100644 --- a/mysql-test/r/plugin.result +++ b/mysql-test/r/plugin.result @@ -143,3 +143,73 @@ SELECT @@SESSION.example_double_thdvar; @@SESSION.example_double_thdvar 1000.500000 UNINSTALL PLUGIN example; +# +# BUG#18008907 - DEADLOCK BETWEEN MYSQL_CHANGE_USER(), SHOW VARIABLES AND INSTALL PLUGIN +# +CREATE PROCEDURE p_install() +BEGIN +INSTALL PLUGIN no_such_plugin SONAME 'no_such_object'; +END +| +CREATE PROCEDURE p_show_vars() +BEGIN +SELECT COUNT(*) FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES; +END| +connect con1, localhost, root,,; +# Case 18008907_1: Deadlock situation cause by +# con1: has LOCK_system_variables_hash and waits on LOCK_plugin AND +# default: has LOCK_plugin and waits on LOCK_system_variables_hash. +# +SET DEBUG_SYNC='acquired_LOCK_system_variables_hash SIGNAL install_plugin WAIT_FOR cont_show_vars'; +call p_show_vars();; +connection default; +SET DEBUG_SYNC='now WAIT_FOR install_plugin'; +SET DEBUG_SYNC='acquired_LOCK_plugin SIGNAL cont_show_vars'; +call p_install();; +connection con1; +COUNT(*) +# +SET DEBUG_SYNC='RESET'; +connection default; +ERROR HY000: Can't open shared library +SET DEBUG_SYNC='RESET'; +# Case 18008907_2: Deadlock situation caused by +# default: has LOCK_system_variables_hash and waits on LOCK_global_system_variables, +# con1: has LOCK_plugin and waits on LOCK_system_variables_hash AND +# con2: has LOCK_global_system_variables and waits on LOCK_plugin. +SET DEBUG_SYNC='acquired_LOCK_system_variables_hash SIGNAL install_plugin WAIT_FOR nothing TIMEOUT 10'; +call p_show_vars();; +connection con1; +SET DEBUG_SYNC='now WAIT_FOR install_plugin'; +SET DEBUG_SYNC='acquired_LOCK_plugin SIGNAL create_connection WAIT_FOR nothing TIMEOUT 10'; +call p_install();; +connect con2, localhost, root,,; +SET DEBUG_SYNC='now WAIT_FOR create_connection'; +connection con1; +ERROR HY000: Can't open shared library +connection default; +COUNT(*) +# +Warnings: +# 1639 debug sync point wait timed out +disconnect con2; +# Case 18008907_3: Testing Concurrent "Show Variables" and "Plugin Uninstall" operations. +INSTALL PLUGIN example SONAME 'ha_example.so'; +connection con1; +SET DEBUG_SYNC='acquired_LOCK_system_variables_hash SIGNAL uninstall_plugin WAIT_FOR go'; +call p_show_vars();; +connect con2, localhost, root,,; +SET DEBUG_SYNC='now WAIT_FOR uninstall_plugin'; +UNINSTALL PLUGIN example;; +connection default; +SET DEBUG_SYNC='now SIGNAL go'; +connection con1; +COUNT(*) +# +connection con2; +connection default; +DROP PROCEDURE p_show_vars; +DROP PROCEDURE p_install; +SET DEBUG_SYNC='RESET'; +disconnect con1; +disconnect con2; diff --git a/mysql-test/t/plugin.test b/mysql-test/t/plugin.test index f3a3b0cc068..c9220651fa8 100644 --- a/mysql-test/t/plugin.test +++ b/mysql-test/t/plugin.test @@ -1,5 +1,7 @@ +# --source include/not_windows_embedded.inc --source include/have_example_plugin.inc +--source include/have_debug_sync.inc CREATE TABLE t1(a int) ENGINE=EXAMPLE; DROP TABLE t1; @@ -137,3 +139,118 @@ SET SESSION example_double_thdvar = 1000.51; SELECT @@SESSION.example_double_thdvar; UNINSTALL PLUGIN example; + +--echo # +--echo # BUG#18008907 - DEADLOCK BETWEEN MYSQL_CHANGE_USER(), SHOW VARIABLES AND INSTALL PLUGIN +--echo # + +--enable_connect_log + +delimiter |; + +CREATE PROCEDURE p_install() +BEGIN + INSTALL PLUGIN no_such_plugin SONAME 'no_such_object'; +END +| + +CREATE PROCEDURE p_show_vars() +BEGIN + SELECT COUNT(*) FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES; +END| + +delimiter ;| + +connect(con1, localhost, root,,); + +--echo # Case 18008907_1: Deadlock situation cause by +--echo # con1: has LOCK_system_variables_hash and waits on LOCK_plugin AND +--echo # default: has LOCK_plugin and waits on LOCK_system_variables_hash. +--echo # +SET DEBUG_SYNC='acquired_LOCK_system_variables_hash SIGNAL install_plugin WAIT_FOR cont_show_vars'; +--send call p_show_vars(); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR install_plugin'; +SET DEBUG_SYNC='acquired_LOCK_plugin SIGNAL cont_show_vars'; +--send call p_install(); + +connection con1; +# Without fix, reap will hang. +--replace_column 1 # +--reap; +SET DEBUG_SYNC='RESET'; + +connection default; +--replace_regex /(Can\'t open shared library).*$/\1/ +--error ER_CANT_OPEN_LIBRARY +--reap; +SET DEBUG_SYNC='RESET'; + +--echo # Case 18008907_2: Deadlock situation caused by +--echo # default: has LOCK_system_variables_hash and waits on LOCK_global_system_variables, +--echo # con1: has LOCK_plugin and waits on LOCK_system_variables_hash AND +--echo # con2: has LOCK_global_system_variables and waits on LOCK_plugin. + +SET DEBUG_SYNC='acquired_LOCK_system_variables_hash SIGNAL install_plugin WAIT_FOR nothing TIMEOUT 10'; +--send call p_show_vars(); + +connection con1; +SET DEBUG_SYNC='now WAIT_FOR install_plugin'; +SET DEBUG_SYNC='acquired_LOCK_plugin SIGNAL create_connection WAIT_FOR nothing TIMEOUT 10'; +--send call p_install(); + +connect(con2, localhost, root,,); +SET DEBUG_SYNC='now WAIT_FOR create_connection'; +# Without fix, deadlock situation will occur on timeout of debug_syncs in +# default and con1. Because of this, change_user operation hangs. +change_user; + +connection con1; +--replace_regex /(Can\'t open shared library).*$/\1/ +--error ER_CANT_OPEN_LIBRARY +--reap; + +connection default; +--replace_column 1 # +--reap; +disconnect con2; + +--echo # Case 18008907_3: Testing Concurrent "Show Variables" and "Plugin Uninstall" operations. + +#Installing plugin +--replace_regex /\.dll/.so/ +eval INSTALL PLUGIN example SONAME '$EXAMPLE_PLUGIN'; + +connection con1; +#Acquiring LOCK_system_variables_hash and LOCK_plugin_delete +SET DEBUG_SYNC='acquired_LOCK_system_variables_hash SIGNAL uninstall_plugin WAIT_FOR go'; +--send call p_show_vars(); + +connect(con2, localhost, root,,); +SET DEBUG_SYNC='now WAIT_FOR uninstall_plugin'; +--send UNINSTALL PLUGIN example; + +connection default; +#Plugin Uninstall operation will wait until show variables operations releases LOCK_plugin_delete. +let $wait_condition= SELECT count(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE INFO LIKE 'UNINSTALL PLUGIN example' and + STATE LIKE 'Waiting on cond'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL go'; + +connection con1; +--replace_column 1 # +--reap + +connection con2; +--reap + +connection default; +DROP PROCEDURE p_show_vars; +DROP PROCEDURE p_install; +SET DEBUG_SYNC='RESET'; +disconnect con1; +disconnect con2; + +--disable_connect_log diff --git a/scripts/mysqlaccess.conf b/scripts/mysqlaccess.conf old mode 100644 new mode 100755 diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 9851cb2739a..9ec0390483a 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -33,6 +33,7 @@ #include "sql_audit.h" #include #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT +#include "debug_sync.h" #define REPORT_TO_LOG 1 #define REPORT_TO_USER 2 @@ -131,6 +132,12 @@ static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= #include "sql_plugin_services.h" +/* + A mutex LOCK_plugin_delete must be acquired before calling plugin_del + function. +*/ +mysql_mutex_t LOCK_plugin_delete; + /* A mutex LOCK_plugin must be acquired before accessing the following variables/structures. @@ -949,6 +956,7 @@ static void plugin_del(struct st_plugin_int *plugin) { DBUG_ENTER("plugin_del(plugin)"); mysql_mutex_assert_owner(&LOCK_plugin); + mysql_mutex_assert_owner(&LOCK_plugin_delete); /* Free allocated strings before deleting the plugin. */ mysql_rwlock_wrlock(&LOCK_system_variables_hash); mysql_del_sys_var_chain(plugin->system_vars); @@ -996,11 +1004,14 @@ static void reap_plugins(void) while ((plugin= *(--list))) plugin_deinitialize(plugin, true); + mysql_mutex_lock(&LOCK_plugin_delete); mysql_mutex_lock(&LOCK_plugin); while ((plugin= *(--reap))) plugin_del(plugin); + mysql_mutex_unlock(&LOCK_plugin_delete); + my_afree(reap); } @@ -1205,10 +1216,12 @@ static inline void convert_underscore_to_dash(char *str, int len) #ifdef HAVE_PSI_INTERFACE static PSI_mutex_key key_LOCK_plugin; +static PSI_mutex_key key_LOCK_plugin_delete; static PSI_mutex_info all_plugin_mutexes[]= { - { &key_LOCK_plugin, "LOCK_plugin", PSI_FLAG_GLOBAL} + { &key_LOCK_plugin, "LOCK_plugin", PSI_FLAG_GLOBAL}, + { &key_LOCK_plugin_delete, "LOCK_plugin_delete", PSI_FLAG_GLOBAL} }; static void init_plugin_psi_keys(void) @@ -1259,6 +1272,7 @@ int plugin_init(int *argc, char **argv, int flags) mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_plugin_delete, &LOCK_plugin_delete, MY_MUTEX_INIT_FAST); if (my_init_dynamic_array(&plugin_dl_array, sizeof(struct st_plugin_dl *),16,16) || @@ -1401,8 +1415,10 @@ int plugin_init(int *argc, char **argv, int flags) plugin_ptr->load_option == PLUGIN_FORCE_PLUS_PERMANENT) reaped_mandatory_plugin= TRUE; plugin_deinitialize(plugin_ptr, true); + mysql_mutex_lock(&LOCK_plugin_delete); mysql_mutex_lock(&LOCK_plugin); plugin_del(plugin_ptr); + mysql_mutex_unlock(&LOCK_plugin_delete); } mysql_mutex_unlock(&LOCK_plugin); @@ -1692,10 +1708,11 @@ void plugin_shutdown(void) } /* - It's perfectly safe not to lock LOCK_plugin, as there're no - concurrent threads anymore. But some functions called from here - use mysql_mutex_assert_owner(), so we lock the mutex to satisfy it + It's perfectly safe not to lock LOCK_plugin, LOCK_plugin_delete, as + there're no concurrent threads anymore. But some functions called from + here use mysql_mutex_assert_owner(), so we lock the mutex to satisfy it */ + mysql_mutex_lock(&LOCK_plugin_delete); mysql_mutex_lock(&LOCK_plugin); /* @@ -1718,9 +1735,11 @@ void plugin_shutdown(void) cleanup_variables(NULL, &global_system_variables); cleanup_variables(NULL, &max_system_variables); mysql_mutex_unlock(&LOCK_plugin); + mysql_mutex_unlock(&LOCK_plugin_delete); initialized= 0; mysql_mutex_destroy(&LOCK_plugin); + mysql_mutex_destroy(&LOCK_plugin_delete); my_afree(plugins); } @@ -1795,6 +1814,7 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl mysql_audit_acquire_plugins(thd, MYSQL_AUDIT_GENERAL_CLASS); mysql_mutex_lock(&LOCK_plugin); + DEBUG_SYNC(thd, "acquired_LOCK_plugin"); mysql_rwlock_wrlock(&LOCK_system_variables_hash); if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL)) diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h index da59e6ee58a..848b608b64e 100644 --- a/sql/sql_plugin.h +++ b/sql/sql_plugin.h @@ -40,6 +40,8 @@ extern const char *global_plugin_typelib_names[]; #include +extern mysql_mutex_t LOCK_plugin_delete; + #ifdef DBUG_OFF #define plugin_ref_to_int(A) A #define plugin_int_to_ref(A) A diff --git a/sql/sql_show.cc b/sql/sql_show.cc index f02ea6bead7..ccbf1f41126 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -6292,6 +6292,7 @@ int fill_open_tables(THD *thd, TABLE_LIST *tables, COND *cond) int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond) { DBUG_ENTER("fill_variables"); + SHOW_VAR *sys_var_array; int res= 0; LEX *lex= thd->lex; const char *wild= lex->wild ? lex->wild->ptr() : NullS; @@ -6305,10 +6306,21 @@ int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond) schema_table_idx == SCH_GLOBAL_VARIABLES) option_type= OPT_GLOBAL; + /* + Lock LOCK_plugin_delete to avoid deletion of any plugins while creating + SHOW_VAR array and hold it until all variables are stored in the table. + */ + mysql_mutex_lock(&LOCK_plugin_delete); + // Lock LOCK_system_variables_hash to prepare SHOW_VARs array. mysql_rwlock_rdlock(&LOCK_system_variables_hash); - res= show_status_array(thd, wild, enumerate_sys_vars(thd, sorted_vars, option_type), - option_type, NULL, "", tables->table, upper_case_names, cond); + DEBUG_SYNC(thd, "acquired_LOCK_system_variables_hash"); + sys_var_array= enumerate_sys_vars(thd, sorted_vars, option_type); mysql_rwlock_unlock(&LOCK_system_variables_hash); + + res= show_status_array(thd, wild, sys_var_array, option_type, NULL, "", + tables->table, upper_case_names, cond); + + mysql_mutex_unlock(&LOCK_plugin_delete); DBUG_RETURN(res); }