Skip to content

Commit 9696697

Browse files
MDEV-33011 mariabackup --backup: FATAL ERROR: ... Can't open datafile cool_down/t3
The root cause is the WAL logging of file operation when the actual operation fails afterwards. It creates a situation with a log entry for a operation that would always fail. I could simulate both the backup scenario error and Innodb recovery failure exploiting the weakness. We are following WAL for file rename operation and once logged the operation must eventually complete successfully, or it is a major catastrophe. Right now, we fail for rename and handle it as normal error and it is the problem. I created a patch to address RENAME operation to a non existing schema where the destination schema directory is missing. The patch checks for the missing schema before logging in an attempt to avoid the failure after WAL log is written/flushed. I also checked that the schema cannot be dropped or there cannot be any race with other rename to the same file. This is protected by the MDL lock in SQL today. The patch should this be a good improvement over the current situation and solves the issue at hand.
1 parent 8778a83 commit 9696697

File tree

6 files changed

+107
-16
lines changed

6 files changed

+107
-16
lines changed

mysql-test/suite/innodb/r/rename_table.result

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@ path
2020
DROP DATABASE abc_def;
2121
# restart
2222
DROP DATABASE abc_def2;
23-
call mtr.add_suppression("InnoDB: (Operating system error|The error means|Cannot rename file)");
23+
call mtr.add_suppression("InnoDB: Cannot rename '.*t1.ibd' to '.*non_existing_db.*' because the target schema directory doesn't exist");
2424
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
25+
INSERT INTO t1 VALUES(100);
2526
RENAME TABLE t1 TO non_existing_db.t1;
2627
ERROR HY000: Error on rename of './test/t1' to './non_existing_db/t1' (errno: 168 "Unknown (generic) error from engine")
27-
FOUND 1 /\[ERROR\] InnoDB: Cannot rename file '.*t1\.ibd' to '.*non_existing_db/ in mysqld.1.err
28+
FOUND 1 /\[ERROR\] InnoDB: Cannot rename '.*t1\.ibd' to '.*non_existing_db/ in mysqld.1.err
29+
SET GLOBAL innodb_fast_shutdown=2;
30+
# restart
31+
SELECT * FROM t1;
32+
a
33+
100
2834
DROP TABLE t1;

mysql-test/suite/innodb/t/rename_table.test

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,21 @@ DROP DATABASE abc_def;
3030

3131
DROP DATABASE abc_def2;
3232

33-
call mtr.add_suppression("InnoDB: (Operating system error|The error means|Cannot rename file)");
33+
call mtr.add_suppression("InnoDB: Cannot rename '.*t1.ibd' to '.*non_existing_db.*' because the target schema directory doesn't exist");
3434

3535
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
36+
INSERT INTO t1 VALUES(100);
3637
--replace_result "\\" "/"
3738
--error ER_ERROR_ON_RENAME
3839
RENAME TABLE t1 TO non_existing_db.t1;
3940

40-
--let SEARCH_PATTERN= \[ERROR\] InnoDB: Cannot rename file '.*t1\.ibd' to '.*non_existing_db
41+
--let SEARCH_PATTERN= \[ERROR\] InnoDB: Cannot rename '.*t1\.ibd' to '.*non_existing_db
4142
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
4243
--source include/search_pattern_in_file.inc
4344

45+
SET GLOBAL innodb_fast_shutdown=2;
46+
--source include/restart_mysqld.inc
47+
48+
SELECT * FROM t1;
4449
# Cleanup
4550
DROP TABLE t1;

mysql-test/suite/mariabackup/rename_during_backup.result

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,15 @@ SELECT * from t6;
6060
i
6161
5
6262
DROP TABLE t6;
63+
#
64+
# MDEV-33011 mariabackup --backup: FATAL ERROR: ... Can't open datafile cool_down/t3
65+
#
66+
# Simulate zero initialized page to defer tablespace load after rename log is found
67+
SET @save_dbug = @@SESSION.debug_dbug;
68+
SET DEBUG_DBUG="+d,checkpoint_after_file_create";
69+
CREATE TABLE t1(f1 INT NOT NULL)ENGINE=InnoDB;
70+
INSERT INTO t1 VALUES(1);
71+
# RENAME that fails after redo log entry is written and flushed
72+
RENAME TABLE t1 TO non_existing_db.t1;
73+
ERROR HY000: Error on rename of './test/t1' to './non_existing_db/t1' (errno: 168 "Unknown (generic) error from engine")
74+
DROP TABLE t1;

mysql-test/suite/mariabackup/rename_during_backup.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,31 @@ SELECT * from t6;
9090
DROP TABLE t6;
9191
rmdir $targetdir;
9292

93+
--echo #
94+
--echo # MDEV-33011 mariabackup --backup: FATAL ERROR: ... Can't open datafile cool_down/t3
95+
--echo #
9396

97+
--disable_query_log
98+
call mtr.add_suppression("InnoDB: Cannot rename '.*t1.ibd' to '.*non_existing_db.*' because the target schema directory doesn't exist");
99+
--enable_query_log
100+
101+
mkdir $targetdir;
102+
103+
--echo # Simulate zero initialized page to defer tablespace load after rename log is found
104+
SET @save_dbug = @@SESSION.debug_dbug;
105+
SET DEBUG_DBUG="+d,checkpoint_after_file_create";
106+
CREATE TABLE t1(f1 INT NOT NULL)ENGINE=InnoDB;
107+
INSERT INTO t1 VALUES(1);
108+
109+
--echo # RENAME that fails after redo log entry is written and flushed
110+
--replace_result "\\" "/"
111+
--error ER_ERROR_ON_RENAME
112+
RENAME TABLE t1 TO non_existing_db.t1;
113+
114+
--disable_result_log
115+
exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir;
116+
exec $XTRABACKUP --prepare --target-dir=$targetdir;
117+
--enable_result_log
118+
119+
DROP TABLE t1;
120+
rmdir $targetdir;

