Bug #108060 Add file_space with converted name to cache then cause some error
Submitted: 3 Aug 9:34 Modified: 3 Aug 13:10
Reporter: Haiping Zhang Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: DDL Severity:S3 (Non-critical)
Version:8.0 OS:Any
Assigned to: CPU Architecture:Any
Tags: DDL, TDE

[3 Aug 9:34] Haiping Zhang
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
[3 Aug 9:38] Haiping Zhang
2022-08-03T09:15:21.968737Z 8 [ERROR] [MY-012141] [InnoDB] Table test/t-1 in InnoDB data dictionary has tablespace id 7, but the tablespace with that id has name test/t@002d1. Have you deleted or moved .ibd files?
[3 Aug 13:10] MySQL Verification Team
Hi Mr. Zhang,

Thank you for your bug report.

We were able to reproduce it by running your test. 

We are also grateful for your patch.

Verified as reported.