Commit 013b3d8a authored by unknown's avatar unknown
Browse files

Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash

Version for 5.0.
It fixes three problems:
1. The cause of the bug was that we did not check the table version for
 the HANDLER ... READ commands. We did not notice when a table was
 replaced by a new one. This can happen during ALTER TABLE, REPAIR
 TABLE, and OPTIMIZE TABLE (there might be more cases). I call the fix
 for this problem "the primary bug fix".
2. mysql_ha_flush() was not always called with a locked LOCK_open.
 Though the function comment clearly said it must.
 I changed the code so that the locking is done when required. I call
 the fix for this problem "the secondary fix".
3. In 5.0 (not in 4.1 or 4.0) DROP TABLE had a possible deadlock flaw in
 concur with FLUSH TABLES WITH READ LOCK. I call the fix for this
 problem "the 5.0 addendum fix".


include/my_pthread.h:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  Added a new macro for the 5.0 addendum fix.
mysql-test/r/handler.result:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  The test result.
mysql-test/t/handler.test:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  The test case.
sql/lock.cc:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  Changed a comment which did confuse me and which is not fully
  correct anymore after the 5.0 addendum fix.
  Added an assertion which would fire without the 5.0 addendum fix.
sql/mysql_priv.h:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  Changed a definition for the secondary fix.
sql/sql_base.cc:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  Changed function calls for the secondary fix.
sql/sql_class.cc:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  Changed a function call for the secondary fix.
sql/sql_handler.cc:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  The first two diffs make the primary bug fix.
  The rest is for the secondary fix.
sql/sql_table.cc:
  Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
  The first diff (four changed places) make the 5.0 addendum fix.
  The other three are changed function calls for the secondary fix.
parent d9c7aaf2
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -536,9 +536,15 @@ void safe_mutex_end(FILE *file);
#define pthread_cond_timedwait(A,B,C) safe_cond_timedwait((A),(B),(C),__FILE__,__LINE__)
#define pthread_mutex_trylock(A) pthread_mutex_lock(A)
#define pthread_mutex_t safe_mutex_t
#define safe_mutex_assert_owner(mp) DBUG_ASSERT((mp)->count > 0 && pthread_equal(pthread_self(),(mp)->thread))
#define safe_mutex_assert_owner(mp) \
          DBUG_ASSERT((mp)->count > 0 && \
                      pthread_equal(pthread_self(), (mp)->thread))
#define safe_mutex_assert_not_owner(mp) \
          DBUG_ASSERT(! (mp)->count || \
                      ! pthread_equal(pthread_self(), (mp)->thread))
#else
#define safe_mutex_assert_owner(mp)
#define safe_mutex_assert_not_owner(mp)
#endif /* SAFE_MUTEX */

	/* READ-WRITE thread locking */
+37 −0
Original line number Diff line number Diff line
@@ -445,3 +445,40 @@ drop table t2;
drop table t3;
drop table t4;
drop table t5;
create table t1 (c1 int);
insert into t1 values (1);
handler t1 open;
handler t1 read first;
c1
1
send the below to another connection, do not wait for the result
 optimize table t1;
proceed with the normal connection
handler t1 read next;
c1
1
handler t1 close;
read the result from the other connection
Table	Op	Msg_type	Msg_text
test.t1	optimize	status	OK
proceed with the normal connection
drop table t1;
create table t1 (c1 int);
insert into t1 values (14397);
flush tables with read lock;
drop table t1;
ERROR HY000: Can't execute the query because you have a conflicting read lock
send the below to another connection, do not wait for the result
 drop table t1;
proceed with the normal connection
select * from t1;
c1
14397
unlock tables;
read the result from the other connection
proceed with the normal connection
select * from t1;
ERROR 42S02: Table 'test.t1' doesn't exist
drop table if exists t1;
Warnings:
Note	1051	Unknown table 't1'
+75 −0
Original line number Diff line number Diff line
@@ -347,4 +347,79 @@ drop table t3;
drop table t4;
drop table t5;

#
# Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
#
create table t1 (c1 int);
insert into t1 values (1);
# client 1
handler t1 open;
handler t1 read first;
# client 2
connect (con2,localhost,root,,);
connection con2;
--exec echo send the below to another connection, do not wait for the result
send optimize table t1;
--sleep 1
# client 1
--exec echo proceed with the normal connection
connection default;
handler t1 read next;
handler t1 close;
# client 2
--exec echo read the result from the other connection
connection con2;
reap;
# client 1
--exec echo proceed with the normal connection
connection default;
drop table t1;

# End of 4.1 tests

#
# Addendum to Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
# Show that DROP TABLE can no longer deadlock against
# FLUSH TABLES WITH READ LOCK. This is a 5.0 issue.
#
create table t1 (c1 int);
insert into t1 values (14397);
flush tables with read lock;
# The thread with the global read lock cannot drop the table itself:
--error 1223
drop table t1;
#
# client 2
# We need a second connection to try the drop.
# The drop waits for the global read lock to go away.
# Without the addendum fix it locked LOCK_open before entering the wait loop.
connection con2;
--exec echo send the below to another connection, do not wait for the result
send drop table t1;
--sleep 1
#
# client 1
# Now we need something that wants LOCK_open. A simple table access which
# opens the table does the trick.
--exec echo proceed with the normal connection
connection default;
# This would hang on LOCK_open without the 5.0 addendum fix.
select * from t1;
# Release the read lock. This should make the DROP go through.
unlock tables;
#
# client 2
# Read the result of the drop command.
connection con2;
--exec echo read the result from the other connection
reap;
#
# client 1
# Now back to normal operation. The table should not exist any more.
--exec echo proceed with the normal connection
connection default;
--error 1146
select * from t1;
# Just to be sure and not confuse the next test case writer.
drop table if exists t1;
+14 −4
Original line number Diff line number Diff line
@@ -815,10 +815,13 @@ static void print_lock_error(int error, const char *table)

  access to them is protected with a mutex LOCK_global_read_lock

  (XXX: one should never take LOCK_open if LOCK_global_read_lock is taken,
  otherwise a deadlock may occur - see mysql_rm_table. Other mutexes could
  be a problem too - grep the code for global_read_lock if you want to use
  any other mutex here)
  (XXX: one should never take LOCK_open if LOCK_global_read_lock is
  taken, otherwise a deadlock may occur. Other mutexes could be a
  problem too - grep the code for global_read_lock if you want to use
  any other mutex here) Also one must not hold LOCK_open when calling
  wait_if_global_read_lock(). When the thread with the global read lock
  tries to close its tables, it needs to take LOCK_open in
  close_thread_table().

  How blocking of threads by global read lock is achieved: that's
  advisory. Any piece of code which should be blocked by global read lock must
@@ -937,6 +940,13 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
  DBUG_ENTER("wait_if_global_read_lock");

  LINT_INIT(old_message);
  /*
    Assert that we do not own LOCK_open. If we would own it, other
    threads could not close their tables. This would make a pretty
    deadlock.
  */
  safe_mutex_assert_not_owner(&LOCK_open);

  (void) pthread_mutex_lock(&LOCK_global_read_lock);
  if ((need_exit_cond= must_wait))
  {
+2 −1
Original line number Diff line number Diff line
@@ -890,7 +890,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen);
bool mysql_ha_close(THD *thd, TABLE_LIST *tables);
bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *,
                   List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows);
int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags);
int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags,
                   bool is_locked);
/* mysql_ha_flush mode_flags bits */
#define MYSQL_HA_CLOSE_FINAL        0x00
#define MYSQL_HA_REOPEN_ON_USAGE    0x01
Loading