Description:
1. if a table has name like `t-1`, it will be converted to `t@002d1`;
2. fil_op_replay_rename may add a fil_space_t with wrong name `t@002d1` to file
space cache directly which should be `t-1`;
3. when dd_load_tablespace try to search a cache with name `t-1` in cache, it will find a fil_space_t with name `t@002d1` and then cause some error;
4. ddl rollback may be a typical bad case and if a table is encrypted, it will report another error
5. Too many tables make this occur with a higher probability
How to repeat:
diff --git a/mysql-test/suite/innodb/t/rename_tablespace_with_reserved_name.test b/m
ysql-test/suite/innodb/t/rename_tablespace_with_reserved_name.test
new file mode 100644
index 00000000000..2ab80bc6e17
--- /dev/null
+++ b/mysql-test/suite/innodb/t/rename_tablespace_with_reserved_name.test
@@ -0,0 +1,75 @@
+--source include/have_debug.inc
+
+--echo #
+--echo # bad case 1: post_ddl in truncate table may pollute fil_space cache
+--echo #
+set global debug = "+d,force_intern_close_table";
+set global debug = "+d,ib_force_evict_all_table";
+
+create table `t-1` (a int key, b int);
+set global debug = "+d,ib_truncate_fail_after_rename";
+--error ER_GET_ERRNO
+truncate table `t-1`;
+
+# This select clear dict_table_t->ddl_not_evictable.
+select a from `t-1`;
+# Sleep to wait dict cache evicted
+--sleep 5
+
+--error ER_TABLESPACE_MISSING
+select a from `t-1`;
+drop table `t-1`;
+
+--let $restart_parameters=restart:
+--source include/restart_mysqld.inc
+
+--echo #
+--echo # bad case 2: TDE will report error after space cache pullution
+--echo #
+
+let $restart_parameters = restart: --early-plugin-load=keyring_file=$KEYRING_PLUGIN
--loose-keyring_file_data=$MYSQL_TMP_DIR/mysecret_keyring $KEYRING_PLUGIN_OPT;
+--source include/restart_mysqld_no_echo.inc
+
+set global debug = "reset";
+set global debug = "+d,force_intern_close_table";
+set global debug = "+d,ib_force_evict_all_table";
+--sleep 5
+
+delimiter |;
+create procedure populate(IN `cnt` INT)
+begin
+ declare i int default 1;
+ while (i <= cnt) do
+ insert into `t-1` (`type`, `info`) values (repeat("#",3000), repeat("#",5));
+ set i = i + 1;
+ end while;
+end |
+delimiter ;|
+
+create table `t-1` (
+ `id` int primary key auto_increment,
+ `type` text DEFAULT NULL,
+ `info` varchar(50) DEFAULT NULL
+) engine=InnoDB encryption='Y';
+
+call populate(5);
+
+set global debug = "+d,ib_truncate_fail_after_rename";
+--error ER_GET_ERRNO
+truncate table `t-1`;
+
+# This select clear dict_table_t->ddl_not_evictable.
+select id from `t-1`;
+# Sleep to wait dict cache evicted
+--sleep 5
+
+--error ER_CANNOT_FIND_KEY_IN_KEYRING
+call populate(5);
+
+drop procedure populate;
+drop table `t-1`;
+set global debug = "reset";
+
+remove_file $MYSQL_TMP_DIR/mysecret_keyring;
+--let $restart_parameters=restart:
+--source include/restart_mysqld.inc
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index cc0c3ff8971..a8cd720c120 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -987,7 +987,8 @@ void release_table_share(TABLE_SHARE *share) {
assert(share->ref_count() != 0);
if (share->decrement_ref_count() == 0) {
- if (share->has_old_version() || table_def_shutdown_in_progress)
+ if (share->has_old_version() || table_def_shutdown_in_progress ||
+ DBUG_EVALUATE_IF("force_intern_close_table", true, false))
table_def_cache->erase(to_string(share->table_cache_key));
else {
/* Link share last in used_table_share list */
@@ -1714,7 +1715,8 @@ static void release_or_close_table(THD *thd, TABLE *table) {
tc->lock();
if (table->s->has_old_version() || table->has_invalid_dict() ||
- table->has_invalid_stats() || table_def_shutdown_in_progress) {
+ table->has_invalid_stats() || table_def_shutdown_in_progress ||
+ DBUG_EVALUATE_IF("force_intern_close_table", true, false)) {
tc->remove_table(table);
mysql_mutex_lock(&LOCK_open);
intern_close_table(table);
diff --git a/storage/innobase/dict/dict0dd.cc b/storage/innobase/dict/dict0dd.cc
index e77568a2f2b..7b59bd99ca8 100644
--- a/storage/innobase/dict/dict0dd.cc
+++ b/storage/innobase/dict/dict0dd.cc
@@ -4636,7 +4636,7 @@ void dd_load_tablespace(const Table *dd_table, dict_table_t *t
able,
}
auto is_already_opened = [&]() {
- if (fil_space_exists_in_mem(table->space, space_name, false, true)) {
+ if (fil_space_exists_in_mem(table->space, space_name, true, true)) {
dd_get_and_save_data_dir_path(table, dd_table, true);
ut::free(shared_space_name);
return true;
diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc
index d8c8f6c6c15..a3299483850 100644
--- a/storage/innobase/dict/dict0dict.cc
+++ b/storage/innobase/dict/dict0dict.cc
@@ -1327,6 +1327,11 @@ ulint dict_make_room_in_cache(
ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X));
ut_ad(dict_lru_validate());
+ DBUG_EXECUTE_IF("ib_force_evict_all_table", {
+ max_tables = 0;
+ pct_check = 100;
+ });
+
i = len = UT_LIST_GET_LEN(dict_sys->table_LRU);
if (len < max_tables) {
Suggested fix:
Convert space name(`t@002d2`) back to user-defined format(`t-2`) in fil_op_replay_rename
Description: 1. if a table has name like `t-1`, it will be converted to `t@002d1`; 2. fil_op_replay_rename may add a fil_space_t with wrong name `t@002d1` to file space cache directly which should be `t-1`; 3. when dd_load_tablespace try to search a cache with name `t-1` in cache, it will find a fil_space_t with name `t@002d1` and then cause some error; 4. ddl rollback may be a typical bad case and if a table is encrypted, it will report another error 5. Too many tables make this occur with a higher probability How to repeat: diff --git a/mysql-test/suite/innodb/t/rename_tablespace_with_reserved_name.test b/m ysql-test/suite/innodb/t/rename_tablespace_with_reserved_name.test new file mode 100644 index 00000000000..2ab80bc6e17 --- /dev/null +++ b/mysql-test/suite/innodb/t/rename_tablespace_with_reserved_name.test @@ -0,0 +1,75 @@ +--source include/have_debug.inc + +--echo # +--echo # bad case 1: post_ddl in truncate table may pollute fil_space cache +--echo # +set global debug = "+d,force_intern_close_table"; +set global debug = "+d,ib_force_evict_all_table"; + +create table `t-1` (a int key, b int); +set global debug = "+d,ib_truncate_fail_after_rename"; +--error ER_GET_ERRNO +truncate table `t-1`; + +# This select clear dict_table_t->ddl_not_evictable. +select a from `t-1`; +# Sleep to wait dict cache evicted +--sleep 5 + +--error ER_TABLESPACE_MISSING +select a from `t-1`; +drop table `t-1`; + +--let $restart_parameters=restart: +--source include/restart_mysqld.inc + +--echo # +--echo # bad case 2: TDE will report error after space cache pullution +--echo # + +let $restart_parameters = restart: --early-plugin-load=keyring_file=$KEYRING_PLUGIN --loose-keyring_file_data=$MYSQL_TMP_DIR/mysecret_keyring $KEYRING_PLUGIN_OPT; +--source include/restart_mysqld_no_echo.inc + +set global debug = "reset"; +set global debug = "+d,force_intern_close_table"; +set global debug = "+d,ib_force_evict_all_table"; +--sleep 5 + +delimiter |; +create procedure populate(IN `cnt` INT) +begin + declare i int default 1; + while (i <= cnt) do + insert into `t-1` (`type`, `info`) values (repeat("#",3000), repeat("#",5)); + set i = i + 1; + end while; +end | +delimiter ;| + +create table `t-1` ( + `id` int primary key auto_increment, + `type` text DEFAULT NULL, + `info` varchar(50) DEFAULT NULL +) engine=InnoDB encryption='Y'; + +call populate(5); + +set global debug = "+d,ib_truncate_fail_after_rename"; +--error ER_GET_ERRNO +truncate table `t-1`; + +# This select clear dict_table_t->ddl_not_evictable. +select id from `t-1`; +# Sleep to wait dict cache evicted +--sleep 5 + +--error ER_CANNOT_FIND_KEY_IN_KEYRING +call populate(5); + +drop procedure populate; +drop table `t-1`; +set global debug = "reset"; + +remove_file $MYSQL_TMP_DIR/mysecret_keyring; +--let $restart_parameters=restart: +--source include/restart_mysqld.inc diff --git a/sql/sql_base.cc b/sql/sql_base.cc index cc0c3ff8971..a8cd720c120 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -987,7 +987,8 @@ void release_table_share(TABLE_SHARE *share) { assert(share->ref_count() != 0); if (share->decrement_ref_count() == 0) { - if (share->has_old_version() || table_def_shutdown_in_progress) + if (share->has_old_version() || table_def_shutdown_in_progress || + DBUG_EVALUATE_IF("force_intern_close_table", true, false)) table_def_cache->erase(to_string(share->table_cache_key)); else { /* Link share last in used_table_share list */ @@ -1714,7 +1715,8 @@ static void release_or_close_table(THD *thd, TABLE *table) { tc->lock(); if (table->s->has_old_version() || table->has_invalid_dict() || - table->has_invalid_stats() || table_def_shutdown_in_progress) { + table->has_invalid_stats() || table_def_shutdown_in_progress || + DBUG_EVALUATE_IF("force_intern_close_table", true, false)) { tc->remove_table(table); mysql_mutex_lock(&LOCK_open); intern_close_table(table); diff --git a/storage/innobase/dict/dict0dd.cc b/storage/innobase/dict/dict0dd.cc index e77568a2f2b..7b59bd99ca8 100644 --- a/storage/innobase/dict/dict0dd.cc +++ b/storage/innobase/dict/dict0dd.cc @@ -4636,7 +4636,7 @@ void dd_load_tablespace(const Table *dd_table, dict_table_t *t able, } auto is_already_opened = [&]() { - if (fil_space_exists_in_mem(table->space, space_name, false, true)) { + if (fil_space_exists_in_mem(table->space, space_name, true, true)) { dd_get_and_save_data_dir_path(table, dd_table, true); ut::free(shared_space_name); return true; diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index d8c8f6c6c15..a3299483850 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -1327,6 +1327,11 @@ ulint dict_make_room_in_cache( ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X)); ut_ad(dict_lru_validate()); + DBUG_EXECUTE_IF("ib_force_evict_all_table", { + max_tables = 0; + pct_check = 100; + }); + i = len = UT_LIST_GET_LEN(dict_sys->table_LRU); if (len < max_tables) { Suggested fix: Convert space name(`t@002d2`) back to user-defined format(`t-2`) in fil_op_replay_rename