From 5ce6fb59a03108cfff11c71ca3f660fb2d8ea11e Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 5 Mar 2019 22:40:55 +0100 Subject: [PATCH 1/5] disable LeakSanitizer for unit.dbug test --- dbug/tests.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dbug/tests.c b/dbug/tests.c index 22a445fdeca..70424046bf4 100644 --- a/dbug/tests.c +++ b/dbug/tests.c @@ -86,3 +86,11 @@ int main (int argc __attribute__((unused)), return 0; #endif /* DBUG_OFF */ } + +#ifdef __SANITIZE_ADDRESS__ +/* Disable LeakSanitizer in this executable */ +const char* __asan_default_options() +{ + return "detect_leaks=0"; +} +#endif From 84645366c459b01771223a6d1a20bf7ac38adf48 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 5 Mar 2019 20:19:50 +0100 Subject: [PATCH 2/5] ASAN loves stack, give it some fixes these test failures in ASAN builds (in 10.1 and 10.4): * main.signal_demo3 * main.sp * sys_vars.max_sp_recursion_depth_func * mroonga/storage.foreign_key_delete_existent * mroonga/storage.foreign_key_delete_nonexistent * mroonga/storage.foreign_key_insert_existent * mroonga/storage.foreign_key_update_existent * mroonga/storage.foreign_key_update_nonexistent * mroonga/storage.function_command_auto-escape * mroonga/storage.function_command_select * mroonga/storage.variable_enable_operations_recording_insert --- include/my_pthread.h | 6 +++++- mysql-test/r/mysqld--help,win.rdiff | 2 +- mysql-test/r/mysqld--help.result | 2 +- mysql-test/suite/sys_vars/inc/sysvars_server.inc | 2 +- .../suite/sys_vars/r/sysvars_server_embedded.result | 4 ++-- .../suite/sys_vars/r/sysvars_server_notembedded.result | 4 ++-- mysql-test/suite/sys_vars/r/thread_stack_basic.result | 10 +++++----- mysql-test/suite/sys_vars/t/thread_stack_basic.test | 10 +++++----- mysql-test/t/mysqld--help.test | 2 +- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/include/my_pthread.h b/include/my_pthread.h index ae2f912f979..2b3edb73528 100644 --- a/include/my_pthread.h +++ b/include/my_pthread.h @@ -734,7 +734,11 @@ extern void my_mutex_end(void); We need to have at least 256K stack to handle calls to myisamchk_init() with the current number of keys and key parts. */ -#define DEFAULT_THREAD_STACK (289*1024L) +#ifdef __SANITIZE_ADDRESS__ +#define DEFAULT_THREAD_STACK (364*1024L) +#else +#define DEFAULT_THREAD_STACK (292*1024L) +#endif #endif #define MY_PTHREAD_LOCK_READ 0 diff --git a/mysql-test/r/mysqld--help,win.rdiff b/mysql-test/r/mysqld--help,win.rdiff index 6e18d16feaf..128155870b8 100644 --- a/mysql-test/r/mysqld--help,win.rdiff +++ b/mysql-test/r/mysqld--help,win.rdiff @@ -133,6 +133,6 @@ -thread-pool-oversubscribe 3 -thread-pool-stall-limit 500 +thread-pool-min-threads 1 - thread-stack 295936 + thread-stack 299008 time-format %H:%i:%s timed-mutexes FALSE diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result index fdb4d8c280d..4ff44dcd251 100644 --- a/mysql-test/r/mysqld--help.result +++ b/mysql-test/r/mysqld--help.result @@ -1449,7 +1449,7 @@ thread-pool-idle-timeout 60 thread-pool-max-threads 1000 thread-pool-oversubscribe 3 thread-pool-stall-limit 500 -thread-stack 295936 +thread-stack 299008 time-format %H:%i:%s timed-mutexes FALSE tmp-table-size 16777216 diff --git a/mysql-test/suite/sys_vars/inc/sysvars_server.inc b/mysql-test/suite/sys_vars/inc/sysvars_server.inc index cb06b40f8c9..cffc7e7fa62 100644 --- a/mysql-test/suite/sys_vars/inc/sysvars_server.inc +++ b/mysql-test/suite/sys_vars/inc/sysvars_server.inc @@ -12,7 +12,7 @@ set sql_mode=ansi_quotes; set global div_precision_increment=5; --replace_regex /^\/\S+/PATH/ ---replace_result $MASTER_MYPORT MASTER_MYPORT +--replace_result $MASTER_MYPORT MASTER_MYPORT 372736 299008 select * from information_schema.system_variables where variable_name not like 'aria%' and variable_name not like 'debug%' and diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 4da76bdaec1..9ca6995d7ef 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -3917,9 +3917,9 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME THREAD_STACK SESSION_VALUE NULL -GLOBAL_VALUE 295936 +GLOBAL_VALUE 299008 GLOBAL_VALUE_ORIGIN COMPILE-TIME -DEFAULT_VALUE 295936 +DEFAULT_VALUE 299008 VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT The stack size for each thread diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 3e34bfce8f9..9288912eb57 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -4687,9 +4687,9 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME THREAD_STACK SESSION_VALUE NULL -GLOBAL_VALUE 295936 +GLOBAL_VALUE 299008 GLOBAL_VALUE_ORIGIN COMPILE-TIME -DEFAULT_VALUE 295936 +DEFAULT_VALUE 299008 VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT The stack size for each thread diff --git a/mysql-test/suite/sys_vars/r/thread_stack_basic.result b/mysql-test/suite/sys_vars/r/thread_stack_basic.result index d5a30bbaf6e..5be5db5626c 100644 --- a/mysql-test/suite/sys_vars/r/thread_stack_basic.result +++ b/mysql-test/suite/sys_vars/r/thread_stack_basic.result @@ -1,20 +1,20 @@ select @@global.thread_stack; @@global.thread_stack -295936 +299008 select @@session.thread_stack; ERROR HY000: Variable 'thread_stack' is a GLOBAL variable show global variables like 'thread_stack'; Variable_name Value -thread_stack 295936 +thread_stack 299008 show session variables like 'thread_stack'; Variable_name Value -thread_stack 295936 +thread_stack 299008 select * from information_schema.global_variables where variable_name='thread_stack'; VARIABLE_NAME VARIABLE_VALUE -THREAD_STACK 295936 +THREAD_STACK 299008 select * from information_schema.session_variables where variable_name='thread_stack'; VARIABLE_NAME VARIABLE_VALUE -THREAD_STACK 295936 +THREAD_STACK 299008 set global thread_stack=1; ERROR HY000: Variable 'thread_stack' is a read only variable set session thread_stack=1; diff --git a/mysql-test/suite/sys_vars/t/thread_stack_basic.test b/mysql-test/suite/sys_vars/t/thread_stack_basic.test index 2d4d144572d..bfd3fb40db3 100644 --- a/mysql-test/suite/sys_vars/t/thread_stack_basic.test +++ b/mysql-test/suite/sys_vars/t/thread_stack_basic.test @@ -1,17 +1,17 @@ # # only global # ---replace_result 196608 262144 +--replace_result 372736 299008 select @@global.thread_stack; --error ER_INCORRECT_GLOBAL_LOCAL_VAR select @@session.thread_stack; ---replace_result 196608 262144 +--replace_result 372736 299008 show global variables like 'thread_stack'; ---replace_result 196608 262144 +--replace_result 372736 299008 show session variables like 'thread_stack'; ---replace_result 196608 262144 +--replace_result 372736 299008 select * from information_schema.global_variables where variable_name='thread_stack'; ---replace_result 196608 262144 +--replace_result 372736 299008 select * from information_schema.session_variables where variable_name='thread_stack'; # diff --git a/mysql-test/t/mysqld--help.test b/mysql-test/t/mysqld--help.test index 3fb924b9199..8b4674c140c 100644 --- a/mysql-test/t/mysqld--help.test +++ b/mysql-test/t/mysqld--help.test @@ -57,7 +57,7 @@ perl; # fixes for 32-bit s/\b4294967295\b/18446744073709551615/; s/\b2146435072\b/9223372036853727232/; - s/\b196608\b/262144/; + s/\b372736\b/299008/; s/\b4294963200\b/18446744073709547520/; foreach $var (@env) { s/\Q$ENV{$var}\E/$var/ } next if /use --skip-(use-)?symbolic-links to disable/; # for valgrind, again From 8024f8c6b86b204c3475e562587640cf2b141683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 7 Mar 2019 11:57:14 +0200 Subject: [PATCH 3/5] MDEV-18272 InnoDB fails to rollback after exceeding FOREIGN KEY recursion depth row_mysql_handle_errors(): Correct the wrong error handling for the code DB_FOREIGN_EXCEED_MAX_CASCADE that was introduced in https://github.com/mysql/mysql-server/commit/c0923d396aef46799883390e9dcf7bbf173e4a03 commit 35f5429eda94f2ce87e453653cd22298d5851cfa Author: Jimmy Yang Date: Wed Oct 6 06:55:34 2010 -0700 Manual port Bug #Bug #54582 "stack overflow when opening many tables linked with foreign keys at once" from mysql-5.1-security to mysql-5.5-security again. rb://391 approved by Heikki No known test case exists for repeating the bug before MariaDB 10.2. The scenario should be that DB_FOREIGN_EXCEED_MAX_CASCADE is returned, then InnoDB wrongly skips the rollback to the start of the current row operation, and finally the SQL layer commits the transaction. Normally the SQL layer would roll back either the entire transaction or to the start of the statement. In the faulty scenario, InnoDB would leave the transaction in an inconsistent state, and the SQL layer could commit the transaction. --- storage/innobase/row/row0mysql.c | 10 ++++++---- storage/xtradb/row/row0mysql.c | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c index 8804e2c4e03..c16d1b8ea36 100644 --- a/storage/innobase/row/row0mysql.c +++ b/storage/innobase/row/row0mysql.c @@ -1,6 +1,7 @@ /***************************************************************************** Copyright (c) 2000, 2014, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -572,8 +573,7 @@ handle_new_error: switch (err) { case DB_LOCK_WAIT_TIMEOUT: if (row_rollback_on_timeout) { - trx_general_rollback_for_mysql(trx, NULL); - break; + goto rollback; } /* fall through */ case DB_DUPLICATE_KEY: @@ -586,6 +586,7 @@ handle_new_error: case DB_TOO_MANY_CONCURRENT_TRXS: case DB_OUT_OF_FILE_SPACE: case DB_INTERRUPTED: + rollback_to_savept: if (savept) { /* Roll back the latest, possibly incomplete insertion or update */ @@ -609,6 +610,7 @@ handle_new_error: case DB_DEADLOCK: case DB_LOCK_TABLE_FULL: + rollback: /* Roll back the whole transaction; this resolution was added to version 3.23.43 */ @@ -638,14 +640,14 @@ handle_new_error: "InnoDB: you dump the tables, look at\n" "InnoDB: " REFMAN "forcing-innodb-recovery.html" " for help.\n", stderr); - break; + goto rollback_to_savept; case DB_FOREIGN_EXCEED_MAX_CASCADE: fprintf(stderr, "InnoDB: Cannot delete/update rows with" " cascading foreign key constraints that exceed max" " depth of %lu\n" "Please drop excessive foreign constraints" " and try again\n", (ulong) DICT_FK_MAX_RECURSIVE_LOAD); - break; + goto rollback_to_savept; default: fprintf(stderr, "InnoDB: unknown error code %lu\n", (ulong) err); diff --git a/storage/xtradb/row/row0mysql.c b/storage/xtradb/row/row0mysql.c index 5b01adf7c82..a17a8d59b83 100644 --- a/storage/xtradb/row/row0mysql.c +++ b/storage/xtradb/row/row0mysql.c @@ -1,6 +1,7 @@ /***************************************************************************** Copyright (c) 2000, 2014, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -574,8 +575,7 @@ handle_new_error: switch (err) { case DB_LOCK_WAIT_TIMEOUT: if (row_rollback_on_timeout) { - trx_general_rollback_for_mysql(trx, NULL); - break; + goto rollback; } /* fall through */ case DB_DUPLICATE_KEY: @@ -588,6 +588,7 @@ handle_new_error: case DB_TOO_MANY_CONCURRENT_TRXS: case DB_OUT_OF_FILE_SPACE: case DB_INTERRUPTED: + rollback_to_savept: if (savept) { /* Roll back the latest, possibly incomplete insertion or update */ @@ -611,6 +612,7 @@ handle_new_error: case DB_DEADLOCK: case DB_LOCK_TABLE_FULL: + rollback: /* Roll back the whole transaction; this resolution was added to version 3.23.43 */ @@ -640,14 +642,14 @@ handle_new_error: "InnoDB: you dump the tables, look at\n" "InnoDB: " REFMAN "forcing-innodb-recovery.html" " for help.\n", stderr); - break; + goto rollback_to_savept; case DB_FOREIGN_EXCEED_MAX_CASCADE: fprintf(stderr, "InnoDB: Cannot delete/update rows with" " cascading foreign key constraints that exceed max" " depth of %lu\n" "Please drop excessive foreign constraints" " and try again\n", (ulong) DICT_FK_MAX_RECURSIVE_LOAD); - break; + goto rollback_to_savept; default: fprintf(stderr, "InnoDB: unknown error code %lu\n", (ulong) err); From 1a5028f43081f9877743a465317ad3e33bd9ddfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 7 Mar 2019 14:31:54 +0200 Subject: [PATCH 4/5] Fix the WITH_ASAN clang build of dynamic plugins --- cmake/plugin.cmake | 2 +- mysql-test/suite/innodb/r/alter_crash.result | 149 ++++++++++++ mysql-test/suite/innodb/t/alter_crash.test | 228 +++++++++++++++++++ 3 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/innodb/r/alter_crash.result create mode 100644 mysql-test/suite/innodb/t/alter_crash.test diff --git a/cmake/plugin.cmake b/cmake/plugin.cmake index 9c9c7082a43..cfd9677333e 100644 --- a/cmake/plugin.cmake +++ b/cmake/plugin.cmake @@ -209,7 +209,7 @@ MACRO(MYSQL_ADD_PLUGIN) ELSEIF(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") TARGET_LINK_LIBRARIES (${target} mysqld) ENDIF() - ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Linux") + ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT WITH_ASAN) TARGET_LINK_LIBRARIES (${target} "-Wl,--no-undefined") ENDIF() diff --git a/mysql-test/suite/innodb/r/alter_crash.result b/mysql-test/suite/innodb/r/alter_crash.result new file mode 100644 index 00000000000..8de02cc5fbd --- /dev/null +++ b/mysql-test/suite/innodb/r/alter_crash.result @@ -0,0 +1,149 @@ +# +# Bug#20015132 ALTER TABLE FAILS TO CHECK IF TABLE IS CORRUPTED +# +CREATE TABLE t1(c1 INT PRIMARY KEY, c2 CHAR(1), c3 INT UNSIGNED) ENGINE=InnoDB; +SET @saved_debug_dbug = @@SESSION.debug_dbug; +SET DEBUG_DBUG='+d,ib_create_table_fail_too_many_trx'; +ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); +ERROR HY000: Too many active concurrent transactions +SET DEBUG_DBUG=@saved_debug_dbug; +ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); +SET DEBUG_DBUG='+d,dict_set_index_corrupted'; +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check Warning InnoDB: Index c2 is marked as corrupted +test.t1 check Warning InnoDB: Index c3 is marked as corrupted +test.t1 check error Corrupt +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check Warning InnoDB: Index c2 is marked as corrupted +test.t1 check Warning InnoDB: Index c3 is marked as corrupted +test.t1 check error Corrupt +ALTER TABLE t1 DROP INDEX c2; +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check Warning InnoDB: Index c3 is marked as corrupted +test.t1 check error Corrupt +ALTER TABLE t1 ADD INDEX (c2,c3); +ERROR HY000: Index c3 is corrupted +ALTER TABLE t1 CHANGE c3 c3 INT NOT NULL; +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +ALTER TABLE t1 ADD INDEX (c2,c3); +DROP TABLE t1; +# +# Bug #14669848 CRASH DURING ALTER MAKES ORIGINAL TABLE INACCESSIBLE +# +# -- Scenario 1: +# Crash the server in ha_innobase::commit_inplace_alter_table() +# just after committing the dictionary changes. +CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb; +INSERT INTO t1 VALUES (1,2),(3,4); +SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit'; +ALTER TABLE t1 ADD PRIMARY KEY (f2, f1); +ERROR HY000: Lost connection to MySQL server during query +# Restart mysqld after the crash and reconnect. +# Manual *.frm recovery begin. +# Manual recovery end +FLUSH TABLES; +# Drop the orphaned original table. +# Files in datadir after manual recovery. +t1.frm +t1.ibd +SHOW TABLES; +Tables_in_test +t1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` int(11) NOT NULL, + PRIMARY KEY (`f2`,`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +INSERT INTO t1 VALUES (5,6),(7,8); +SELECT * FROM t1; +f1 f2 +1 2 +3 4 +5 6 +7 8 +DROP TABLE t1; +CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=InnoDB; +ALTER TABLE t1 ADD PRIMARY KEY (f2, f1); +DROP TABLE t1; +# -- Scenario 2: +# Crash the server in ha_innobase::commit_inplace_alter_table() +# just before committing the dictionary changes, but after +# writing the MLOG_FILE_RENAME records. As the mini-transaction +# is not committed, the renames will not be replayed. +CREATE TABLE t2 (f1 int not null, f2 int not null) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1,2),(3,4); +SET DEBUG_DBUG='+d,innodb_alter_commit_crash_before_commit'; +ALTER TABLE t2 ADD PRIMARY KEY (f2, f1); +ERROR HY000: Lost connection to MySQL server during query +# Startup the server after the crash +# Read and remember the temporary table name +# Manual *.frm recovery begin. The dictionary was not updated +# and the files were not renamed. The rebuilt table +# was left behind on purpose, to faciliate data recovery. +# Manual recovery end +# Drop the orphaned rebuilt table. +SHOW TABLES; +Tables_in_test +t2 +INSERT INTO t2 VALUES (5,6),(7,8); +SELECT * from t2; +f1 f2 +1 2 +3 4 +5 6 +7 8 +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `f1` int(11) NOT NULL, + `f2` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t2; +CREATE TABLE t2 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=InnoDB; +ALTER TABLE t2 ADD PRIMARY KEY (f2, f1); +DROP TABLE t2; +# ------------------------- +# End of Testing Scenario 2 +# ------------------------- +# +# Bug#19330255 WL#7142 - CRASH DURING ALTER TABLE LEADS TO +# DATA DICTIONARY INCONSISTENCY +# +CREATE TABLE t1(a int PRIMARY KEY, b varchar(255), c int NOT NULL) +ENGINE=InnoDB; +INSERT INTO t1 SET a=1,c=2; +SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit'; +ALTER TABLE t1 ADD INDEX (b), CHANGE c d int, ALGORITHM=INPLACE; +ERROR HY000: Lost connection to MySQL server during query +# Restart mysqld after the crash and reconnect. +# Manual *.frm recovery begin. +# Manual recovery end +FLUSH TABLES; +# Drop the orphaned original table. +# Files in datadir after manual recovery. +t1.frm +t1.ibd +SHOW TABLES; +Tables_in_test +t1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(255) DEFAULT NULL, + `d` int(11) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +UPDATE t1 SET d=NULL; +SELECT * FROM t1; +a b d +1 NULL NULL +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/alter_crash.test b/mysql-test/suite/innodb/t/alter_crash.test new file mode 100644 index 00000000000..54cc51aecf4 --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_crash.test @@ -0,0 +1,228 @@ +# Crash-safe InnoDB ALTER operations + +--source include/not_valgrind.inc +--source include/not_embedded.inc +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/not_crashrep.inc + +--disable_query_log +call mtr.add_suppression('InnoDB: cannot find a free slot for an undo log'); +call mtr.add_suppression('InnoDB: row_merge_rename_index_to_add failed with error 47'); +call mtr.add_suppression('InnoDB: Flagged corruption of `c[23]`'); +call mtr.add_suppression('InnoDB: Index `c[23]` .*is corrupted'); +--enable_query_log + +--echo # +--echo # Bug#20015132 ALTER TABLE FAILS TO CHECK IF TABLE IS CORRUPTED +--echo # + +CREATE TABLE t1(c1 INT PRIMARY KEY, c2 CHAR(1), c3 INT UNSIGNED) ENGINE=InnoDB; +SET @saved_debug_dbug = @@SESSION.debug_dbug; +SET DEBUG_DBUG='+d,ib_create_table_fail_too_many_trx'; +--error ER_TOO_MANY_CONCURRENT_TRXS +ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); + +SET DEBUG_DBUG=@saved_debug_dbug; +ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); +# Flag the secondary indexes corrupted. +SET DEBUG_DBUG='+d,dict_set_index_corrupted'; +CHECK TABLE t1; + +# Ensure that the corruption is permanent. +--source include/restart_mysqld.inc +CHECK TABLE t1; +ALTER TABLE t1 DROP INDEX c2; +CHECK TABLE t1; +# We refuse an ALTER TABLE that would modify the InnoDB data dictionary +# while leaving some of the table corrupted. +--error ER_INDEX_CORRUPT +ALTER TABLE t1 ADD INDEX (c2,c3); +# This will rebuild the table, uncorrupting all secondary indexes. +ALTER TABLE t1 CHANGE c3 c3 INT NOT NULL; +CHECK TABLE t1; +ALTER TABLE t1 ADD INDEX (c2,c3); +DROP TABLE t1; + +let $MYSQLD_DATADIR= `select @@datadir`; +let datadir= `select @@datadir`; + +# These are from include/shutdown_mysqld.inc and allow to call start_mysqld.inc +--let $_server_id= `SELECT @@server_id` +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect + +--echo # +--echo # Bug #14669848 CRASH DURING ALTER MAKES ORIGINAL TABLE INACCESSIBLE +--echo # +--echo # -- Scenario 1: +--echo # Crash the server in ha_innobase::commit_inplace_alter_table() +--echo # just after committing the dictionary changes. + +CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb; +INSERT INTO t1 VALUES (1,2),(3,4); +SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit'; + +let $orig_table_id = `SELECT table_id + FROM information_schema.innodb_sys_tables + WHERE name = 'test/t1'`; + +# Write file to make mysql-test-run.pl expect crash +--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +--error 2013 +ALTER TABLE t1 ADD PRIMARY KEY (f2, f1); + +--echo # Restart mysqld after the crash and reconnect. +--source include/start_mysqld.inc + +let $temp_table_name = `SELECT SUBSTR(name, 6) + FROM information_schema.innodb_sys_tables + WHERE table_id = $orig_table_id`; + +--echo # Manual *.frm recovery begin. + +--move_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/$temp_table_name.frm + +perl; +my @frm_file = glob "$ENV{'datadir'}/test/#sql-*.frm"; +my $t1_frm = "$ENV{'datadir'}/test/t1.frm"; +rename($frm_file[0], $t1_frm); +EOF + +--echo # Manual recovery end + +FLUSH TABLES; + +--echo # Drop the orphaned original table. +--disable_query_log +eval DROP TABLE `#mysql50#$temp_table_name`; +--enable_query_log + +--echo # Files in datadir after manual recovery. +--list_files $MYSQLD_DATADIR/test + +SHOW TABLES; +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (5,6),(7,8); +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=InnoDB; +ALTER TABLE t1 ADD PRIMARY KEY (f2, f1); +DROP TABLE t1; + +--echo # -- Scenario 2: +--echo # Crash the server in ha_innobase::commit_inplace_alter_table() +--echo # just before committing the dictionary changes, but after +--echo # writing the MLOG_FILE_RENAME records. As the mini-transaction +--echo # is not committed, the renames will not be replayed. + +CREATE TABLE t2 (f1 int not null, f2 int not null) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1,2),(3,4); +SET DEBUG_DBUG='+d,innodb_alter_commit_crash_before_commit'; + +let $orig_table_id = `SELECT table_id + FROM information_schema.innodb_sys_tables + WHERE name = 'test/t2'`; + +# Write file to make mysql-test-run.pl expect crash +--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +--error 2013 +ALTER TABLE t2 ADD PRIMARY KEY (f2, f1); + +--echo # Startup the server after the crash +--source include/start_mysqld.inc + +--echo # Read and remember the temporary table name +let $temp_table_name = `SELECT SUBSTRING(name,6) + FROM information_schema.innodb_sys_tables + WHERE name LIKE "test/#sql-ib$orig_table_id%"`; +# This second copy is an environment variable for the perl script below. +let temp_table_name = $temp_table_name; + +--echo # Manual *.frm recovery begin. The dictionary was not updated +--echo # and the files were not renamed. The rebuilt table +--echo # was left behind on purpose, to faciliate data recovery. + +perl; +my @frm_file = glob "$ENV{'datadir'}/test/#sql-*.frm"; +my $target_frm = "$ENV{'datadir'}/test/$ENV{'temp_table_name'}.frm"; +rename($frm_file[0], $target_frm); +EOF + +--echo # Manual recovery end + +--echo # Drop the orphaned rebuilt table. +--disable_query_log +eval DROP TABLE `#mysql50#$temp_table_name`; +--enable_query_log + +SHOW TABLES; +INSERT INTO t2 VALUES (5,6),(7,8); +SELECT * from t2; +SHOW CREATE TABLE t2; +DROP TABLE t2; + +CREATE TABLE t2 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=InnoDB; +ALTER TABLE t2 ADD PRIMARY KEY (f2, f1); +DROP TABLE t2; +--list_files $MYSQLD_DATADIR/test + +--echo # ------------------------- +--echo # End of Testing Scenario 2 +--echo # ------------------------- + +--echo # +--echo # Bug#19330255 WL#7142 - CRASH DURING ALTER TABLE LEADS TO +--echo # DATA DICTIONARY INCONSISTENCY +--echo # + +CREATE TABLE t1(a int PRIMARY KEY, b varchar(255), c int NOT NULL) +ENGINE=InnoDB; +INSERT INTO t1 SET a=1,c=2; +SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit'; + +let $orig_table_id = `select table_id from + information_schema.innodb_sys_tables where name = 'test/t1'`; + +# FIXME: MDEV-9469 'Incorrect key file' on ALTER TABLE +# Write file to make mysql-test-run.pl expect crash +--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +# +--error 2013 +ALTER TABLE t1 ADD INDEX (b), CHANGE c d int, ALGORITHM=INPLACE; + +--echo # Restart mysqld after the crash and reconnect. +--source include/start_mysqld.inc + +let $temp_table_name = `SELECT SUBSTR(name, 6) + FROM information_schema.innodb_sys_tables + WHERE table_id = $orig_table_id`; + +--echo # Manual *.frm recovery begin. +--move_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/$temp_table_name.frm + +perl; +my @frm_file = glob "$ENV{'datadir'}/test/#sql-*.frm"; +my $t1_frm = "$ENV{'datadir'}/test/t1.frm"; +rename($frm_file[0], $t1_frm); +EOF + +--echo # Manual recovery end + +FLUSH TABLES; + +--echo # Drop the orphaned original table. +--disable_query_log +eval DROP TABLE `#mysql50#$temp_table_name`; +--enable_query_log + +--echo # Files in datadir after manual recovery. +--list_files $MYSQLD_DATADIR/test + +SHOW TABLES; +SHOW CREATE TABLE t1; +UPDATE t1 SET d=NULL; +SELECT * FROM t1; +DROP TABLE t1; From e3adf96aeb39ad43143ca2a9c7a2f23d06e8e26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 7 Mar 2019 15:35:55 +0200 Subject: [PATCH 5/5] MDEV-13818 CREATE INDEX leaks memory if running out of undo log space row_merge_create_index_graph(): Relay the internal state from dict_create_index_step(). Our caller should free the index only if it was not copied, added to the cache, and freed. row_merge_create_index(): Free the index template if it was not added to the cache. This is a safer variant of the logic that was introduced in 65070beffd2e9279145b48d1f27c517b6588e543 in 10.2. prepare_inplace_alter_table_dict(): Add additional fault injection to exercise a code path where we have already added an index to the cache. --- mysql-test/suite/innodb/r/alter_crash.result | 15 +++++++++------ mysql-test/suite/innodb/t/alter_crash.test | 9 +++++---- storage/innobase/handler/handler0alter.cc | 12 ++++++++++-- storage/innobase/include/ut0dbg.h | 3 ++- storage/innobase/row/row0merge.cc | 17 ++++++++++------- storage/xtradb/handler/handler0alter.cc | 12 ++++++++++-- storage/xtradb/include/ut0dbg.h | 3 ++- storage/xtradb/row/row0merge.cc | 17 ++++++++++------- 8 files changed, 58 insertions(+), 30 deletions(-) diff --git a/mysql-test/suite/innodb/r/alter_crash.result b/mysql-test/suite/innodb/r/alter_crash.result index 8de02cc5fbd..4b49a788cd4 100644 --- a/mysql-test/suite/innodb/r/alter_crash.result +++ b/mysql-test/suite/innodb/r/alter_crash.result @@ -3,6 +3,9 @@ # CREATE TABLE t1(c1 INT PRIMARY KEY, c2 CHAR(1), c3 INT UNSIGNED) ENGINE=InnoDB; SET @saved_debug_dbug = @@SESSION.debug_dbug; +SET DEBUG_DBUG='+d,create_index_metadata_fail'; +ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); +ERROR HY000: The table 't1' is full SET DEBUG_DBUG='+d,ib_create_table_fail_too_many_trx'; ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); ERROR HY000: Too many active concurrent transactions @@ -11,21 +14,21 @@ ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); SET DEBUG_DBUG='+d,dict_set_index_corrupted'; CHECK TABLE t1; Table Op Msg_type Msg_text -test.t1 check Warning InnoDB: Index c2 is marked as corrupted -test.t1 check Warning InnoDB: Index c3 is marked as corrupted +test.t1 check Warning InnoDB: Index "c2" is marked as corrupted +test.t1 check Warning InnoDB: Index "c3" is marked as corrupted test.t1 check error Corrupt CHECK TABLE t1; Table Op Msg_type Msg_text -test.t1 check Warning InnoDB: Index c2 is marked as corrupted -test.t1 check Warning InnoDB: Index c3 is marked as corrupted +test.t1 check Warning InnoDB: Index "c2" is marked as corrupted +test.t1 check Warning InnoDB: Index "c3" is marked as corrupted test.t1 check error Corrupt ALTER TABLE t1 DROP INDEX c2; CHECK TABLE t1; Table Op Msg_type Msg_text -test.t1 check Warning InnoDB: Index c3 is marked as corrupted +test.t1 check Warning InnoDB: Index "c3" is marked as corrupted test.t1 check error Corrupt ALTER TABLE t1 ADD INDEX (c2,c3); -ERROR HY000: Index c3 is corrupted +ERROR HY000: Index "c3" is corrupted ALTER TABLE t1 CHANGE c3 c3 INT NOT NULL; CHECK TABLE t1; Table Op Msg_type Msg_text diff --git a/mysql-test/suite/innodb/t/alter_crash.test b/mysql-test/suite/innodb/t/alter_crash.test index 54cc51aecf4..b417b441723 100644 --- a/mysql-test/suite/innodb/t/alter_crash.test +++ b/mysql-test/suite/innodb/t/alter_crash.test @@ -7,10 +7,7 @@ --source include/not_crashrep.inc --disable_query_log -call mtr.add_suppression('InnoDB: cannot find a free slot for an undo log'); -call mtr.add_suppression('InnoDB: row_merge_rename_index_to_add failed with error 47'); -call mtr.add_suppression('InnoDB: Flagged corruption of `c[23]`'); -call mtr.add_suppression('InnoDB: Index `c[23]` .*is corrupted'); +call mtr.add_suppression('InnoDB: Flagged corruption of c[23]'); --enable_query_log --echo # @@ -19,6 +16,10 @@ call mtr.add_suppression('InnoDB: Index `c[23]` .*is corrupted'); CREATE TABLE t1(c1 INT PRIMARY KEY, c2 CHAR(1), c3 INT UNSIGNED) ENGINE=InnoDB; SET @saved_debug_dbug = @@SESSION.debug_dbug; +SET DEBUG_DBUG='+d,create_index_metadata_fail'; +--error ER_RECORD_FILE_FULL +ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); + SET DEBUG_DBUG='+d,ib_create_table_fail_too_many_trx'; --error ER_TOO_MANY_CONCURRENT_TRXS ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3); diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 2bcec2ce51f..b03b70e488e 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -3090,10 +3090,18 @@ prepare_inplace_alter_table_dict( /* Create the indexes in SYS_INDEXES and load into dictionary. */ for (ulint a = 0; a < ctx->num_to_add_index; a++) { - + DBUG_EXECUTE_IF( + "create_index_metadata_fail", + if (a + 1 == ctx->num_to_add_index) { + ctx->trx->error_state = DB_OUT_OF_FILE_SPACE; + ctx->add_index[a] = NULL; + goto index_created; + }); ctx->add_index[a] = row_merge_create_index( ctx->trx, ctx->new_table, &index_defs[a]); - +#ifndef DBUG_OFF +index_created: +#endif add_key_nums[a] = index_defs[a].key_number; if (!ctx->add_index[a]) { diff --git a/storage/innobase/include/ut0dbg.h b/storage/innobase/include/ut0dbg.h index 3f5baef0a3c..b4c941bc163 100644 --- a/storage/innobase/include/ut0dbg.h +++ b/storage/innobase/include/ut0dbg.h @@ -1,6 +1,7 @@ /***************************************************************************** Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2017, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -84,7 +85,7 @@ ut_dbg_assertion_failed( /** Debug assertion. Does nothing unless UNIV_DEBUG is defined. */ #define ut_ad(EXPR) ut_a(EXPR) /** Debug statement. Does nothing unless UNIV_DEBUG is defined. */ -#define ut_d(EXPR) do {EXPR;} while (0) +#define ut_d(EXPR) EXPR #else /** Debug assertion. Does nothing unless UNIV_DEBUG is defined. */ #define ut_ad(EXPR) diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index 356eec8e7ee..9fa2eaccfa8 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2005, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2014, 2018, MariaDB Corporation. +Copyright (c) 2014, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -3702,7 +3702,7 @@ row_merge_create_index_graph( /*=========================*/ trx_t* trx, /*!< in: trx */ dict_table_t* table, /*!< in: table */ - dict_index_t* index) /*!< in: index */ + dict_index_t*& index) /*!< in,out: index */ { ind_node_t* node; /*!< Index creation node */ mem_heap_t* heap; /*!< Memory heap */ @@ -3726,6 +3726,8 @@ row_merge_create_index_graph( err = trx->error_state; + index = node->index; + que_graph_free((que_t*) que_node_get_parent(thr)); return(err); @@ -3767,20 +3769,21 @@ row_merge_create_index( ifield->prefix_len); } + ut_d(const dict_index_t* const index_template = index); /* Add the index to SYS_INDEXES, using the index prototype. */ err = row_merge_create_index_graph(trx, table, index); if (err == DB_SUCCESS) { - - index = dict_table_get_index_on_name(table, index_def->name); - - ut_a(index); - + ut_ad(index != index_template); /* Note the id of the transaction that created this index, we use it to restrict readers from accessing this index, to ensure read consistency. */ ut_ad(index->trx_id == trx->id); } else { + ut_ad(!index || index == index_template); + if (index) { + dict_mem_index_free(index); + } index = NULL; } diff --git a/storage/xtradb/handler/handler0alter.cc b/storage/xtradb/handler/handler0alter.cc index fa41c963464..b0cfb17fe28 100644 --- a/storage/xtradb/handler/handler0alter.cc +++ b/storage/xtradb/handler/handler0alter.cc @@ -3097,10 +3097,18 @@ prepare_inplace_alter_table_dict( /* Create the indexes in SYS_INDEXES and load into dictionary. */ for (ulint a = 0; a < ctx->num_to_add_index; a++) { - + DBUG_EXECUTE_IF( + "create_index_metadata_fail", + if (a + 1 == ctx->num_to_add_index) { + ctx->trx->error_state = DB_OUT_OF_FILE_SPACE; + ctx->add_index[a] = NULL; + goto index_created; + }); ctx->add_index[a] = row_merge_create_index( ctx->trx, ctx->new_table, &index_defs[a]); - +#ifndef DBUG_OFF +index_created: +#endif add_key_nums[a] = index_defs[a].key_number; if (!ctx->add_index[a]) { diff --git a/storage/xtradb/include/ut0dbg.h b/storage/xtradb/include/ut0dbg.h index 3f5baef0a3c..b4c941bc163 100644 --- a/storage/xtradb/include/ut0dbg.h +++ b/storage/xtradb/include/ut0dbg.h @@ -1,6 +1,7 @@ /***************************************************************************** Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2017, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -84,7 +85,7 @@ ut_dbg_assertion_failed( /** Debug assertion. Does nothing unless UNIV_DEBUG is defined. */ #define ut_ad(EXPR) ut_a(EXPR) /** Debug statement. Does nothing unless UNIV_DEBUG is defined. */ -#define ut_d(EXPR) do {EXPR;} while (0) +#define ut_d(EXPR) EXPR #else /** Debug assertion. Does nothing unless UNIV_DEBUG is defined. */ #define ut_ad(EXPR) diff --git a/storage/xtradb/row/row0merge.cc b/storage/xtradb/row/row0merge.cc index e4440640431..02d272f095a 100644 --- a/storage/xtradb/row/row0merge.cc +++ b/storage/xtradb/row/row0merge.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2005, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2014, 2018, MariaDB Corporation. +Copyright (c) 2014, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -3705,7 +3705,7 @@ row_merge_create_index_graph( /*=========================*/ trx_t* trx, /*!< in: trx */ dict_table_t* table, /*!< in: table */ - dict_index_t* index) /*!< in: index */ + dict_index_t*& index) /*!< in,out: index */ { ind_node_t* node; /*!< Index creation node */ mem_heap_t* heap; /*!< Memory heap */ @@ -3729,6 +3729,8 @@ row_merge_create_index_graph( err = trx->error_state; + index = node->index; + que_graph_free((que_t*) que_node_get_parent(thr)); return(err); @@ -3770,20 +3772,21 @@ row_merge_create_index( ifield->prefix_len); } + ut_d(const dict_index_t* const index_template = index); /* Add the index to SYS_INDEXES, using the index prototype. */ err = row_merge_create_index_graph(trx, table, index); if (err == DB_SUCCESS) { - - index = dict_table_get_index_on_name(table, index_def->name); - - ut_a(index); - + ut_ad(index != index_template); /* Note the id of the transaction that created this index, we use it to restrict readers from accessing this index, to ensure read consistency. */ ut_ad(index->trx_id == trx->id); } else { + ut_ad(!index || index == index_template); + if (index) { + dict_mem_index_free(index); + } index = NULL; }