MDEV-31851 After crash recovery, undo tablespace fails to open

Problem:
========
- InnoDB fails to open undo tablespace when page0 is corrupted
and fails to throw error.

Solution:
=========
- InnoDB throws DB_CORRUPTION error when InnoDB encounters
page0 corruption of undo tablespace.

- InnoDB restores the page0 of undo tablespace from
doublewrite buffer if it encounters page corruption

- Moved Datafile::restore_from_doublewrite() to
recv_dblwr_t::restore_first_page(). So that undo
tablespace and system tablespace can use this function
instead of duplicating the code

srv_undo_tablespace_open(): Returns 0 if file doesn't exist
or ULINT_UNDEFINED if page0 is corrupted.
parent ea0b1ccd
call mtr.add_suppression("Checksum mismatch in the first page of file");
show variables like 'innodb_doublewrite';
Variable_name Value
innodb_doublewrite ON
create table t1(f1 int not null, f2 int not null)engine=innodb;
insert into t1 values (1, 1);
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
InnoDB 0 transactions not purged
set GLOBAL innodb_log_checkpoint_now=1;
# Make the first page dirty for undo tablespace
set global innodb_saved_page_number_debug = 0;
set global innodb_fil_make_page_dirty_debug = 1;
SET GLOBAL innodb_max_dirty_pages_pct_lwm=0.0;
SET GLOBAL innodb_max_dirty_pages_pct=0.0;
# Kill the server
# restart
FOUND 1 /Checksum mismatch in the first page of file/ in mysqld.1.err
check table t1;
Table Op Msg_type Msg_text
test.t1 check status OK
drop table t1;
--innodb_undo_tablespaces=3
--innodb_sys_tablespaces
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/not_embedded.inc
call mtr.add_suppression("Checksum mismatch in the first page of file");
let INNODB_PAGE_SIZE=`select @@innodb_page_size`;
let MYSQLD_DATADIR=`select @@datadir`;
show variables like 'innodb_doublewrite';
create table t1(f1 int not null, f2 int not null)engine=innodb;
insert into t1 values (1, 1);
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
--source include/wait_all_purged.inc
set GLOBAL innodb_log_checkpoint_now=1;
--source ../include/no_checkpoint_start.inc
--echo # Make the first page dirty for undo tablespace
set global innodb_saved_page_number_debug = 0;
set global innodb_fil_make_page_dirty_debug = 1;
SET GLOBAL innodb_max_dirty_pages_pct_lwm=0.0;
SET GLOBAL innodb_max_dirty_pages_pct=0.0;
sleep 1;
--let CLEANUP_IF_CHECKPOINT=drop table t1;
--source ../include/no_checkpoint_end.inc
perl;
use IO::Handle;
my $fname= "$ENV{'MYSQLD_DATADIR'}/undo001";
my $page_size = $ENV{INNODB_PAGE_SIZE};
die unless open(FILE, "+<", $fname);
sysread(FILE, $page, $page_size)==$page_size||die "Unable to read $name\n";
substr($page, 49, 4) = pack("N", 1000);
sysseek(FILE, 0, 0)||die "Unable to seek $fname\n";
die unless syswrite(FILE, $page, $page_size) == $page_size;
close FILE;
EOF
--source include/start_mysqld.inc
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
let SEARCH_PATTERN= Checksum mismatch in the first page of file;
--source include/search_pattern_in_file.inc
check table t1;
drop table t1;
...@@ -350,7 +350,7 @@ void buf_dblwr_t::recover() ...@@ -350,7 +350,7 @@ void buf_dblwr_t::recover()
{ {
byte *page= *i; byte *page= *i;
const uint32_t page_no= page_get_page_no(page); const uint32_t page_no= page_get_page_no(page);
if (!page_no) /* recovered via Datafile::restore_from_doublewrite() */ if (!page_no) /* recovered via recv_dblwr_t::restore_first_page() */
continue; continue;
const lsn_t lsn= mach_read_from_8(page + FIL_PAGE_LSN); const lsn_t lsn= mach_read_from_8(page + FIL_PAGE_LSN);
......
...@@ -481,7 +481,8 @@ Datafile::validate_for_recovery() ...@@ -481,7 +481,8 @@ Datafile::validate_for_recovery()
return(err); return(err);
} }
if (restore_from_doublewrite()) { if (recv_sys.dblwr.restore_first_page(
m_space_id, m_filepath, m_handle)) {
return(DB_CORRUPTION); return(DB_CORRUPTION);
} }
...@@ -768,61 +769,6 @@ Datafile::find_space_id() ...@@ -768,61 +769,6 @@ Datafile::find_space_id()
return(DB_CORRUPTION); return(DB_CORRUPTION);
} }
/** Restore the first page of the tablespace from
the double write buffer.
@return whether the operation failed */
bool
Datafile::restore_from_doublewrite()
{
if (srv_operation > SRV_OPERATION_EXPORT_RESTORED) {
return true;
}
/* Find if double write buffer contains page_no of given space id. */
const page_id_t page_id(m_space_id, 0);
const byte* page = recv_sys.dblwr.find_page(page_id);
if (!page) {
/* If the first page of the given user tablespace is not there
in the doublewrite buffer, then the recovery is going to fail
now. Hence this is treated as an error. */
ib::error()
<< "Corrupted page " << page_id
<< " of datafile '" << m_filepath
<< "' could not be found in the doublewrite buffer.";
return(true);
}
ulint flags = mach_read_from_4(
FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page);
if (!fil_space_t::is_valid_flags(flags, m_space_id)) {
flags = fsp_flags_convert_from_101(flags);
/* recv_dblwr_t::validate_page() inside find_page()
checked this already. */
ut_ad(flags != ULINT_UNDEFINED);
/* The flags on the page should be converted later. */
}
ulint physical_size = fil_space_t::physical_size(flags);
ut_a(page_get_page_no(page) == page_id.page_no());
ib::info() << "Restoring page " << page_id
<< " of datafile '" << m_filepath
<< "' from the doublewrite buffer. Writing "
<< physical_size << " bytes into file '"
<< m_filepath << "'";
return(os_file_write(
IORequestWrite,
m_filepath, m_handle, page, 0, physical_size)
!= DB_SUCCESS);
}
/** Create a link filename based on the contents of m_name, /** Create a link filename based on the contents of m_name,
open that file, and read the contents into m_filepath. open that file, and read the contents into m_filepath.
@retval DB_SUCCESS if remote linked tablespace file is opened and read. @retval DB_SUCCESS if remote linked tablespace file is opened and read.
......
...@@ -594,7 +594,9 @@ SysTablespace::read_lsn_and_check_flags(lsn_t* flushed_lsn) ...@@ -594,7 +594,9 @@ SysTablespace::read_lsn_and_check_flags(lsn_t* flushed_lsn)
if (err != DB_SUCCESS if (err != DB_SUCCESS
&& (retry == 1 && (retry == 1
|| it->restore_from_doublewrite())) { || recv_sys.dblwr.restore_first_page(
it->m_space_id, it->m_filepath,
it->handle()))) {
it->close(); it->close();
......
...@@ -18298,6 +18298,7 @@ checkpoint_now_set(THD*, st_mysql_sys_var*, void*, const void* save) ...@@ -18298,6 +18298,7 @@ checkpoint_now_set(THD*, st_mysql_sys_var*, void*, const void* save)
while (log_sys.last_checkpoint_lsn.load( while (log_sys.last_checkpoint_lsn.load(
std::memory_order_acquire) std::memory_order_acquire)
+ SIZE_OF_FILE_CHECKPOINT + SIZE_OF_FILE_CHECKPOINT
+ log_sys.framing_size()
< (lsn= log_sys.get_lsn(std::memory_order_acquire))) { < (lsn= log_sys.get_lsn(std::memory_order_acquire))) {
log_make_checkpoint(); log_make_checkpoint();
log_sys.log.flush(); log_sys.log.flush();
......
...@@ -423,11 +423,6 @@ class Datafile { ...@@ -423,11 +423,6 @@ class Datafile {
else DB_ERROR. */ else DB_ERROR. */
dberr_t find_space_id(); dberr_t find_space_id();
/** Restore the first page of the tablespace from
the double write buffer.
@return whether the operation failed */
bool restore_from_doublewrite();
/** Points into m_filepath to the file name with extension */ /** Points into m_filepath to the file name with extension */
char* m_filename; char* m_filename;
......
...@@ -134,6 +134,15 @@ struct recv_dblwr_t ...@@ -134,6 +134,15 @@ struct recv_dblwr_t
byte* find_page(const page_id_t page_id, const fil_space_t *space= NULL, byte* find_page(const page_id_t page_id, const fil_space_t *space= NULL,
byte *tmp_buf= NULL); byte *tmp_buf= NULL);
/** Restore the first page of the given tablespace from
doublewrite buffer.
@param space_id tablespace identifier
@param name tablespace filepath
@param file tablespace file handle
@return whether the operation failed */
bool restore_first_page(
ulint space_id, const char *name, os_file_t file);
typedef std::deque<byte*, ut_allocator<byte*> > list; typedef std::deque<byte*, ut_allocator<byte*> > list;
/** Recovered doublewrite buffer page frames */ /** Recovered doublewrite buffer page frames */
......
...@@ -3819,6 +3819,11 @@ byte *recv_dblwr_t::find_page(const page_id_t page_id, ...@@ -3819,6 +3819,11 @@ byte *recv_dblwr_t::find_page(const page_id_t page_id,
if (page_get_page_no(page) != page_id.page_no() || if (page_get_page_no(page) != page_id.page_no() ||
page_get_space_id(page) != page_id.space()) page_get_space_id(page) != page_id.space())
continue; continue;
uint32_t flags= mach_read_from_4(
FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page);
if (!fil_space_t::is_valid_flags(flags, page_id.space()))
continue;
const lsn_t lsn= mach_read_from_8(page + FIL_PAGE_LSN); const lsn_t lsn= mach_read_from_8(page + FIL_PAGE_LSN);
if (lsn <= max_lsn || if (lsn <= max_lsn ||
!validate_page(page_id, page, space, tmp_buf)) !validate_page(page_id, page, space, tmp_buf))
...@@ -3827,9 +3832,38 @@ byte *recv_dblwr_t::find_page(const page_id_t page_id, ...@@ -3827,9 +3832,38 @@ byte *recv_dblwr_t::find_page(const page_id_t page_id,
memset(page + FIL_PAGE_LSN, 0, 8); memset(page + FIL_PAGE_LSN, 0, 8);
continue; continue;
} }
ut_a(page_get_page_no(page) == page_id.page_no());
max_lsn= lsn; max_lsn= lsn;
result= page; result= page;
} }
return result; return result;
} }
bool recv_dblwr_t::restore_first_page(ulint space_id, const char *name,
os_file_t file)
{
const page_id_t page_id(space_id, 0);
const byte* page= find_page(page_id);
if (!page)
{
/* If the first page of the given user tablespace is not there
in the doublewrite buffer, then the recovery is going to fail
now. Hence this is treated as error. */
ib::error()
<< "Corrupted page " << page_id << " of datafile '"
<< name <<"' could not be found in the doublewrite buffer.";
return true;
}
ulint physical_size= fil_space_t::physical_size(
mach_read_from_4(page + FSP_HEADER_OFFSET + FSP_SPACE_FLAGS));
ib::info() << "Restoring page " << page_id << " of datafile '"
<< name << "' from the doublewrite buffer. Writing "
<< physical_size << " bytes into file '" << name << "'";
return os_file_write(
IORequestWrite, name, file, page, 0, physical_size) !=
DB_SUCCESS;
}
...@@ -484,7 +484,8 @@ static ulint trx_rseg_get_n_undo_tablespaces() ...@@ -484,7 +484,8 @@ static ulint trx_rseg_get_n_undo_tablespaces()
@param[in] name tablespace file name @param[in] name tablespace file name
@param[in] i undo tablespace count @param[in] i undo tablespace count
@return undo tablespace identifier @return undo tablespace identifier
@retval 0 on failure */ @retval 0 if file doesn't exist
@retval ULINT_UNDEFINED if page0 is corrupted */
static ulint srv_undo_tablespace_open(bool create, const char* name, ulint i) static ulint srv_undo_tablespace_open(bool create, const char* name, ulint i)
{ {
bool success; bool success;
...@@ -523,13 +524,13 @@ static ulint srv_undo_tablespace_open(bool create, const char* name, ulint i) ...@@ -523,13 +524,13 @@ static ulint srv_undo_tablespace_open(bool create, const char* name, ulint i)
{ {
page_t *page= static_cast<byte*>(aligned_malloc(srv_page_size, page_t *page= static_cast<byte*>(aligned_malloc(srv_page_size,
srv_page_size)); srv_page_size));
dberr_t err= os_file_read(IORequestRead, fh, page, 0, srv_page_size); if (os_file_read(IORequestRead, fh, page, 0, srv_page_size) !=
if (err != DB_SUCCESS) DB_SUCCESS)
{ {
err_exit: err_exit:
ib::error() << "Unable to read first page of file " << name; ib::error() << "Unable to read first page of file " << name;
aligned_free(page); aligned_free(page);
return err; return ULINT_UNDEFINED;
} }
uint32_t id= mach_read_from_4(FIL_PAGE_SPACE_ID + page); uint32_t id= mach_read_from_4(FIL_PAGE_SPACE_ID + page);
...@@ -538,19 +539,18 @@ static ulint srv_undo_tablespace_open(bool create, const char* name, ulint i) ...@@ -538,19 +539,18 @@ static ulint srv_undo_tablespace_open(bool create, const char* name, ulint i)
FSP_HEADER_OFFSET + FSP_SPACE_ID + page, 4)) FSP_HEADER_OFFSET + FSP_SPACE_ID + page, 4))
{ {
ib::error() << "Inconsistent tablespace ID in file " << name; ib::error() << "Inconsistent tablespace ID in file " << name;
err= DB_CORRUPTION;
goto err_exit; goto err_exit;
} }
space_id= id;
fsp_flags= mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page); fsp_flags= mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + page);
if (buf_page_is_corrupted(false, page, fsp_flags)) if (buf_page_is_corrupted(false, page, fsp_flags))
{ {
ib::error() << "Checksum mismatch in the first page of file " << name; ib::error() << "Checksum mismatch in the first page of file " << name;
err= DB_CORRUPTION; if (recv_sys.dblwr.restore_first_page(space_id, name, fh))
goto err_exit; goto err_exit;
} }
space_id= id;
snprintf(undo_name, sizeof undo_name, "innodb_undo%03u", id); snprintf(undo_name, sizeof undo_name, "innodb_undo%03u", id);
aligned_free(page); aligned_free(page);
} }
...@@ -669,17 +669,19 @@ static dberr_t srv_all_undo_tablespaces_open(bool create_new_db, ulint n_undo) ...@@ -669,17 +669,19 @@ static dberr_t srv_all_undo_tablespaces_open(bool create_new_db, ulint n_undo)
snprintf(name, sizeof name, "%s%cundo%03zu", srv_undo_dir, snprintf(name, sizeof name, "%s%cundo%03zu", srv_undo_dir,
OS_PATH_SEPARATOR, i + 1); OS_PATH_SEPARATOR, i + 1);
ulint space_id= srv_undo_tablespace_open(create_new_db, name, i); ulint space_id= srv_undo_tablespace_open(create_new_db, name, i);
if (!space_id) switch (space_id) {
{ case ULINT_UNDEFINED:
return DB_CORRUPTION;
case 0:
if (!create_new_db) if (!create_new_db)
break; goto unused_undo;
ib::error() << "Unable to open create tablespace '" << name << "'."; sql_print_error("InnoDB: Unable to open create tablespace '%s'", name);
return DB_ERROR; return DB_ERROR;
default:
/* Should be no gaps in undo tablespace ids. */
ut_a(!i || prev_id + 1 == space_id);
} }
/* Should be no gaps in undo tablespace ids. */
ut_a(!i || prev_id + 1 == space_id);
prev_id= space_id; prev_id= space_id;
/* Note the first undo tablespace id in case of /* Note the first undo tablespace id in case of
...@@ -692,14 +694,15 @@ static dberr_t srv_all_undo_tablespaces_open(bool create_new_db, ulint n_undo) ...@@ -692,14 +694,15 @@ static dberr_t srv_all_undo_tablespaces_open(bool create_new_db, ulint n_undo)
We stop at the first failure. These are undo tablespaces that are We stop at the first failure. These are undo tablespaces that are
not in use and therefore not required by recovery. We only check not in use and therefore not required by recovery. We only check
that there are no gaps. */ that there are no gaps. */
unused_undo:
for (ulint i= prev_id + 1; i < srv_undo_space_id_start + TRX_SYS_N_RSEGS; for (ulint i= prev_id + 1; i < srv_undo_space_id_start + TRX_SYS_N_RSEGS;
++i) ++i)
{ {
char name[OS_FILE_MAX_PATH]; char name[OS_FILE_MAX_PATH];
snprintf(name, sizeof(name), snprintf(name, sizeof(name),
"%s%cundo%03zu", srv_undo_dir, OS_PATH_SEPARATOR, i); "%s%cundo%03zu", srv_undo_dir, OS_PATH_SEPARATOR, i);
if (!srv_undo_tablespace_open(create_new_db, name, i)) ulint space_id= srv_undo_tablespace_open(create_new_db, name, i);
if (!space_id || space_id == SRV_SPACE_ID_UPPER_BOUND)
break; break;
++srv_undo_tablespaces_open; ++srv_undo_tablespaces_open;
} }
......
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