From 207f091bce19e00f17d6ae158ed16575c34793e5 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Wed, 30 May 2012 15:07:57 -0700 Subject: [PATCH] Bug#65469: Infinite loop when opening a corrupted table If a corrupted page is read when calculating statistics for a table, InnoDB will forever keep trying to read the page. If the page cannot be read after a fixed number of attempts, abort the server. The table might be accessed later by forcing InnoDB recovery (e.g. innodb_force_recovery = 6). --- .../suite/innodb/r/innodb_corrupt_table.result | 16 ++++ .../suite/innodb/t/innodb_corrupt_table-master.opt | 1 + .../suite/innodb/t/innodb_corrupt_table.test | 74 ++++++++++++++++++++ storage/innobase/buf/buf0buf.c | 10 ++- storage/innobase/buf/buf0rea.c | 13 +++- storage/innobase/include/buf0buf.h | 2 +- 6 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/innodb/r/innodb_corrupt_table.result create mode 100644 mysql-test/suite/innodb/t/innodb_corrupt_table-master.opt create mode 100644 mysql-test/suite/innodb/t/innodb_corrupt_table.test diff --git a/mysql-test/suite/innodb/r/innodb_corrupt_table.result b/mysql-test/suite/innodb/r/innodb_corrupt_table.result new file mode 100644 index 0000000..08dc118 --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_corrupt_table.result @@ -0,0 +1,16 @@ +CALL mtr.add_suppression("InnoDB: Error: Unable to read tablespace .* page no .* into the buffer pool after 100 attempts"); +# Create and populate the table to be corrupted +CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b TEXT) ENGINE=InnoDB; +INSERT INTO t1 (b) VALUES ('corrupt me'); +INSERT INTO t1 (b) VALUES ('corrupt me'); +# Restart server to flush buffers +# Corrupt the table +Munged a string. +Munged a string. +# Write file to make mysql-test-run.pl expect crash and restart +SELECT * FROM t1; +ERROR HY000: Lost connection to MySQL server during query +# Turn on reconnect +# Wait for server to fully start +# Cleanup +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb_corrupt_table-master.opt b/mysql-test/suite/innodb/t/innodb_corrupt_table-master.opt new file mode 100644 index 0000000..6b82bac --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_corrupt_table-master.opt @@ -0,0 +1 @@ +--innodb_file_per_table=1 --skip-stack-trace --skip-core-file diff --git a/mysql-test/suite/innodb/t/innodb_corrupt_table.test b/mysql-test/suite/innodb/t/innodb_corrupt_table.test new file mode 100644 index 0000000..1c426bf --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_corrupt_table.test @@ -0,0 +1,74 @@ +# +# Test opening a corrupted table. +# + +# Don't test under valgrind, memory leaks will occur +source include/not_valgrind.inc; +# Avoid CrashReporter popup on Mac +source include/not_crashrep.inc; +# Don't test under embedded +source include/not_embedded.inc; +# Require InnoDB +source include/have_innodb.inc; + +CALL mtr.add_suppression("InnoDB: Error: Unable to read tablespace .* page no .* into the buffer pool after 100 attempts"); + +--echo # Create and populate the table to be corrupted +CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b TEXT) ENGINE=InnoDB; +INSERT INTO t1 (b) VALUES ('corrupt me'); +--disable_query_log +--let $i = 10 +while ($i) +{ + INSERT INTO t1 (b) VALUES (REPEAT('abcdefghijklmnopqrstuvwxyz', 100)); + dec $i; +} +--enable_query_log +INSERT INTO t1 (b) VALUES ('corrupt me'); + +--echo # Restart server to flush buffers +source include/restart_mysqld.inc; + +--echo # Corrupt the table +let $MYSQLD_DATADIR=`select @@datadir`; +let t1_IBD = $MYSQLD_DATADIR/test/t1.ibd; + +perl; +use strict; +use warnings; +use Fcntl qw(:DEFAULT :seek); + +my $ibd_file = $ENV{'t1_IBD'}; + +my $chunk; +my $len; + +sysopen IBD_FILE, $ibd_file, O_RDWR || die "Unable to open $ibd_file"; + +while ($len = sysread IBD_FILE, $chunk, 1024) +{ + if ($chunk =~ s/corrupt me/korrupt me/) + { + print "Munged a string.\n"; + sysseek IBD_FILE, -$len, SEEK_CUR; + syswrite IBD_FILE, $chunk, $len; + } +} + +close IBD_FILE; +EOF + +--echo # Write file to make mysql-test-run.pl expect crash and restart +--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +--error 2013 +SELECT * FROM t1; + +--echo # Turn on reconnect +--enable_reconnect + +--echo # Wait for server to fully start +--source include/wait_until_connected_again.inc + +--echo # Cleanup +DROP TABLE t1; diff --git a/storage/innobase/buf/buf0buf.c b/storage/innobase/buf/buf0buf.c index f990cd1..3ae0649 100644 --- a/storage/innobase/buf/buf0buf.c +++ b/storage/innobase/buf/buf0buf.c @@ -3464,9 +3464,11 @@ buf_mark_space_corrupt( /********************************************************************//** Completes an asynchronous read or write request of a file page to or from -the buffer pool. */ +the buffer pool. +@return FALSE if the read or write request completed successfully, TRUE +otherwise (page is corrupted). */ UNIV_INTERN -void +ibool buf_page_io_complete( /*=================*/ buf_page_t* bpage) /*!< in: pointer to the block in question */ @@ -3593,7 +3595,7 @@ corrupt: table as corrupted instead of crashing server */ if (bpage->space > TRX_SYS_SPACE && buf_mark_space_corrupt(bpage)) { - return; + return(TRUE); } else { fputs("InnoDB: Ending processing" " because of" @@ -3683,6 +3685,8 @@ corrupt: mutex_exit(buf_page_get_mutex(bpage)); buf_pool_mutex_exit(buf_pool); + + return(FALSE); } /*********************************************************************//** diff --git a/storage/innobase/buf/buf0rea.c b/storage/innobase/buf/buf0rea.c index b3b4b52..b3db50e 100644 --- a/storage/innobase/buf/buf0rea.c +++ b/storage/innobase/buf/buf0rea.c @@ -67,7 +67,8 @@ buf_read_page_low( /*==============*/ ulint* err, /*!< out: DB_SUCCESS or DB_TABLESPACE_DELETED if we are trying to read from a non-existent tablespace, or a - tablespace which is just now being dropped */ + tablespace which is just now being dropped, or + DB_CORRUPTION if the page is corrupted */ ibool sync, /*!< in: TRUE if synchronous aio is desired */ ulint mode, /*!< in: BUF_READ_IBUF_PAGES_ONLY, ..., ORed to OS_AIO_SIMULATED_WAKE_LATER (see below @@ -158,7 +159,11 @@ buf_read_page_low( if (sync) { /* The i/o is already completed when we arrive from fil_read */ - buf_page_io_complete(bpage); + if (buf_page_io_complete(bpage)) { + /* Page corruption on disk. */ + + *err = DB_CORRUPTION; + } } return(1); @@ -365,6 +370,10 @@ buf_read_page( "InnoDB: but the tablespace does not exist" " or is just being dropped.\n", (ulong) space, (ulong) offset); + } else if (err == DB_CORRUPTION) { + /* Page has not been read in if it's corrupted. */ + + count = 0; } /* Flush pages from the end of the LRU list if necessary */ diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index 6daade1..1f1c563 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -1181,7 +1181,7 @@ buf_page_init_for_read( Completes an asynchronous read or write request of a file page to or from the buffer pool. */ UNIV_INTERN -void +ibool buf_page_io_complete( /*=================*/ buf_page_t* bpage); /*!< in: pointer to the block in question */ -- 1.7.4.4