From fcda595233cd5bdf87e4b7f16a6f09a5eccbb942 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= <marko.makela@mariadb.com>
Date: Fri, 23 Aug 2019 17:43:55 +0300
Subject: [PATCH] Implement innodb_evict_tables_on_commit_debug

Some bugs are detected only after a table definition has been evicted
and then reloaded to the InnoDB data dictionary cache.

For debug builds, introduce the settable Boolean configuration parameter
innodb_evict_tables_on_commit_debug that can be set to request InnoDB
to attempt to evict table definitions from the data dictionary cache
whenever a transaction is committed.

This has been tested on 10.3 and 10.4 with the following:

./mysql-test-run.pl --mysqld=--loose-innodb-evict-tables-on-commit-debug

You can also use the following:

SET GLOBAL innodb_evict_tables_on_commit_debug=ON;
SET GLOBAL innodb_evict_tables_on_commit_debug=OFF;

The parameter affects the commit (or rollback or abort) of
transactions that have modified persistent InnoDB tables.
---
 .../suite/sys_vars/r/sysvars_innodb.result    |  1 +
 .../suite/sys_vars/t/sysvars_innodb.test      |  1 +
 storage/innobase/handler/ha_innodb.cc         |  7 ++++
 storage/innobase/include/srv0srv.h            |  3 +-
 storage/innobase/trx/trx0trx.cc               | 38 ++++++++++++++++++-
 5 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result
index 591f31aa844..76a57edd6c2 100644
--- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result
+++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result
@@ -5,6 +5,7 @@ variable_name not in (
 'innodb_disallow_writes',           # only available WITH_WSREP
 'innodb_numa_interleave',           # only available WITH_NUMA
 'innodb_sched_priority_cleaner',    # linux only
+'innodb_evict_tables_on_commit_debug', # one may want to override this
 'innodb_use_native_aio',            # default value depends on OS
 'innodb_buffer_pool_load_pages_abort')            # debug build only, and is only for testing
 order by variable_name;
diff --git a/mysql-test/suite/sys_vars/t/sysvars_innodb.test b/mysql-test/suite/sys_vars/t/sysvars_innodb.test
index 9be60723f85..c573c5592b2 100644
--- a/mysql-test/suite/sys_vars/t/sysvars_innodb.test
+++ b/mysql-test/suite/sys_vars/t/sysvars_innodb.test
@@ -12,6 +12,7 @@ select * from information_schema.system_variables
     'innodb_disallow_writes',           # only available WITH_WSREP
     'innodb_numa_interleave',           # only available WITH_NUMA
     'innodb_sched_priority_cleaner',    # linux only
+    'innodb_evict_tables_on_commit_debug', # one may want to override this
     'innodb_use_native_aio',            # default value depends on OS
     'innodb_buffer_pool_load_pages_abort')            # debug build only, and is only for testing
   order by variable_name;
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index 59a7c8641f8..538a542bdf8 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -235,6 +235,7 @@ extern my_bool srv_background_scrub_data_compressed;
 extern uint srv_background_scrub_data_interval;
 extern uint srv_background_scrub_data_check_interval;
 #ifdef UNIV_DEBUG
+my_bool innodb_evict_tables_on_commit_debug;
 extern my_bool srv_scrub_force_testing;
 #endif
 
@@ -19900,6 +19901,11 @@ static MYSQL_SYSVAR_BOOL(trx_purge_view_update_only_debug,
   " but the each purges were not done yet.",
   NULL, NULL, FALSE);
 
+static MYSQL_SYSVAR_BOOL(evict_tables_on_commit_debug,
+  innodb_evict_tables_on_commit_debug, PLUGIN_VAR_OPCMDARG,
+  "On transaction commit, try to evict tables from the data dictionary cache.",
+  NULL, NULL, FALSE);
+
 static MYSQL_SYSVAR_UINT(data_file_size_debug,
   srv_sys_space_size_debug,
   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
@@ -20265,6 +20271,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= {
   MYSQL_SYSVAR(trx_rseg_n_slots_debug),
   MYSQL_SYSVAR(limit_optimistic_insert_debug),
   MYSQL_SYSVAR(trx_purge_view_update_only_debug),
+  MYSQL_SYSVAR(evict_tables_on_commit_debug),
   MYSQL_SYSVAR(data_file_size_debug),
   MYSQL_SYSVAR(fil_make_page_dirty_debug),
   MYSQL_SYSVAR(saved_page_number_debug),
diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h
index d1596764bd1..db2218633b3 100644
--- a/storage/innobase/include/srv0srv.h
+++ b/storage/innobase/include/srv0srv.h
@@ -3,7 +3,7 @@
 Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved.
 Copyright (c) 2008, 2009, Google Inc.
 Copyright (c) 2009, Percona Inc.
-Copyright (c) 2013, 2018, MariaDB Corporation.
+Copyright (c) 2013, 2019, MariaDB Corporation.
 
 Portions of this file contain modifications contributed and copyrighted by
 Google, Inc. Those modifications are gratefully acknowledged and are described
@@ -536,6 +536,7 @@ extern my_bool	srv_ibuf_disable_background_merge;
 #endif /* UNIV_DEBUG || UNIV_IBUF_DEBUG */
 
 #ifdef UNIV_DEBUG
+extern my_bool	innodb_evict_tables_on_commit_debug;
 extern my_bool	srv_sync_debug;
 extern my_bool	srv_purge_view_update_only_debug;
 
diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc
index 89ac734652e..723e8458c6d 100644
--- a/storage/innobase/trx/trx0trx.cc
+++ b/storage/innobase/trx/trx0trx.cc
@@ -1230,6 +1230,22 @@ trx_update_mod_tables_timestamp(
 	const time_t now = time(NULL);
 
 	trx_mod_tables_t::const_iterator	end = trx->mod_tables.end();
+#ifdef UNIV_DEBUG
+# if MYSQL_VERSION_ID >= 100405
+#  define dict_sys_mutex dict_sys.mutex
+# else
+#  define dict_sys_mutex dict_sys->mutex
+# endif
+
+	const bool preserve_tables = !innodb_evict_tables_on_commit_debug
+		|| trx->is_recovered /* avoid trouble with XA recovery */
+# if 1 /* if dict_stats_exec_sql() were not playing dirty tricks */
+		|| mutex_own(&dict_sys_mutex)
+# else /* this would be more proper way to do it */
+		|| trx->dict_operation_lock_mode || trx->dict_operation
+# endif
+		;
+#endif
 
 	for (trx_mod_tables_t::const_iterator it = trx->mod_tables.begin();
 	     it != end;
@@ -1243,7 +1259,27 @@ trx_update_mod_tables_timestamp(
 		"garbage" in table->update_time is justified because
 		protecting it with a latch here would be too performance
 		intrusive. */
-		it->first->update_time = now;
+		dict_table_t* table = it->first;
+		table->update_time = now;
+#ifdef UNIV_DEBUG
+		if (preserve_tables || table->get_ref_count()) {
+			/* do not evict when committing DDL operations
+			or if some other transaction is holding the
+			table handle */
+			continue;
+		}
+		/* recheck while holding the mutex that blocks
+		table->acquire() */
+		mutex_enter(&dict_sys_mutex);
+		if (!table->get_ref_count()) {
+# if MYSQL_VERSION_ID >= 100405
+			dict_sys.remove(table, true);
+# else
+			dict_table_remove_from_cache_low(table, true);
+# endif
+		}
+		mutex_exit(&dict_sys_mutex);
+#endif
 	}
 
 	trx->mod_tables.clear();
-- 
2.23.0

