From 2eeab0b0d8e561fec1451e7f763de4526236962b Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 10 May 2006 21:50:04 +0500 Subject: [PATCH 1/2] BUG#18233 - Memory tables INDEX USING HASH (a,b) returns 1 row on SELECT WHERE a= AND b= Selecting data from memory table with varchar column and hash index over it returns only first row matched. Problem was that key length calculation for varchar columns didn't include number of bytes to store length. Fixed key length for varchar fields to include number of bytes to store length. heap/hp_create.c: Fixed key length for varchar fields to include number of bytes to store length. mysql-test/r/heap.result: Testcase for BUG#18233. mysql-test/t/heap.test: Testcase for BUG#18233. --- heap/hp_create.c | 2 ++ mysql-test/r/heap.result | 13 +++++++++++++ mysql-test/t/heap.test | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/heap/hp_create.c b/heap/hp_create.c index 9ffa4e33108..eb7a068c78b 100644 --- a/heap/hp_create.c +++ b/heap/hp_create.c @@ -85,6 +85,7 @@ int heap_create(const char *name, uint keys, HP_KEYDEF *keydef, if (!my_binary_compare(keyinfo->seg[j].charset)) keyinfo->flag|= HA_END_SPACE_KEY; keyinfo->flag|= HA_VAR_LENGTH_KEY; + length+= 2; /* Save number of bytes used to store length */ keyinfo->seg[j].bit_start= 1; break; @@ -95,6 +96,7 @@ int heap_create(const char *name, uint keys, HP_KEYDEF *keydef, if (!my_binary_compare(keyinfo->seg[j].charset)) keyinfo->flag|= HA_END_SPACE_KEY; keyinfo->flag|= HA_VAR_LENGTH_KEY; + length+= 2; /* Save number of bytes used to store length */ keyinfo->seg[j].bit_start= 2; /* diff --git a/mysql-test/r/heap.result b/mysql-test/r/heap.result index d72f5771f15..906c431b834 100644 --- a/mysql-test/r/heap.result +++ b/mysql-test/r/heap.result @@ -718,3 +718,16 @@ Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length I t1 MEMORY 10 Fixed 0 11 0 # 0 0 NULL NULL NULL NULL latin1_swedish_ci NULL t2 MEMORY 10 Fixed 0 12 0 # 0 0 NULL NULL NULL NULL latin1_swedish_ci NULL drop table t1, t2; +CREATE TABLE t1(a VARCHAR(1), b VARCHAR(2), c VARCHAR(256), +KEY(a), KEY(b), KEY(c)) ENGINE=MEMORY; +INSERT INTO t1 VALUES('a','aa',REPEAT('a', 256)),('a','aa',REPEAT('a',256)); +SELECT COUNT(*) FROM t1 WHERE a='a'; +COUNT(*) +2 +SELECT COUNT(*) FROM t1 WHERE b='aa'; +COUNT(*) +2 +SELECT COUNT(*) FROM t1 WHERE c=REPEAT('a',256); +COUNT(*) +2 +DROP TABLE t1; diff --git a/mysql-test/t/heap.test b/mysql-test/t/heap.test index 82294db336d..e501fce1eeb 100644 --- a/mysql-test/t/heap.test +++ b/mysql-test/t/heap.test @@ -458,4 +458,16 @@ create table t2 (c varchar(10)) engine=memory; show table status like 't_'; drop table t1, t2; +# +# BUG#18233 - Memory tables INDEX USING HASH (a,b) returns 1 row on +# SELECT WHERE a= AND b= +# +CREATE TABLE t1(a VARCHAR(1), b VARCHAR(2), c VARCHAR(256), + KEY(a), KEY(b), KEY(c)) ENGINE=MEMORY; +INSERT INTO t1 VALUES('a','aa',REPEAT('a', 256)),('a','aa',REPEAT('a',256)); +SELECT COUNT(*) FROM t1 WHERE a='a'; +SELECT COUNT(*) FROM t1 WHERE b='aa'; +SELECT COUNT(*) FROM t1 WHERE c=REPEAT('a',256); +DROP TABLE t1; + # End of 5.0 tests From 4f8407b6315736ab52b49e1e248e7585614c3f20 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 May 2006 15:26:23 +0200 Subject: [PATCH 2/2] 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. mysql-test/r/lock_multi.result: Bug#19815 - CREATE/RENAME/DROP DATABASE can deadlock on a global read lock The test result mysql-test/t/lock_multi.test: Bug#19815 - CREATE/RENAME/DROP DATABASE can deadlock on a global read lock The test case sql/sql_db.cc: Bug#19815 - CREATE/RENAME/DROP DATABASE can deadlock on a global read lock Exchanged the order of acquiring LOCK_mysql_create_db and wait_if_global_read_lock(). --- mysql-test/r/lock_multi.result | 8 +++++ mysql-test/t/lock_multi.test | 35 ++++++++++++++++++++ sql/sql_db.cc | 60 ++++++++++++++++++++++++++-------- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index 73e3a9d32e3..9c5652657a5 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -43,3 +43,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 diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 0d2266fc2ae..c7d2bcdfbc5 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -107,3 +107,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; + diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 4caa0076c60..a52972753a7 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -424,16 +424,27 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, my_error(ER_DB_CREATE_EXISTS, MYF(0), db); 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 */ strxmov(path, mysql_data_home, "/", db, NullS); 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, } 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); } @@ -553,12 +564,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)); + /* Check directory */ strxmov(path, mysql_data_home, "/", db, "/", MY_DB_OPT_FILE, NullS); 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) 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); } @@ -630,15 +652,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)); + (void) sprintf(path,"%s/%s",mysql_data_home,db); length= unpack_dirname(path,path); // Convert if not unix 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) exit: (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 client's selected database to nothing (to have an empty SELECT DATABASE() @@ -776,9 +808,9 @@ exit: 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); }