Commit d00441e3 authored by ingo@mysql.com's avatar ingo@mysql.com

Bug#19815 - CREATE/RENAME/DROP DATABASE can deadlock on a global read lock

The order of acquiring LOCK_mysql_create_db
and wait_if_global_read_lock() was wrong. It could happen
that a thread held LOCK_mysql_create_db while waiting for
the global read lock to be released. The thread with the
global read lock could try to administrate a database too.
It would first try to lock LOCK_mysql_create_db and hang...

The check if the current thread has the global read lock
is done in wait_if_global_read_lock(), which could not be
reached because of the hang in LOCK_mysql_create_db.

Now I exchanged the order of acquiring LOCK_mysql_create_db
and wait_if_global_read_lock(). This makes 
wait_if_global_read_lock() fail with an error message for
the thread with the global read lock. No deadlock happens.
parent da1fdb8a
...@@ -43,3 +43,11 @@ Field Type Null Key Default Extra ...@@ -43,3 +43,11 @@ Field Type Null Key Default Extra
a int(11) YES NULL a int(11) YES NULL
unlock tables; unlock tables;
drop table t1; 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
...@@ -107,3 +107,38 @@ show columns from t1; ...@@ -107,3 +107,38 @@ show columns from t1;
connection locker; connection locker;
unlock tables; unlock tables;
drop table t1; 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;
...@@ -425,15 +425,26 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, ...@@ -425,15 +425,26 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info,
DBUG_RETURN(-1); 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)) if (wait_if_global_read_lock(thd, 0, 1))
{ {
error= -1; error= -1;
goto exit2; goto exit2;
} }
VOID(pthread_mutex_lock(&LOCK_mysql_create_db));
/* Check directory */ /* Check directory */
strxmov(path, mysql_data_home, "/", db, NullS); strxmov(path, mysql_data_home, "/", db, NullS);
path_len= unpack_dirname(path,path); // Convert if not unix path_len= unpack_dirname(path,path); // Convert if not unix
...@@ -537,9 +548,9 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, ...@@ -537,9 +548,9 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info,
} }
exit: exit:
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
start_waiting_global_read_lock(thd); start_waiting_global_read_lock(thd);
exit2: exit2:
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
DBUG_RETURN(error); DBUG_RETURN(error);
} }
...@@ -553,12 +564,23 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) ...@@ -553,12 +564,23 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info)
int error= 0; int error= 0;
DBUG_ENTER("mysql_alter_db"); 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))) if ((error=wait_if_global_read_lock(thd,0,1)))
goto exit2; goto exit2;
VOID(pthread_mutex_lock(&LOCK_mysql_create_db));
/* Check directory */ /* Check directory */
strxmov(path, mysql_data_home, "/", db, "/", MY_DB_OPT_FILE, NullS); strxmov(path, mysql_data_home, "/", db, "/", MY_DB_OPT_FILE, NullS);
fn_format(path, path, "", "", MYF(MY_UNPACK_FILENAME)); fn_format(path, path, "", "", MYF(MY_UNPACK_FILENAME));
...@@ -596,9 +618,9 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) ...@@ -596,9 +618,9 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info)
send_ok(thd, result); send_ok(thd, result);
exit: exit:
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
start_waiting_global_read_lock(thd); start_waiting_global_read_lock(thd);
exit2: exit2:
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
DBUG_RETURN(error); DBUG_RETURN(error);
} }
...@@ -630,15 +652,26 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) ...@@ -630,15 +652,26 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
TABLE_LIST* dropped_tables= 0; TABLE_LIST* dropped_tables= 0;
DBUG_ENTER("mysql_rm_db"); 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)) if (wait_if_global_read_lock(thd, 0, 1))
{ {
error= -1; error= -1;
goto exit2; goto exit2;
} }
VOID(pthread_mutex_lock(&LOCK_mysql_create_db));
(void) sprintf(path,"%s/%s",mysql_data_home,db); (void) sprintf(path,"%s/%s",mysql_data_home,db);
length= unpack_dirname(path,path); // Convert if not unix length= unpack_dirname(path,path); // Convert if not unix
strmov(path+length, MY_DB_OPT_FILE); // Append db option file name strmov(path+length, MY_DB_OPT_FILE); // Append db option file name
...@@ -747,7 +780,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) ...@@ -747,7 +780,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
exit: exit:
(void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ (void)sp_drop_db_routines(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 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() client's selected database to nothing (to have an empty SELECT DATABASE()
...@@ -776,9 +808,9 @@ exit: ...@@ -776,9 +808,9 @@ exit:
thd->db= 0; thd->db= 0;
thd->db_length= 0; thd->db_length= 0;
} }
exit2:
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db)); VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
start_waiting_global_read_lock(thd);
exit2:
DBUG_RETURN(error); DBUG_RETURN(error);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment