[Commits] Rev 4009: MDEV-5345 - Deadlock between mysql_change_user(), SHOW VARIABLES and in lp:maria/5.5

Sergey Vojtovich svoj at mariadb.org
Fri Dec 27 12:14:27 EET 2013


At lp:maria/5.5

------------------------------------------------------------
revno: 4009
revision-id: svoj at mariadb.org-20131227101419-ahpdahcundclr6gv
parent: sergii at pisem.net-20131217162654-dw2zlm3td1p12bxl
committer: Sergey Vojtovich <svoj at mariadb.org>
branch nick: 5.5-mdev5345
timestamp: Fri 2013-12-27 14:14:19 +0400
message:
  MDEV-5345 - Deadlock between mysql_change_user(), SHOW VARIABLES and
              INSTALL PLUGIN
  
  There was mixed lock order between LOCK_plugin, LOCK_global_system_variables
  and LOCK_system_variables_hash. This patch ensures that write-lock on
  LOCK_system_variables_hash doesn't intersect with LOCK_plugin.
  
  Fixed by moving initialization/deinitialization of plugin options from
  plugin_add()/plugin_del() to plugin_initialize()/plugin_deinitalize().
  So that plugin options are handled without protection of LOCK_plugin.
=== added file 'mysql-test/r/plugin_vars.result'
--- a/mysql-test/r/plugin_vars.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/plugin_vars.result	2013-12-27 10:14:19 +0000
@@ -0,0 +1,22 @@
+#
+# MDEV-5345 - Deadlock between mysql_change_user(), SHOW VARIABLES and
+#             INSTALL PLUGIN
+#
+CREATE PROCEDURE p_install(x INT)
+BEGIN
+DECLARE CONTINUE HANDLER FOR 1126 BEGIN END;
+WHILE x DO
+SET x= x - 1;
+INSTALL PLUGIN no_such_plugin SONAME 'no_such_object';
+END WHILE;
+END|
+CREATE PROCEDURE p_show_vars(x INT)
+WHILE x DO
+SET x= x - 1;
+SHOW VARIABLES;
+END WHILE|
+CALL p_install(100);;
+CALL p_show_vars(100);;
+USE test;
+DROP PROCEDURE p_install;
+DROP PROCEDURE p_show_vars;

=== added file 'mysql-test/t/plugin_vars.test'
--- a/mysql-test/t/plugin_vars.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/plugin_vars.test	2013-12-27 10:14:19 +0000
@@ -0,0 +1,56 @@
+--echo #
+--echo # MDEV-5345 - Deadlock between mysql_change_user(), SHOW VARIABLES and
+--echo #             INSTALL PLUGIN
+--echo #
+
+# Prepare test
+delimiter |;
+CREATE PROCEDURE p_install(x INT)
+BEGIN
+  DECLARE CONTINUE HANDLER FOR 1126 BEGIN END;
+  WHILE x DO
+    SET x= x - 1;
+    INSTALL PLUGIN no_such_plugin SONAME 'no_such_object';
+  END WHILE;
+END|
+
+CREATE PROCEDURE p_show_vars(x INT)
+WHILE x DO
+  SET x= x - 1;
+  SHOW VARIABLES;
+END WHILE|
+delimiter ;|
+
+connect(con1, localhost, root,,);
+connect(con2, localhost, root,,);
+
+# Start test
+connection con1;
+--send CALL p_install(100);
+
+connection con2;
+--send CALL p_show_vars(100);
+
+connection default;
+
+disable_result_log;
+let $i= 100;
+while ($i)
+{
+  change_user;
+  dec $i;
+}
+
+# Cleanup
+connection con1;
+reap;
+connection con2;
+reap;
+connection default;
+enable_result_log;
+
+disconnect con1;
+disconnect con2;
+USE test;
+DROP PROCEDURE p_install;
+DROP PROCEDURE p_show_vars;

=== modified file 'sql/sql_plugin.cc'
--- a/sql/sql_plugin.cc	2013-12-12 17:14:08 +0000
+++ b/sql/sql_plugin.cc	2013-12-27 10:14:19 +0000
@@ -192,6 +192,7 @@ mysql_mutex_t LOCK_plugin;
 static DYNAMIC_ARRAY plugin_dl_array;
 static DYNAMIC_ARRAY plugin_array;
 static HASH plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM];