storage/innobase/fil/fil0fil.cc

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,33 +1959,29 @@ Allocates and builds a file name from a path, a table or tablespace name
19591959
and a suffix. The string must be freed by caller with ut_free().
19601960
@param[in] path NULL or the directory path or the full path and filename.
19611961
@param[in] name NULL if path is full, or Table/Tablespace name
1962-
@param[in] suffix NULL or the file extention to use.
1962+
@param[in] extension NULL or the file extension to use.
19631963
@param[in] trim_name true if the last name on the path should be trimmed.
19641964
@return own: file name */
19651965
char*
1966-
fil_make_filepath(
1966+
fil_make_filepath_low(
19671967
const char* path,
19681968
const char* name,
1969-
ib_extentionext,
1969+
ib_extentionextension,
19701970
booltrim_name)
19711971
{
19721972
/* The path may contain the basename of the file, if so we do not
19731973
need the name. If the path is NULL, we can use the default path,
19741974
but there needs to be a name. */
19751975
ut_ad(path != NULL || name != NULL);
19761976

1977-
/* If we are going to strip a name off the path, there better be a
1978-
path and a new name to put back on. */
1979-
ut_ad(!trim_name || (path != NULL && name != NULL));
1980-
19811977
if (path == NULL) {
19821978
path = fil_path_to_mysql_datadir;
19831979
}
19841980

19851981
ulint len = 0;/* current length */
19861982
ulint path_len = strlen(path);
19871983
ulint name_len = (name ? strlen(name) : 0);
1988-
const char* suffix = dot_ext[ext];
1984+
const char* suffix = dot_ext[extension];
19891985
ulint suffix_len = strlen(suffix);
19901986
ulint full_len = path_len + 1 + name_len + suffix_len + 1;
19911987

@@ -2083,6 +2079,21 @@ fil_rename_tablespace_check(
20832079
}
20842080

20852081
exists = false;
2082+
auto schema_path= fil_make_dirpath(new_path, NULL, NO_EXT, true);
2083+
if (schema_path == NULL) {
2084+
return DB_ERROR;
2085+
}
2086+
2087+
if (os_file_status(schema_path, &exists, &ftype) && !exists) {
2088+
sql_print_error("InnoDB: Cannot rename '%s' to '%s'"
2089+
" because the target schema directory doesn't exist.",
2090+
old_path, new_path);
2091+
ut_free(schema_path);
2092+
return DB_ERROR;
2093+
}
2094+
ut_free(schema_path);
2095+
exists = false;
2096+
20862097
if (os_file_status(new_path, &exists, &ftype) && !exists) {
20872098
return DB_SUCCESS;
20882099
}

storage/innobase/include/fil0fil.h

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,14 +1618,44 @@ Allocates and builds a file name from a path, a table or tablespace name
16181618
and a suffix. The string must be freed by caller with ut_free().
16191619
@param[in] path NULL or the directory path or the full path and filename.
16201620
@param[in] name NULL if path is full, or Table/Tablespace name
1621-
@param[in] suffix NULL or the file extention to use.
1621+
@param[in] extension NULL or the file extension to use.
1622+
@param[in] trim_name true if the last name on the path should be trimmed.
16221623
@return own: file name */
16231624
char*
1624-
fil_make_filepath(
1625+
fil_make_filepath_low(
16251626
const char* path,
16261627
const char* name,
1627-
ib_extention suffix,
1628-
boolstrip_name);
1628+
ib_extention extension,
1629+
booltrim_name);
1630+
1631+
/** Wrapper function over fil_make_filepath_low to build file name.
1632+
@param path NULL or the directory path or the full path and filename.
1633+
@param name NULL if path is full, or Table/Tablespace name
1634+
@param extension NULL or the file extension to use.
1635+
@param trim_name true if the last name on the path should be trimmed.
1636+
@return own: file name */
1637+
static inline char*
1638+
fil_make_filepath(const char* path, const char* name, ib_extention extension,
1639+
bool trim_name)
1640+
{
1641+
/* If we are going to strip a name off the path, there better be a
1642+
path and a new name to put back on. */
1643+
ut_ad(!trim_name || (path && name));
1644+
return fil_make_filepath_low(path, name, extension, trim_name);
1645+
}
1646+
1647+
/** Wrapper function over fil_make_filepath_low to build directory name.
1648+
@param path NULL or the directory path or the full path and filename.
1649+
@param name NULL if path is full, or Table/Tablespace name
1650+
@param extension NULL or the file extension to use.
1651+
@param trim_name true if the last name on the path should be trimmed.
1652+
@return own: directory name */
1653+
static inline char*
1654+
fil_make_dirpath(const char* path, const char* name, ib_extention extension,
1655+
bool trim_name)
1656+
{
1657+
return fil_make_filepath_low(path, name, extension, trim_name);
1658+
}
16291659

16301660
/** Create a tablespace file.
16311661
@param[in] space_id Tablespace ID

0 commit comments

Comments
 (0)