Loading mysql-test/r/lock_multi.result +8 −0 Original line number Diff line number Diff line Loading @@ -50,3 +50,11 @@ Field Type Null Key Default Extra a int(11) YES NULL unlock tables; drop table t1; CREATE DATABASE mysqltest_1; FLUSH TABLES WITH READ LOCK; DROP DATABASE mysqltest_1; DROP DATABASE mysqltest_1; ERROR HY000: Can't execute the query because you have a conflicting read lock UNLOCK TABLES; DROP DATABASE mysqltest_1; ERROR HY000: Can't drop database 'mysqltest_1'; database doesn't exist mysql-test/t/lock_multi.test +35 −0 Original line number Diff line number Diff line Loading @@ -126,3 +126,38 @@ show columns from t1; connection locker; unlock tables; drop table t1; # # Bug#19815 - CREATE/RENAME/DROP DATABASE can deadlock on a global read lock # connect (con1,localhost,root,,); connect (con2,localhost,root,,); # connection con1; CREATE DATABASE mysqltest_1; FLUSH TABLES WITH READ LOCK; # # With bug in place: acquire LOCK_mysql_create_table and # wait in wait_if_global_read_lock(). connection con2; send DROP DATABASE mysqltest_1; --sleep 1 # # With bug in place: try to acquire LOCK_mysql_create_table... # When fixed: Reject dropping db because of the read lock. connection con1; --error ER_CANT_UPDATE_WITH_READLOCK DROP DATABASE mysqltest_1; UNLOCK TABLES; # connection con2; reap; # connection default; disconnect con1; disconnect con2; # This must have been dropped by connection 2 already, # which waited until the global read lock was released. --error ER_DB_DROP_EXISTS DROP DATABASE mysqltest_1; sql/sql_db.cc +46 −14 Original line number Diff line number Diff line Loading @@ -539,15 +539,26 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, DBUG_RETURN(-1); } VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* do not create database if another thread is holding read lock */ /* Do not create database if another thread is holding read lock. Wait for global read lock before acquiring LOCK_mysql_create_db. After wait_if_global_read_lock() we have protection against another global read lock. If we would acquire LOCK_mysql_create_db first, another thread could step in and get the global read lock before we reach wait_if_global_read_lock(). If this thread tries the same as we (admin a db), it would then go and wait on LOCK_mysql_create_db... Furthermore wait_if_global_read_lock() checks if the current thread has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ if (wait_if_global_read_lock(thd, 0, 1)) { error= -1; goto exit2; } VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* Check directory */ path_len= build_table_filename(path, sizeof(path), db, "", ""); path[path_len-1]= 0; // Remove last '/' from path Loading Loading @@ -655,9 +666,9 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, } exit: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); start_waiting_global_read_lock(thd); exit2: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); DBUG_RETURN(error); } Loading @@ -671,12 +682,23 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) int error= 0; DBUG_ENTER("mysql_alter_db"); VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* do not alter database if another thread is holding read lock */ /* Do not alter database if another thread is holding read lock. Wait for global read lock before acquiring LOCK_mysql_create_db. After wait_if_global_read_lock() we have protection against another global read lock. If we would acquire LOCK_mysql_create_db first, another thread could step in and get the global read lock before we reach wait_if_global_read_lock(). If this thread tries the same as we (admin a db), it would then go and wait on LOCK_mysql_create_db... Furthermore wait_if_global_read_lock() checks if the current thread has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ if ((error=wait_if_global_read_lock(thd,0,1))) goto exit2; VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* Recreate db options file: /dbpath/.db.opt We pass MY_DB_OPT_FILE as "extension" to avoid Loading Loading @@ -721,9 +743,9 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) send_ok(thd, result); exit: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); start_waiting_global_read_lock(thd); exit2: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); DBUG_RETURN(error); } Loading Loading @@ -755,15 +777,26 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) TABLE_LIST* dropped_tables= 0; DBUG_ENTER("mysql_rm_db"); VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* do not drop database if another thread is holding read lock */ /* Do not drop database if another thread is holding read lock. Wait for global read lock before acquiring LOCK_mysql_create_db. After wait_if_global_read_lock() we have protection against another global read lock. If we would acquire LOCK_mysql_create_db first, another thread could step in and get the global read lock before we reach wait_if_global_read_lock(). If this thread tries the same as we (admin a db), it would then go and wait on LOCK_mysql_create_db... Furthermore wait_if_global_read_lock() checks if the current thread has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ if (wait_if_global_read_lock(thd, 0, 1)) { error= -1; goto exit2; } VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); length= build_table_filename(path, sizeof(path), db, "", ""); strmov(path+length, MY_DB_OPT_FILE); // Append db option file name del_dbopt(path); // Remove dboption hash entry Loading Loading @@ -872,7 +905,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) exit: (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ (void)evex_drop_db_events(thd, db); /* QQ Ignore errors for now */ start_waiting_global_read_lock(thd); /* If this database was the client's selected database, we silently change the client's selected database to nothing (to have an empty SELECT DATABASE() Loading Loading @@ -901,9 +933,9 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) thd->db= 0; thd->db_length= 0; } exit2: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); start_waiting_global_read_lock(thd); exit2: DBUG_RETURN(error); } Loading Loading
mysql-test/r/lock_multi.result +8 −0 Original line number Diff line number Diff line Loading @@ -50,3 +50,11 @@ Field Type Null Key Default Extra a int(11) YES NULL unlock tables; drop table t1; CREATE DATABASE mysqltest_1; FLUSH TABLES WITH READ LOCK; DROP DATABASE mysqltest_1; DROP DATABASE mysqltest_1; ERROR HY000: Can't execute the query because you have a conflicting read lock UNLOCK TABLES; DROP DATABASE mysqltest_1; ERROR HY000: Can't drop database 'mysqltest_1'; database doesn't exist
mysql-test/t/lock_multi.test +35 −0 Original line number Diff line number Diff line Loading @@ -126,3 +126,38 @@ show columns from t1; connection locker; unlock tables; drop table t1; # # Bug#19815 - CREATE/RENAME/DROP DATABASE can deadlock on a global read lock # connect (con1,localhost,root,,); connect (con2,localhost,root,,); # connection con1; CREATE DATABASE mysqltest_1; FLUSH TABLES WITH READ LOCK; # # With bug in place: acquire LOCK_mysql_create_table and # wait in wait_if_global_read_lock(). connection con2; send DROP DATABASE mysqltest_1; --sleep 1 # # With bug in place: try to acquire LOCK_mysql_create_table... # When fixed: Reject dropping db because of the read lock. connection con1; --error ER_CANT_UPDATE_WITH_READLOCK DROP DATABASE mysqltest_1; UNLOCK TABLES; # connection con2; reap; # connection default; disconnect con1; disconnect con2; # This must have been dropped by connection 2 already, # which waited until the global read lock was released. --error ER_DB_DROP_EXISTS DROP DATABASE mysqltest_1;
sql/sql_db.cc +46 −14 Original line number Diff line number Diff line Loading @@ -539,15 +539,26 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, DBUG_RETURN(-1); } VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* do not create database if another thread is holding read lock */ /* Do not create database if another thread is holding read lock. Wait for global read lock before acquiring LOCK_mysql_create_db. After wait_if_global_read_lock() we have protection against another global read lock. If we would acquire LOCK_mysql_create_db first, another thread could step in and get the global read lock before we reach wait_if_global_read_lock(). If this thread tries the same as we (admin a db), it would then go and wait on LOCK_mysql_create_db... Furthermore wait_if_global_read_lock() checks if the current thread has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ if (wait_if_global_read_lock(thd, 0, 1)) { error= -1; goto exit2; } VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* Check directory */ path_len= build_table_filename(path, sizeof(path), db, "", ""); path[path_len-1]= 0; // Remove last '/' from path Loading Loading @@ -655,9 +666,9 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, } exit: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); start_waiting_global_read_lock(thd); exit2: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); DBUG_RETURN(error); } Loading @@ -671,12 +682,23 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) int error= 0; DBUG_ENTER("mysql_alter_db"); VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* do not alter database if another thread is holding read lock */ /* Do not alter database if another thread is holding read lock. Wait for global read lock before acquiring LOCK_mysql_create_db. After wait_if_global_read_lock() we have protection against another global read lock. If we would acquire LOCK_mysql_create_db first, another thread could step in and get the global read lock before we reach wait_if_global_read_lock(). If this thread tries the same as we (admin a db), it would then go and wait on LOCK_mysql_create_db... Furthermore wait_if_global_read_lock() checks if the current thread has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ if ((error=wait_if_global_read_lock(thd,0,1))) goto exit2; VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* Recreate db options file: /dbpath/.db.opt We pass MY_DB_OPT_FILE as "extension" to avoid Loading Loading @@ -721,9 +743,9 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) send_ok(thd, result); exit: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); start_waiting_global_read_lock(thd); exit2: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); DBUG_RETURN(error); } Loading Loading @@ -755,15 +777,26 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) TABLE_LIST* dropped_tables= 0; DBUG_ENTER("mysql_rm_db"); VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* do not drop database if another thread is holding read lock */ /* Do not drop database if another thread is holding read lock. Wait for global read lock before acquiring LOCK_mysql_create_db. After wait_if_global_read_lock() we have protection against another global read lock. If we would acquire LOCK_mysql_create_db first, another thread could step in and get the global read lock before we reach wait_if_global_read_lock(). If this thread tries the same as we (admin a db), it would then go and wait on LOCK_mysql_create_db... Furthermore wait_if_global_read_lock() checks if the current thread has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ if (wait_if_global_read_lock(thd, 0, 1)) { error= -1; goto exit2; } VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); length= build_table_filename(path, sizeof(path), db, "", ""); strmov(path+length, MY_DB_OPT_FILE); // Append db option file name del_dbopt(path); // Remove dboption hash entry Loading Loading @@ -872,7 +905,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) exit: (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ (void)evex_drop_db_events(thd, db); /* QQ Ignore errors for now */ start_waiting_global_read_lock(thd); /* If this database was the client's selected database, we silently change the client's selected database to nothing (to have an empty SELECT DATABASE() Loading Loading @@ -901,9 +933,9 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) thd->db= 0; thd->db_length= 0; } exit2: VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); start_waiting_global_read_lock(thd); exit2: DBUG_RETURN(error); } Loading