summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/include/commit.inc2
-rw-r--r--mysql-test/lib/My/Debugger.pm2
-rw-r--r--mysql-test/main/commit_1innodb.result2
-rw-r--r--mysql-test/main/mdev_22370.result4
-rw-r--r--mysql-test/main/mdev_22370.test17
-rw-r--r--mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result40
-rw-r--r--mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test95
-rw-r--r--mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result77
-rw-r--r--mysql-test/suite/rpl/r/rpl_slave_shutdown_mdev20821.result79
-rw-r--r--mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test142
-rw-r--r--mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.cnf19
-rw-r--r--mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.test171
-rw-r--r--sql/handler.h12
-rw-r--r--sql/log_event.cc4
-rw-r--r--sql/rpl_parallel.cc15
-rw-r--r--sql/rpl_parallel.h2
-rw-r--r--sql/rpl_rli.cc4
-rw-r--r--sql/slave.cc3
-rw-r--r--sql/sql_admin.cc66
-rw-r--r--sql/sql_class.h3
-rw-r--r--storage/innobase/handler/ha_innodb.cc8
21 files changed, 722 insertions, 45 deletions
diff --git a/mysql-test/include/commit.inc b/mysql-test/include/commit.inc
index a28d3e5f3d1..1844a5320f7 100644
--- a/mysql-test/include/commit.inc
+++ b/mysql-test/include/commit.inc
@@ -770,7 +770,7 @@ call p_verify_status_increment(2, 0, 2, 0);
commit;
call p_verify_status_increment(0, 0, 0, 0);
check table t1, t2, t3;
-call p_verify_status_increment(6, 0, 6, 0);
+call p_verify_status_increment(4, 0, 4, 0);
commit;
call p_verify_status_increment(0, 0, 0, 0);
drop view v1;
diff --git a/mysql-test/lib/My/Debugger.pm b/mysql-test/lib/My/Debugger.pm
index 5e04c070e7f..cc151b233d5 100644
--- a/mysql-test/lib/My/Debugger.pm
+++ b/mysql-test/lib/My/Debugger.pm
@@ -139,7 +139,7 @@ sub do_args($$$$$) {
my $v = $debuggers{$k};
# on windows mtr args are quoted (for system), otherwise not (for exec)
- sub quote($) { $_[0] =~ / / ? "\"$_[0]\"" : $_[0] }
+ sub quote($) { $_[0] =~ /[; ]/ ? "\"$_[0]\"" : $_[0] }
sub unquote($) { $_[0] =~ s/^"(.*)"$/$1/; $_[0] }
sub quote_from_mtr($) { IS_WINDOWS() ? $_[0] : quote($_[0]) }
sub unquote_for_mtr($) { IS_WINDOWS() ? $_[0] : unquote($_[0]) }
diff --git a/mysql-test/main/commit_1innodb.result b/mysql-test/main/commit_1innodb.result
index 7d21540b548..a7b544dc282 100644
--- a/mysql-test/main/commit_1innodb.result
+++ b/mysql-test/main/commit_1innodb.result
@@ -874,7 +874,7 @@ Table Op Msg_type Msg_text
test.t1 check status OK
test.t2 check status OK
test.t3 check status OK
-call p_verify_status_increment(6, 0, 6, 0);
+call p_verify_status_increment(4, 0, 4, 0);
SUCCESS
commit;
diff --git a/mysql-test/main/mdev_22370.result b/mysql-test/main/mdev_22370.result
new file mode 100644
index 00000000000..d422ee6e81f
--- /dev/null
+++ b/mysql-test/main/mdev_22370.result
@@ -0,0 +1,4 @@
+connect con1,localhost,root,,;
+SET DEBUG_DBUG='+d,mark_busy_mdev_22370';
+FLUSH TABLES WITH READ LOCK;
+connection default;
diff --git a/mysql-test/main/mdev_22370.test b/mysql-test/main/mdev_22370.test
new file mode 100644
index 00000000000..86bc527ebc0
--- /dev/null
+++ b/mysql-test/main/mdev_22370.test
@@ -0,0 +1,17 @@
+#
+# MDEV-22370 safe_mutex: Trying to lock uninitialized mutex at
+# /data/src/10.4-bug/sql/rpl_parallel.cc, line 470 upon shutdown during FTWRL
+#
+# Purpose of this test case to test crash while FTWRL and shutdown is in race
+# condition
+# Shutdown can execute first and destroy the mutex making mutex_lock in pool_mark_busy
+# to crash
+
+--source include/have_debug.inc
+--connect (con1,localhost,root,,)
+SET DEBUG_DBUG='+d,mark_busy_mdev_22370';
+--send
+ FLUSH TABLES WITH READ LOCK;
+
+--connection default
+--source include/restart_mysqld.inc
diff --git a/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result b/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result
new file mode 100644
index 00000000000..a2eb7ee5c0a
--- /dev/null
+++ b/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result
@@ -0,0 +1,40 @@
+#
+# Kill OPTIMIZE command prior to table modification
+#
+RESET MASTER;
+CREATE TABLE t1 (f INT) ENGINE=INNODB;
+CREATE TABLE t2 (f INT) ENGINE=INNODB;
+connect con1,127.0.0.1,root,,test,$MASTER_MYPORT,;
+connection con1;
+SET debug_sync='admin_command_kill_before_modify SIGNAL ready_to_be_killed WAIT_FOR master_cont';
+OPTIMIZE TABLE t1,t2;
+connection default;
+SET debug_sync='now WAIT_FOR ready_to_be_killed';
+KILL THD_ID;
+SET debug_sync = 'reset';
+disconnect con1;
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Gtid # # GTID #-#-#
+master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (f INT) ENGINE=INNODB
+master-bin.000001 # Gtid # # GTID #-#-#
+master-bin.000001 # Query # # use `test`; CREATE TABLE t2 (f INT) ENGINE=INNODB
+DROP TABLE t1,t2;
+FLUSH LOGS;
+#
+# Kill OPTIMIZE command after table modification
+#
+CREATE TABLE t1 (f INT) ENGINE=INNODB;
+CREATE TABLE t2 (f INT) ENGINE=INNODB;
+connect con1,127.0.0.1,root,,test,$MASTER_MYPORT,;
+connection con1;
+SET debug_sync='admin_command_kill_after_modify SIGNAL ready_to_be_killed WAIT_FOR master_cont';
+OPTIMIZE TABLE t1,t2;
+connection default;
+SET debug_sync='now WAIT_FOR ready_to_be_killed';
+KILL THD_ID;
+SET debug_sync = 'reset';
+disconnect con1;
+DROP TABLE t1,t2;
+FLUSH LOGS;
+FOUND 1 /OPTIMIZE TABLE t1,t2/ in mysqlbinlog.out
diff --git a/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test b/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test
new file mode 100644
index 00000000000..9b248097ae2
--- /dev/null
+++ b/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test
@@ -0,0 +1,95 @@
+# ==== Purpose ====
+#
+# Test verifies that when an admin command execution is interrupted by KILL
+# command it should stop its execution. The admin command in binary log should
+# contain only the list of tables which have successfully executed admin
+# command prior to kill.
+#
+# ==== Implementation ====
+#
+# Steps:
+# 0 - Create two table t1,t2.
+# 1 - Execute OPTIMIZE TABLE t1,t2 command.
+# 2 - Using debug sync mechanism kill OPTIMIZE TABLE command at a stage
+# where it has not optimized any table.
+# 3 - Check that OPTIMIZE TABLE command is not written to binary log.
+# 4 - Using debug sync mechanism hold the execution of OPTIMIZE TABLE after
+# t1 table optimization. Now kill the OPTIMIZE TABLE command.
+# 5 - Observe the binlog output, the OPTIMIZE TABLE command should display `t1,t2`.
+# 6 - Please note that, we binlog the entire query even if at least one
+# table is modified as admin commands are safe to replicate and they will
+# not make the slave to diverge.
+#
+# ==== References ====
+#
+# MDEV-22530: Aborting OPTIMIZE TABLE still logs in binary log and replicates to the Slave server.
+#
+--source include/have_log_bin.inc
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+--source include/have_innodb.inc
+
+--echo #
+--echo # Kill OPTIMIZE command prior to table modification
+--echo #
+RESET MASTER;
+
+CREATE TABLE t1 (f INT) ENGINE=INNODB;
+CREATE TABLE t2 (f INT) ENGINE=INNODB;
+
+--connect(con1,127.0.0.1,root,,test,$MASTER_MYPORT,)
+--connection con1
+SET debug_sync='admin_command_kill_before_modify SIGNAL ready_to_be_killed WAIT_FOR master_cont';
+--send OPTIMIZE TABLE t1,t2
+
+--connection default
+SET debug_sync='now WAIT_FOR ready_to_be_killed';
+--let $thd_id= `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE INFO LIKE '%OPTIMIZE TABLE %'`
+
+# Now kill.
+--replace_result $thd_id THD_ID
+eval KILL $thd_id;
+
+SET debug_sync = 'reset';
+--disconnect con1
+
+--source include/show_binlog_events.inc
+DROP TABLE t1,t2;
+
+FLUSH LOGS;
+
+--echo #
+--echo # Kill OPTIMIZE command after table modification
+--echo #
+
+CREATE TABLE t1 (f INT) ENGINE=INNODB;
+CREATE TABLE t2 (f INT) ENGINE=INNODB;
+
+--connect(con1,127.0.0.1,root,,test,$MASTER_MYPORT,)
+--connection con1
+SET debug_sync='admin_command_kill_after_modify SIGNAL ready_to_be_killed WAIT_FOR master_cont';
+--send OPTIMIZE TABLE t1,t2
+
+--connection default
+SET debug_sync='now WAIT_FOR ready_to_be_killed';
+--let $thd_id= `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE INFO LIKE '%OPTIMIZE TABLE %'`
+
+# Now kill.
+--replace_result $thd_id THD_ID
+eval KILL $thd_id;
+
+SET debug_sync = 'reset';
+--disconnect con1
+
+DROP TABLE t1,t2;
+let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1);
+FLUSH LOGS;
+
+--let $MYSQLD_DATADIR= `select @@datadir`
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/$binlog_file > $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+
+--let SEARCH_PATTERN= OPTIMIZE TABLE t1,t2
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--remove_file $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
diff --git a/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result b/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result
new file mode 100644
index 00000000000..a39dad85244
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result
@@ -0,0 +1,77 @@
+include/rpl_init.inc [topology=1->2]
+connection server_1;
+FLUSH TABLES;
+ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
+connection server_2;
+SET @save_slave_parallel_threads= @@GLOBAL.slave_parallel_threads;
+SET @save_slave_parallel_mode= @@GLOBAL.slave_parallel_mode;
+include/stop_slave.inc
+SET GLOBAL slave_parallel_threads=2;
+SET GLOBAL slave_parallel_mode=optimistic;
+include/start_slave.inc
+connection server_1;
+CREATE TABLE t1(a INT) ENGINE=INNODB;
+OPTIMIZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 optimize note Table does not support optimize, doing recreate + analyze instead
+test.t1 optimize status OK
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 SELECT 1+a FROM t1;
+INSERT INTO t1 SELECT 2+a FROM t1;
+connection server_2;
+#
+# Verify that following admin commands are marked as ddl
+# 'OPTIMIZE TABLE', 'REPAIR TABLE' and 'ANALYZE TABLE'
+#
+connection server_1;
+OPTIMIZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 optimize note Table does not support optimize, doing recreate + analyze instead
+test.t1 optimize status OK
+REPAIR TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 repair note The storage engine for the table doesn't support repair
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status OK
+FLUSH LOGS;
+FOUND 1 /GTID 0-1-8 ddl/ in mysqlbinlog.out
+FOUND 1 /GTID 0-1-9 ddl/ in mysqlbinlog.out
+FOUND 1 /GTID 0-1-10 ddl/ in mysqlbinlog.out
+#
+# Clean up
+#
+DROP TABLE t1;
+connection server_2;
+FLUSH LOGS;
+#
+# Check that ALTER TABLE commands with ANALYZE, OPTIMIZE and REPAIR on
+# partitions will be marked as DDL in binary log.
+#
+connection server_1;
+CREATE TABLE t1(id INT) PARTITION BY RANGE (id) (PARTITION p0 VALUES LESS THAN (100),
+PARTITION pmax VALUES LESS THAN (MAXVALUE));
+INSERT INTO t1 VALUES (1), (10), (100), (1000);
+ALTER TABLE t1 ANALYZE PARTITION p0;
+Table Op Msg_type Msg_text
+test.t1 analyze status OK
+ALTER TABLE t1 OPTIMIZE PARTITION p0;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+ALTER TABLE t1 REPAIR PARTITION p0;
+Table Op Msg_type Msg_text
+test.t1 repair status OK
+FLUSH LOGS;
+FOUND 1 /GTID 0-1-14 ddl/ in mysqlbinlog.out
+FOUND 1 /GTID 0-1-15 ddl/ in mysqlbinlog.out
+FOUND 1 /GTID 0-1-16 ddl/ in mysqlbinlog.out
+#
+# Clean up
+#
+DROP TABLE t1;
+connection server_2;
+include/stop_slave.inc
+SET GLOBAL slave_parallel_threads= @save_slave_parallel_threads;
+SET GLOBAL slave_parallel_mode= @save_slave_parallel_mode;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/r/rpl_slave_shutdown_mdev20821.result b/mysql-test/suite/rpl/r/rpl_slave_shutdown_mdev20821.result
new file mode 100644
index 00000000000..f90d2126103
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_slave_shutdown_mdev20821.result
@@ -0,0 +1,79 @@
+include/rpl_init.inc [topology=1->3]
+connection server_3;
+set default_master_connection = '';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+set default_master_connection = 'm2';
+change master to master_host='127.0.0.1', master_port=SERVER_MYPORT_2, master_user='root', master_use_gtid=slave_pos;
+include/start_slave.inc
+select @@global.slave_parallel_workers as two;
+two
+2
+connection server_3;
+SHUTDOWN;
+connection server_3;
+connection server_3;
+connection server_1;
+create table t1 (i int primary key) engine=Innodb;
+connection server_2;
+create table t2 (i int primary key) engine=Innodb;
+connection server_3;
+set default_master_connection = '';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+set default_master_connection = 'm2';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+connection server_2;
+insert into t2 values (1);
+connection server_3;
+connection server_1;
+insert into t1 values (1);
+connection server_3;
+connection server_3;
+SHUTDOWN;
+connection server_3;
+connection server_3;
+connection server_3;
+set default_master_connection = '';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+set default_master_connection = 'm2';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+connect conn_block_server3, 127.0.0.1, root,, test, $SERVER_MYPORT_3,;
+begin;
+insert into t1 values (2);
+insert into t2 values (2);
+connection server_1;
+insert into t1 values (2);
+connection server_2;
+insert into t2 values (2);
+connection server_3;
+SHUTDOWN;
+connection server_3;
+connection server_3;
+connection server_3;
+set default_master_connection = '';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+set default_master_connection = 'm2';
+include/start_slave.inc
+Warnings:
+Note 1254 Slave is already running
+connection server_1;
+drop table t1;
+connection server_2;
+drop table t2;
+connection server_3;
+set default_master_connection = 'm2';
+include/stop_slave.inc
+RESET SLAVE ALL;
+set default_master_connection = '';
+include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test b/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test
new file mode 100644
index 00000000000..6d66e3fd088
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test
@@ -0,0 +1,142 @@
+# ==== Purpose ====
+#
+# Test verifies that there is no deadlock or assertion in
+# slave_parallel_mode=optimistic configuration while applying admin command
+# like 'OPTIMIZE TABLE', 'REPAIR TABLE' and 'ANALYZE TABLE'.
+#
+# ==== Implementation ====
+#
+# Steps:
+# 0 - Create a table, execute OPTIMIZE TABLE command on the table followed
+# by some DMLS.
+# 1 - No assert should happen on slave server.
+# 2 - Assert that 'OPTIMIZE TABLE', 'REPAIR TABLE' and 'ANALYZE TABLE' are
+# marked as 'DDL' in the binary log.
+#
+# ==== References ====
+#
+# MDEV-17515: GTID Replication in optimistic mode deadlock
+#
+--source include/have_partition.inc
+--source include/have_innodb.inc
+--let $rpl_topology=1->2
+--source include/rpl_init.inc
+
+--connection server_1
+FLUSH TABLES;
+ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
+
+--connection server_2
+SET @save_slave_parallel_threads= @@GLOBAL.slave_parallel_threads;
+SET @save_slave_parallel_mode= @@GLOBAL.slave_parallel_mode;
+--source include/stop_slave.inc
+SET GLOBAL slave_parallel_threads=2;
+SET GLOBAL slave_parallel_mode=optimistic;
+--source include/start_slave.inc
+
+--connection server_1
+CREATE TABLE t1(a INT) ENGINE=INNODB;
+OPTIMIZE TABLE t1;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 SELECT 1+a FROM t1;
+INSERT INTO t1 SELECT 2+a FROM t1;
+--save_master_pos
+
+--connection server_2
+--sync_with_master
+
+--echo #
+--echo # Verify that following admin commands are marked as ddl
+--echo # 'OPTIMIZE TABLE', 'REPAIR TABLE' and 'ANALYZE TABLE'
+--echo #
+--connection server_1
+
+OPTIMIZE TABLE t1;
+--let optimize_gtid= `SELECT @@GLOBAL.gtid_binlog_pos`
+
+REPAIR TABLE t1;
+--let repair_gtid= `SELECT @@GLOBAL.gtid_binlog_pos`
+
+ANALYZE TABLE t1;
+--let analyze_gtid= `SELECT @@GLOBAL.gtid_binlog_pos`
+
+let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1);
+FLUSH LOGS;
+
+--let $MYSQLD_DATADIR= `select @@datadir`
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/$binlog_file > $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+
+--let SEARCH_PATTERN= GTID $optimize_gtid ddl
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_PATTERN= GTID $repair_gtid ddl
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_PATTERN= GTID $analyze_gtid ddl
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Clean up
+--echo #
+DROP TABLE t1;
+--remove_file $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--save_master_pos
+
+--connection server_2
+--sync_with_master
+FLUSH LOGS;
+
+--echo #
+--echo # Check that ALTER TABLE commands with ANALYZE, OPTIMIZE and REPAIR on
+--echo # partitions will be marked as DDL in binary log.
+--echo #
+--connection server_1
+CREATE TABLE t1(id INT) PARTITION BY RANGE (id) (PARTITION p0 VALUES LESS THAN (100),
+ PARTITION pmax VALUES LESS THAN (MAXVALUE));
+INSERT INTO t1 VALUES (1), (10), (100), (1000);
+
+ALTER TABLE t1 ANALYZE PARTITION p0;
+--let analyze_gtid= `SELECT @@GLOBAL.gtid_binlog_pos`
+
+ALTER TABLE t1 OPTIMIZE PARTITION p0;
+--let optimize_gtid= `SELECT @@GLOBAL.gtid_binlog_pos`
+
+ALTER TABLE t1 REPAIR PARTITION p0;
+--let repair_gtid= `SELECT @@GLOBAL.gtid_binlog_pos`
+
+let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1);
+FLUSH LOGS;
+
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/$binlog_file > $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+
+--let SEARCH_PATTERN= GTID $analyze_gtid ddl
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_PATTERN= GTID $optimize_gtid ddl
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_PATTERN= GTID $repair_gtid ddl
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Clean up
+--echo #
+DROP TABLE t1;
+--remove_file $MYSQLTEST_VARDIR/tmp/mysqlbinlog.out
+--save_master_pos
+
+--connection server_2
+--sync_with_master
+
+--source include/stop_slave.inc
+SET GLOBAL slave_parallel_threads= @save_slave_parallel_threads;
+SET GLOBAL slave_parallel_mode= @save_slave_parallel_mode;
+--source include/start_slave.inc
+
+--source include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.cnf b/mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.cnf
new file mode 100644
index 00000000000..1e7cdee510b
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.cnf
@@ -0,0 +1,19 @@
+!include suite/rpl/rpl_1slave_base.cnf
+!include include/default_client.cnf
+
+[mysqld.1]
+log-slave-updates
+gtid-domain-id=1
+
+[mysqld.2]
+log-slave-updates
+gtid-domain-id=2
+
+[mysqld.3]
+log-slave-updates
+gtid-domain-id=3
+slave_parallel_threads=2
+
+[ENV]
+SERVER_MYPORT_3= @mysqld.3.port
+SERVER_MYSOCK_3= @mysqld.3.socket
diff --git a/mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.test b/mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.test
new file mode 100644
index 00000000000..6f73de984d3
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_slave_shutdown_mdev20821.test
@@ -0,0 +1,171 @@
+# MDEV-20821 parallel slave server shutdown hang
+#
+# Test the bug condition of a parallel slave server shutdown
+# hang when the parallel workers were idle.
+# The bug reported scenario is extented to cover the multi-sources case as well as
+# checking is done for both the idle and busy workers cases.
+#
+# MDEV-25336 Parallel replication causes failed assert while restarting
+# Since this test case involves slave restart this will help in testing
+# Mdev-25336 too.
+
+--source include/have_innodb.inc
+--source include/have_binlog_format_mixed.inc
+--let $rpl_topology= 1->3
+--source include/rpl_init.inc
+
+#
+# A. idle workers.
+#
+--connection server_3
+set default_master_connection = '';
+--source include/start_slave.inc
+
+set default_master_connection = 'm2';
+--replace_result $SERVER_MYPORT_2 SERVER_MYPORT_2
+eval change master to master_host='127.0.0.1', master_port=$SERVER_MYPORT_2, master_user='root', master_use_gtid=slave_pos;
+--source include/start_slave.inc
+
+select @@global.slave_parallel_workers as two;
+
+# At this point worker threads have no assignement.
+# Shutdown must not hang.
+# In 10.2/10.3 there should not be any assert failure `prev != 0 && next != 0'
+--connection server_3
+--write_file $MYSQLTEST_VARDIR/tmp/mysqld.3.expect
+wait
+EOF
+--send SHUTDOWN
+--reap
+--source include/wait_until_disconnected.inc
+
+--connection server_3
+--append_file $MYSQLTEST_VARDIR/tmp/mysqld.3.expect
+restart
+EOF
+
+# No hang is *proved* to occur when this point is reached.
+--connection server_3
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+#
+# B. resting workers after some busy time
+#
+--connection server_1
+create table t1 (i int primary key) engine=Innodb;
+
+--connection server_2
+create table t2 (i int primary key) engine=Innodb;
+
+--connection server_3
+set default_master_connection = '';
+--source include/start_slave.inc
+
+set default_master_connection = 'm2';
+--source include/start_slave.inc
+
+--connection server_2
+insert into t2 values (1);
+--save_master_pos
+
+--connection server_3
+--sync_with_master 0,'m2'
+
+--connection server_1
+insert into t1 values (1);
+--save_master_pos
+
+--connection server_3
+--sync_with_master 0,''
+
+# In 10.2/10.3 there should not be any assert failure `prev != 0 && next != 0'
+# At this point worker threads have no assignement.
+# Shutdown must not hang.
+
+--connection server_3
+--write_file $MYSQLTEST_VARDIR/tmp/mysqld.3.expect
+wait
+EOF
+--send SHUTDOWN
+--reap
+--source include/wait_until_disconnected.inc
+
+--connection server_3
+--append_file $MYSQLTEST_VARDIR/tmp/mysqld.3.expect
+restart
+EOF
+
+# No hang is *proved* to occur when this point is reached.
+--connection server_3
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+#
+# C. busy workers
+#
+--connection server_3
+set default_master_connection = '';
+--source include/start_slave.inc
+
+set default_master_connection = 'm2';
+--source include/start_slave.inc
+
+--connect (conn_block_server3, 127.0.0.1, root,, test, $SERVER_MYPORT_3,)
+begin;
+ insert into t1 values (2);
+ insert into t2 values (2);
+
+--connection server_1
+insert into t1 values (2);
+--connection server_2
+insert into t2 values (2);
+
+
+# In 10.2/10.3 there should not be any assert failure `prev != 0 && next != 0'
+# At this point there's a good chance the worker threads are busy.
+# SHUTDOWN must proceed without any delay as above.
+--connection server_3
+--write_file $MYSQLTEST_VARDIR/tmp/mysqld.3.expect
+wait
+EOF
+--send SHUTDOWN
+--reap
+--source include/wait_until_disconnected.inc
+
+--connection server_3
+--append_file $MYSQLTEST_VARDIR/tmp/mysqld.3.expect
+restart
+EOF
+
+# No hang is *proved* to occur when this point is reached.
+--connection server_3
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+
+# Cleanup
+
+--connection server_3
+set default_master_connection = '';
+--source include/start_slave.inc
+
+set default_master_connection = 'm2';
+--source include/start_slave.inc
+
+--connection server_1
+drop table t1;
+
+--connection server_2
+drop table t2;
+--save_master_pos
+
+# (!) The following block is critical to avoid check-mysqld_3.reject by mtr:
+--connection server_3
+--sync_with_master 0,'m2'
+set default_master_connection = 'm2';
+--source include/stop_slave.inc
+RESET SLAVE ALL;
+set default_master_connection = '';
+
+--source include/rpl_end.inc
diff --git a/sql/handler.h b/sql/handler.h
index 55c2f0bcd57..29683b54c80 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -1733,9 +1733,19 @@ struct THD_TRANS
CREATED_TEMP_TABLE= 2,
DROPPED_TEMP_TABLE= 4,
DID_WAIT= 8,
- DID_DDL= 0x10
+ DID_DDL= 0x10,
+ EXECUTED_TABLE_ADMIN_CMD= 0x20
};
+ void mark_executed_table_admin_cmd()
+ {
+ DBUG_PRINT("debug", ("mark_executed_table_admin_cmd"));
+ m_unsafe_rollback_flags|= EXECUTED_TABLE_ADMIN_CMD;
+ }
+ bool trans_executed_admin_cmd()
+ {
+ return (m_unsafe_rollback_flags & EXECUTED_TABLE_ADMIN_CMD) != 0;
+ }
void mark_created_temp_table()
{
DBUG_PRINT("debug", ("mark_created_temp_table"));
diff --git a/sql/log_event.cc b/sql/log_event.cc
index cfebdaa3e3c..c82a721708b 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -7948,8 +7948,10 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg,
flags2|= FL_WAITED;
if (thd_arg->transaction.stmt.trans_did_ddl() ||
thd_arg->transaction.stmt.has_created_dropped_temp_table() ||
+ thd_arg->transaction.stmt.trans_executed_admin_cmd() ||
thd_arg->transaction.all.trans_did_ddl() ||
- thd_arg->transaction.all.has_created_dropped_temp_table())
+ thd_arg->transaction.all.has_created_dropped_temp_table() ||
+ thd_arg->transaction.all.trans_executed_admin_cmd())
flags2|= FL_DDL;
else if (is_transactional && !is_tmp_table)
flags2|= FL_TRANSACTIONAL;
diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc
index 7ebb609dc58..3eb25219ed3 100644
--- a/sql/rpl_parallel.cc
+++ b/sql/rpl_parallel.cc
@@ -454,6 +454,7 @@ pool_mark_busy(rpl_parallel_thread_pool *pool, THD *thd)
So we protect the infrequent operations of FLUSH TABLES WITH READ LOCK and
pool size changes with this condition wait.
*/
+ DBUG_EXECUTE_IF("mark_busy_mdev_22370",my_sleep(1000000););
mysql_mutex_lock(&pool->LOCK_rpl_thread_pool);
if (thd)
{
@@ -2000,9 +2001,23 @@ rpl_parallel_thread_pool::init(uint32 size)
void
rpl_parallel_thread_pool::destroy()
{
+ deactivate();
+ destroy_cond_mutex();
+}
+
+void
+rpl_parallel_thread_pool::deactivate()
+{
if (!inited)
return;
rpl_parallel_change_thread_count(this, 0, 1);
+}
+
+void
+rpl_parallel_thread_pool::destroy_cond_mutex()
+{
+ if (!inited)
+ return;
mysql_mutex_destroy(&LOCK_rpl_thread_pool);
mysql_cond_destroy(&COND_rpl_thread_pool);
inited= false;
diff --git a/sql/rpl_parallel.h b/sql/rpl_parallel.h
index 4579d0da9bc..0fa28e32291 100644
--- a/sql/rpl_parallel.h
+++ b/sql/rpl_parallel.h
@@ -244,6 +244,8 @@ struct rpl_parallel_thread_pool {
rpl_parallel_thread_pool();
int init(uint32 size);
void destroy();
+ void deactivate();
+ void destroy_cond_mutex();
struct rpl_parallel_thread *get_thread(rpl_parallel_thread **owner,
rpl_parallel_entry *entry);
void release_thread(rpl_parallel_thread *rpt);
diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc
index 58594f6f645..88cb8fc5e1e 100644
--- a/sql/rpl_rli.cc
+++ b/sql/rpl_rli.cc
@@ -207,8 +207,8 @@ a file name for --relay-log-index option", opt_relaylog_index_name);
*/
sql_print_warning("Neither --relay-log nor --relay-log-index were used;"
" so replication "
- "may break when this MySQL server acts as a "
- "slave and has his hostname changed!! Please "
+ "may break when this MariaDB server acts as a "
+ "replica and has its hostname changed. Please "
"use '--log-basename=#' or '--relay-log=%s' to avoid "
"this problem.", ln);
name_warning_sent= 1;
diff --git a/sql/slave.cc b/sql/slave.cc
index f145d644bb7..2c9ce3f7947 100644
--- a/sql/slave.cc
+++ b/sql/slave.cc
@@ -1261,6 +1261,9 @@ void slave_prepare_for_shutdown()
mysql_mutex_lock(&LOCK_active_mi);
master_info_index->free_connections();
mysql_mutex_unlock(&LOCK_active_mi);
+ // It's safe to destruct worker pool now when
+ // all driver threads are gone.
+ global_rpl_thread_pool.deactivate();
}
/*
diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc
index b5c8514c302..45c2314fef4 100644
--- a/sql/sql_admin.cc
+++ b/sql/sql_admin.cc
@@ -1,5 +1,5 @@
/* Copyright (c) 2010, 2015, Oracle and/or its affiliates.
- Copyright (c) 2011, 2020, MariaDB
+ Copyright (c) 2011, 2021, MariaDB
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
@@ -455,7 +455,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
int (handler::*operator_func)(THD *,
HA_CHECK_OPT *),
int (view_operator_func)(THD *, TABLE_LIST*,
- HA_CHECK_OPT *))
+ HA_CHECK_OPT *),
+ bool is_cmd_replicated)
{
TABLE_LIST *table;
List<Item> field_list;
@@ -466,6 +467,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
int compl_result_code;
bool need_repair_or_alter= 0;
wait_for_commit* suspended_wfc;
+ bool is_table_modified= false;
+
DBUG_ENTER("mysql_admin_table");
DBUG_PRINT("enter", ("extra_open_options: %u", extra_open_options));
@@ -515,6 +518,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
bool open_for_modify= org_open_for_modify;
DBUG_PRINT("admin", ("table: '%s'.'%s'", db, table->table_name.str));
+ DEBUG_SYNC(thd, "admin_command_kill_before_modify");
+
+ if (thd->is_killed())
+ break;
strxmov(table_name, db, ".", table->table_name.str, NullS);
thd->open_options|= extra_open_options;
table->lock_type= lock_type;
@@ -1167,6 +1174,13 @@ send_result_message:
break;
}
}
+ /*
+ Admin commands acquire table locks and these locks are not detected by
+ parallel replication deadlock detection-and-handling mechanism. Hence
+ they must be marked as DDL so that they are not scheduled in parallel
+ with conflicting DMLs resulting in deadlock.
+ */
+ thd->transaction.stmt.mark_executed_table_admin_cmd();
if (table->table && !table->view)
{
if (table->table->s->tmp_table)
@@ -1202,10 +1216,9 @@ send_result_message:
}
else
{
- if (trans_commit_stmt(thd) ||
- (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END) &&
- trans_commit_implicit(thd)))
+ if (trans_commit_stmt(thd))
goto err;
+ is_table_modified= true;
}
close_thread_tables(thd);
thd->release_transactional_locks();
@@ -1228,6 +1241,13 @@ send_result_message:
if (protocol->write())
goto err;
+ DEBUG_SYNC(thd, "admin_command_kill_after_modify");
+ }
+ if (is_table_modified && is_cmd_replicated &&
+ (!opt_readonly || thd->slave_thread) && !thd->lex->no_write_to_binlog)
+ {
+ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
+ goto err;
}
my_eof(thd);
@@ -1290,7 +1310,7 @@ bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables,
check_opt.key_cache= key_cache;
DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt,
"assign_to_keycache", TL_READ_NO_INSERT, 0, 0,
- 0, 0, &handler::assign_to_keycache, 0));
+ 0, 0, &handler::assign_to_keycache, 0, false));
}
@@ -1317,7 +1337,7 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables)
*/
DBUG_RETURN(mysql_admin_table(thd, tables, 0,
"preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0,
- &handler::preload_keys, 0));
+ &handler::preload_keys, 0, false));
}
@@ -1335,15 +1355,7 @@ bool Sql_cmd_analyze_table::execute(THD *thd)
WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table);
res= mysql_admin_table(thd, first_table, &m_lex->check_opt,
"analyze", lock_type, 1, 0, 0, 0,
- &handler::ha_analyze, 0);
- /* ! we write after unlocking the table */
- if (!res && !m_lex->no_write_to_binlog && (!opt_readonly || thd->slave_thread))
- {
- /*
- Presumably, ANALYZE and binlog writing doesn't require synchronization
- */
- res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
- }
+ &handler::ha_analyze, 0, true);
m_lex->select_lex.table_list.first= first_table;
m_lex->query_tables= first_table;
@@ -1367,7 +1379,7 @@ bool Sql_cmd_check_table::execute(THD *thd)
res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "check",
lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0,
- &handler::ha_check, &view_check);
+ &handler::ha_check, &view_check, false);
m_lex->select_lex.table_list.first= first_table;
m_lex->query_tables= first_table;
@@ -1393,15 +1405,7 @@ bool Sql_cmd_optimize_table::execute(THD *thd)
mysql_recreate_table(thd, first_table, true) :
mysql_admin_table(thd, first_table, &m_lex->check_opt,
"optimize", TL_WRITE, 1, 0, 0, 0,
- &handler::ha_optimize, 0);
- /* ! we write after unlocking the table */
- if (!res && !m_lex->no_write_to_binlog && (!opt_readonly || thd->slave_thread))
- {
- /*
- Presumably, OPTIMIZE and binlog writing doesn't require synchronization
- */
- res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
- }
+ &handler::ha_optimize, 0, true);
m_lex->select_lex.table_list.first= first_table;
m_lex->query_tables= first_table;
@@ -1426,16 +1430,8 @@ bool Sql_cmd_repair_table::execute(THD *thd)
TL_WRITE, 1,
MY_TEST(m_lex->check_opt.sql_flags & TT_USEFRM),
HA_OPEN_FOR_REPAIR, &prepare_for_repair,
- &handler::ha_repair, &view_repair);
+ &handler::ha_repair, &view_repair, true);
- /* ! we write after unlocking the table */
- if (!res && !m_lex->no_write_to_binlog && (!opt_readonly || thd->slave_thread))
- {
- /*
- Presumably, REPAIR and binlog writing doesn't require synchronization
- */
- res= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
- }
m_lex->select_lex.table_list.first= first_table;
m_lex->query_tables= first_table;
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 86925a43d76..2b6ac5e7aba 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -4823,7 +4823,8 @@ public:
transaction.all.m_unsafe_rollback_flags|=
(transaction.stmt.m_unsafe_rollback_flags &
(THD_TRANS::DID_WAIT | THD_TRANS::CREATED_TEMP_TABLE |
- THD_TRANS::DROPPED_TEMP_TABLE | THD_TRANS::DID_DDL));
+ THD_TRANS::DROPPED_TEMP_TABLE | THD_TRANS::DID_DDL |
+ THD_TRANS::EXECUTED_TABLE_ADMIN_CMD));
}
/*
Reset current_linfo
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index 2f02fa31c98..eec5c4b3f4a 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -9312,8 +9312,12 @@ ha_innobase::index_read(
/* For R-Tree index, we will always place the page lock to
pages being searched */
- if (dict_index_is_spatial(index)) {
- ++m_prebuilt->trx->will_lock;
+ if (index->is_spatial() && !m_prebuilt->trx->will_lock) {
+ if (trx_is_started(m_prebuilt->trx)) {
+ DBUG_RETURN(HA_ERR_READ_ONLY_TRANSACTION);
+ } else {
+ m_prebuilt->trx->will_lock = true;
+ }
}
/* Note that if the index for which the search template is built is not