+static MEM_ROOT plugin_mem_root;
 static bool reap_needed= false;
 static int plugin_array_version=0;
 
@@ -201,7 +202,7 @@ static bool initialized= 0;
   write-lock on LOCK_system_variables_hash is required before modifying
   the following variables/structures
 */
-static MEM_ROOT plugin_mem_root;
+static MEM_ROOT plugin_vars_mem_root;
 static uint global_variables_dynamic_size= 0;
 static HASH bookmark_hash;
 
@@ -297,8 +298,8 @@ class sys_var_pluginvar: public sys_var
 
 
 /* prototypes */
-static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv);
-static bool plugin_load_list(MEM_ROOT *, int *, char **, const char *);
+static void plugin_load(MEM_ROOT *tmp_root);
+static bool plugin_load_list(MEM_ROOT *, const char *);
 static int test_plugin_options(MEM_ROOT *, struct st_plugin_int *,
                                int *, char **);
 static bool register_builtin(struct st_maria_plugin *, struct st_plugin_int *,
@@ -1038,8 +1039,7 @@ static st_plugin_int *plugin_insert_or_r
     Requires that a write-lock is held on LOCK_system_variables_hash
 */
 static bool plugin_add(MEM_ROOT *tmp_root,
-                       const LEX_STRING *name, LEX_STRING *dl,
-                       int *argc, char **argv, int report)
+                       const LEX_STRING *name, LEX_STRING *dl, int report)
 {
   struct st_plugin_int tmp;
   struct st_maria_plugin *plugin;
@@ -1102,15 +1102,9 @@ static bool plugin_add(MEM_ROOT *tmp_roo
       tmp.ref_count= 0;
       tmp.state= PLUGIN_IS_UNINITIALIZED;
       tmp.load_option= PLUGIN_ON;
-      if (test_plugin_options(tmp_root, &tmp, argc, argv))
-        tmp.state= PLUGIN_IS_DISABLED;
 
       if (!(tmp_plugin_ptr= plugin_insert_or_reuse(&tmp)))
-      {
-        mysql_del_sys_var_chain(tmp.system_vars);
-        restore_pluginvar_names(tmp.system_vars);
         goto err;
-      }
       plugin_array_version++;
       if (my_hash_insert(&plugin_hash[plugin->type], (uchar*)tmp_plugin_ptr))
         tmp_plugin_ptr->state= PLUGIN_IS_FREED;
@@ -1196,6 +1190,11 @@ static void plugin_deinitialize(struct s
   if (ref_check && plugin->ref_count)
     sql_print_error("Plugin '%s' has ref_count=%d after deinitialization.",
                     plugin->name.str, plugin->ref_count);
+
+  mysql_rwlock_wrlock(&LOCK_system_variables_hash);
+  mysql_del_sys_var_chain(plugin->system_vars);
+  mysql_rwlock_unlock(&LOCK_system_variables_hash);
+  restore_pluginvar_names(plugin->system_vars);
 }
 
 static void plugin_del(struct st_plugin_int *plugin)
@@ -1203,10 +1202,6 @@ static void plugin_del(struct st_plugin_
   DBUG_ENTER("plugin_del");
   mysql_mutex_assert_owner(&LOCK_plugin);
   /* Free allocated strings before deleting the plugin. */
-  mysql_rwlock_wrlock(&LOCK_system_variables_hash);
-  mysql_del_sys_var_chain(plugin->system_vars);
-  mysql_rwlock_unlock(&LOCK_system_variables_hash);
-  restore_pluginvar_names(plugin->system_vars);
   plugin_vars_free_values(plugin->system_vars);
   my_hash_delete(&plugin_hash[plugin->plugin->type], (uchar*)plugin);
   if (plugin->plugin_dl)
@@ -1342,7 +1337,8 @@ void plugin_unlock_list(THD *thd, plugin
 }
 
 
-static int plugin_initialize(struct st_plugin_int *plugin)
+static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin,
+                             int *argc, char **argv, bool initialize)
 {
   int ret= 1;
   DBUG_ENTER("plugin_initialize");
@@ -1352,6 +1348,18 @@ static int plugin_initialize(struct st_p
   DBUG_ASSERT(state == PLUGIN_IS_UNINITIALIZED);
 
   mysql_mutex_unlock(&LOCK_plugin);
+
+  mysql_rwlock_wrlock(&LOCK_system_variables_hash);
+  if (test_plugin_options(tmp_root, plugin, argc, argv))
+    state= PLUGIN_IS_DISABLED;
+  mysql_rwlock_unlock(&LOCK_system_variables_hash);
+
+  if (!initialize || state == PLUGIN_IS_DISABLED)
+  {
+    ret= 0;
+    goto err;
+  }
+
   if (plugin_type_initialize[plugin->plugin->type])
   {
     if ((*plugin_type_initialize[plugin->plugin->type])(plugin))
@@ -1413,6 +1421,14 @@ static int plugin_initialize(struct st_p
   ret= 0;
 
 err:
+  if (ret)
+  {
+    mysql_rwlock_wrlock(&LOCK_system_variables_hash);
+    mysql_del_sys_var_chain(plugin->system_vars);
+    mysql_rwlock_unlock(&LOCK_system_variables_hash);
+    restore_pluginvar_names(plugin->system_vars);
+  }
+
   mysql_mutex_lock(&LOCK_plugin);
   plugin->state= state;
 
@@ -1508,6 +1524,7 @@ int plugin_init(int *argc, char **argv,
 #endif
 
   init_alloc_root(&plugin_mem_root, 4096, 4096);
+  init_alloc_root(&plugin_vars_mem_root, 4096, 4096);
   init_alloc_root(&tmp_root, 4096, 4096);
 
   if (my_hash_init(&bookmark_hash, &my_charset_bin, 16, 0, 0,
@@ -1575,10 +1592,7 @@ int plugin_init(int *argc, char **argv,
       }
 
       free_root(&tmp_root, MYF(MY_MARK_BLOCKS_FREE));
-      if (test_plugin_options(&tmp_root, &tmp, argc, argv))
-        tmp.state= PLUGIN_IS_DISABLED;
-      else
-        tmp.state= PLUGIN_IS_UNINITIALIZED;
+      tmp.state= PLUGIN_IS_UNINITIALIZED;
       if (register_builtin(plugin, &tmp, &plugin_ptr))
         goto err_unlock;
 
@@ -1593,15 +1607,12 @@ int plugin_init(int *argc, char **argv,
         mysqld --help for all other users, we will only initialize
         MyISAM here.
       */
-      if (!(flags & PLUGIN_INIT_SKIP_INITIALIZATION) || is_myisam)
+      if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, is_myisam ||
+                            !(flags & PLUGIN_INIT_SKIP_INITIALIZATION)))
       {
-        if (plugin_ptr->state == PLUGIN_IS_UNINITIALIZED &&
-            plugin_initialize(plugin_ptr))
-        {
-          if (mandatory)
-            goto err_unlock;
-          plugin_ptr->state= PLUGIN_IS_DISABLED;
-        }
+        if (mandatory)
+          goto err_unlock;
+        plugin_ptr->state= PLUGIN_IS_DISABLED;
       }
 
       /*
@@ -1627,14 +1638,11 @@ int plugin_init(int *argc, char **argv,
   if (!(flags & PLUGIN_INIT_SKIP_DYNAMIC_LOADING))
   {
     if (opt_plugin_load)
-      plugin_load_list(&tmp_root, argc, argv, opt_plugin_load);
+      plugin_load_list(&tmp_root, opt_plugin_load);
     if (!(flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE))
-      plugin_load(&tmp_root, argc, argv);
+      plugin_load(&tmp_root);
   }
 
-  if (flags & PLUGIN_INIT_SKIP_INITIALIZATION)
-    goto end;
-
   /*
     Now we initialize all remaining plugins
   */
@@ -1646,9 +1654,10 @@ int plugin_init(int *argc, char **argv,
   for (i= 0; i < plugin_array.elements; i++)
   {
     plugin_ptr= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
-    if (plugin_ptr->state == PLUGIN_IS_UNINITIALIZED)
+    if (plugin_ptr->plugin_dl && plugin_ptr->state == PLUGIN_IS_UNINITIALIZED)
     {
-      if (plugin_initialize(plugin_ptr))
+      if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv,
+                            !(flags & PLUGIN_INIT_SKIP_INITIALIZATION)))
       {
         plugin_ptr->state= PLUGIN_IS_DYING;
         *(reap++)= plugin_ptr;
@@ -1675,7 +1684,6 @@ int plugin_init(int *argc, char **argv,
   if (reaped_mandatory_plugin)
     goto err;
 
-end:
   free_root(&tmp_root, MYF(0));
 
   DBUG_RETURN(0);
@@ -1714,7 +1722,7 @@ static bool register_builtin(struct st_m
 /*
   called only by plugin_init()
 */
-static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv)
+static void plugin_load(MEM_ROOT *tmp_root)
 {
   THD thd;
   TABLE_LIST tables;
@@ -1786,7 +1794,7 @@ static void plugin_load(MEM_ROOT *tmp_ro
       the mutex here to satisfy the assert
     */
     mysql_mutex_lock(&LOCK_plugin);
-    if (plugin_add(tmp_root, &name, &dl, argc, argv, REPORT_TO_LOG))
+    if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG))
       sql_print_warning("Couldn't load plugin named '%s' with soname '%s'.",
                         str_name.c_ptr(), str_dl.c_ptr());
     free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
@@ -1807,8 +1815,7 @@ static void plugin_load(MEM_ROOT *tmp_ro
 /*
   called only by plugin_init()
 */
-static bool plugin_load_list(MEM_ROOT *tmp_root, int *argc, char **argv,
-                             const char *list)
+static bool plugin_load_list(MEM_ROOT *tmp_root, const char *list)
 {
   char buffer[FN_REFLEN];
   LEX_STRING name= {buffer, 0}, dl= {NULL, 0}, *str= &name;
@@ -1843,14 +1850,14 @@ static bool plugin_load_list(MEM_ROOT *t
         mysql_mutex_lock(&LOCK_plugin);
         free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
         name.str= 0; // load everything
-        if (plugin_add(tmp_root, &name, &dl, argc, argv, REPORT_TO_LOG))
+        if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG))
           goto error;
       }
       else
       {
         free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
         mysql_mutex_lock(&LOCK_plugin);
-        if (plugin_add(tmp_root, &name, &dl, argc, argv, REPORT_TO_LOG))
+        if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG))
           goto error;
       }
       mysql_mutex_unlock(&LOCK_plugin);
@@ -2008,6 +2015,7 @@ void plugin_shutdown(void)
 
   my_hash_free(&bookmark_hash);
   free_root(&plugin_mem_root, MYF(0));
+  free_root(&plugin_vars_mem_root, MYF(0));
 
   global_variables_dynamic_size= 0;
 
@@ -2019,28 +2027,22 @@ void plugin_shutdown(void)
 
   That is, initialize it, and update mysql.plugin table
 */
-static bool finalize_install(THD *thd, TABLE *table, const LEX_STRING *name)
+static bool finalize_install(THD *thd, TABLE *table, const LEX_STRING *name,
+                             int *argc, char **argv)
 {
   struct st_plugin_int *tmp= plugin_find_internal(name, MYSQL_ANY_PLUGIN);
   int error;
   DBUG_ASSERT(tmp);
   mysql_mutex_assert_owner(&LOCK_plugin); // because of tmp->state
 
-  if (tmp->state == PLUGIN_IS_DISABLED)
-  {
-    if (global_system_variables.log_warnings)
-      push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
-                          ER_CANT_INITIALIZE_UDF, ER(ER_CANT_INITIALIZE_UDF),
-                          name->str, "Plugin is disabled");
-  }
-  else if (tmp->state != PLUGIN_IS_UNINITIALIZED)
+  if (tmp->state != PLUGIN_IS_UNINITIALIZED)
   {
     /* already installed */
     return 0;
   }
   else
   {
-    if (plugin_initialize(tmp))
+    if (plugin_initialize(thd->mem_root, tmp, argc, argv, true))
     {
       report_error(REPORT_TO_USER, ER_CANT_INITIALIZE_UDF, name->str,
                    "Plugin initialization function failed.");
@@ -2048,6 +2050,13 @@ static bool finalize_install(THD *thd, T
       return 1;
     }
   }
+  if (tmp->state == PLUGIN_IS_DISABLED)
+  {
+    if (global_system_variables.log_warnings)
+      push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+                          ER_CANT_INITIALIZE_UDF, ER(ER_CANT_INITIALIZE_UDF),
+                          name->str, "Plugin is disabled");
+  }
 
   /*
     We do not replicate the INSTALL PLUGIN statement. Disable binlogging
@@ -2097,6 +2106,12 @@ bool mysql_install_plugin(THD *thd, cons
                              MYSQL_LOCK_IGNORE_TIMEOUT)))
     DBUG_RETURN(TRUE);
 
+  if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL))
+  {
+    report_error(REPORT_TO_USER, ER_PLUGIN_IS_NOT_LOADED, name->str);
+    DBUG_RETURN(TRUE);
+  }
+
   /*
     Pre-acquire audit plugins for events that may potentially occur
     during [UN]INSTALL PLUGIN.
@@ -2123,23 +2138,12 @@ bool mysql_install_plugin(THD *thd, cons
   mysql_audit_acquire_plugins(thd, event_class_mask);
 
   mysql_mutex_lock(&LOCK_plugin);
-  mysql_rwlock_wrlock(&LOCK_system_variables_hash);
-
-  if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL))
-  {
-    report_error(REPORT_TO_USER, ER_PLUGIN_IS_NOT_LOADED, name->str);
-    goto err;
-  }
-  error= plugin_add(thd->mem_root, name, &dl, &argc, argv, REPORT_TO_USER);
-  if (argv)
-    free_defaults(argv);
-  mysql_rwlock_unlock(&LOCK_system_variables_hash);
-
+  error= plugin_add(thd->mem_root, name, &dl, REPORT_TO_USER);
   if (error)
     goto err;
 
   if (name->str)
-    error= finalize_install(thd, table, name);
+    error= finalize_install(thd, table, name, &argc, argv);
   else
   {
     st_plugin_dl *plugin_dl= plugin_dl_find(&dl);
@@ -2147,22 +2151,20 @@ bool mysql_install_plugin(THD *thd, cons
     for (plugin= plugin_dl->plugins; plugin->info; plugin++)
     {
       LEX_STRING str= { const_cast<char*>(plugin->name), strlen(plugin->name) };
-      error|= finalize_install(thd, table, &str);
+      error|= finalize_install(thd, table, &str, &argc, argv);
     }
   }
 
   if (error)
-    goto deinit;
-
-  mysql_mutex_unlock(&LOCK_plugin);
-  DBUG_RETURN(FALSE);
-
-deinit:
-  reap_needed= true;
-  reap_plugins();
+  {
+    reap_needed= true;
+    reap_plugins();
+  }
 err:
   mysql_mutex_unlock(&LOCK_plugin);
-  DBUG_RETURN(TRUE);
+  if (argv)
+    free_defaults(argv);
+  DBUG_RETURN(error);
 }
 
 
@@ -2817,7 +2819,7 @@ static st_bookmark *register_var(const c
 
   if (!(result= find_bookmark(NULL, varname + 1, flags)))
   {
-    result= (st_bookmark*) alloc_root(&plugin_mem_root,
+    result= (st_bookmark*) alloc_root(&plugin_vars_mem_root,
                                       sizeof(struct st_bookmark) + length-1);
     varname[0]= plugin_var_bookmark_key(flags);
     memcpy(result->key, varname, length);
@@ -3806,7 +3808,7 @@ static int test_plugin_options(MEM_ROOT
   enum_plugin_load_option plugin_load_option= tmp->load_option;
 
   MEM_ROOT *mem_root= alloc_root_inited(&tmp->mem_root) ?
-                      &tmp->mem_root : &plugin_mem_root;
+                      &tmp->mem_root : &plugin_vars_mem_root;
   st_mysql_sys_var **opt;
   my_option *opts= NULL;
   LEX_STRING plugin_name;



More information about the commits mailing list