Commit 1bdc9bc8 authored by Andrei's avatar Andrei

MDEV-31949 IV. Recovery

MDEV-33168 XA crash-recovery base on engines prepare first rule

This commit address XA transaction's prepare, commit and rollback
commands' crash-recovery both in the normal and semisync slave mode.

Key changes include:
- xid_recovery_member is extended to keep track of XID transaction
  "state" change. The state associated with its XID that when being
  reused could exists in binlog in multiple instance
- xarecover_handlerton is extended to register the user prepare xid
  similarly to the normal transactions
- xarecover_do_commit_or_rollback calls now a new
  xarecover_decide_xa() to decide on a user xid at the
  end of recovery
- TC_LOG_BINLOG::recover and few members of Recovery_context class are
  extended to scan binlog for the user XIDs and keep track of them, and
  their marking as truncatable or contrary - that is durable

Todo:
1. integrate with Xid_list_log_event
2. tests with multiple-engines
parent a4a9b08d
call mtr.add_suppression("Can.t init tc log"); call mtr.add_suppression("Can.t init tc log");
call mtr.add_suppression("Aborting"); call mtr.add_suppression("Aborting");
call mtr.add_suppression("Found.*prepared [XA ]*transaction[s]*");
RESET MASTER; RESET MASTER;
SET @@global.sync_binlog=1; SET @@global.sync_binlog=1;
CREATE TABLE t (f INT) ENGINE=INNODB; CREATE TABLE t (f INT) ENGINE=INNODB;
...@@ -249,7 +250,7 @@ disconnect master2; ...@@ -249,7 +250,7 @@ disconnect master2;
disconnect master3; disconnect master3;
disconnect master4; disconnect master4;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3 # restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-21/ in mysqld.1.err FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-20/ in mysqld.1.err
Pre-crash binlog file content: Pre-crash binlog file content:
include/show_binlog_events.inc include/show_binlog_events.inc
Log_name Pos Event_type Server_id End_log_pos Info Log_name Pos Event_type Server_id End_log_pos Info
...@@ -272,13 +273,9 @@ master-bin.000004 # Xid # # COMMIT /* XID */ ...@@ -272,13 +273,9 @@ master-bin.000004 # Xid # # COMMIT /* XID */
master-bin.000004 # Gtid # # BEGIN GTID #-#-# master-bin.000004 # Gtid # # BEGIN GTID #-#-#
master-bin.000004 # Query # # use `test`; INSERT INTO tm VALUES (10) master-bin.000004 # Query # # use `test`; INSERT INTO tm VALUES (10)
master-bin.000004 # Query # # COMMIT master-bin.000004 # Query # # COMMIT
master-bin.000004 # Gtid # # XA START X'786964',X'',1 GTID #-#-#
master-bin.000004 # Query # # use `test`; DELETE FROM t WHERE f = 10
master-bin.000004 # Query # # XA END X'786964',X'',1
master-bin.000004 # XA_prepare # # XA PREPARE X'786964',X'',1
SELECT @@global.gtid_binlog_pos as 'After the crash'; SELECT @@global.gtid_binlog_pos as 'After the crash';
After the crash After the crash
0-1-20 0-1-19
"One row should be present in table 't'" "One row should be present in table 't'"
SELECT * FROM t; SELECT * FROM t;
f f
...@@ -287,6 +284,202 @@ f ...@@ -287,6 +284,202 @@ f
SELECT * FROM t4; SELECT * FROM t4;
f f
DELETE FROM t; DELETE FROM t;
# no 'xid' in
XA RECOVER;
formatID gtrid_length bqual_length data
SELECT count(*) > 0 as "because of rolled back" FROM t WHERE f = 10;
because of rolled back
0
# Case E.
connect master1,localhost,root,,;
connect master2,localhost,root,,;
connect master3,localhost,root,,;
connect master4,localhost,root,,;
connection default;
INSERT INTO t VALUES (10);
INSERT INTO tm VALUES (10);
connection master1;
CALL sp_xa;
connection master2;
SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready";
INSERT INTO t2 VALUES (20);
connection master3;
SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
SELECT @@global.gtid_binlog_pos as 'Before the crash';
Before the crash
0-1-24
connection master4;
SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master4_ready WAIT_FOR signal_never_arrives";
INSERT INTO t4 VALUES (13);
connection master3;
SET DEBUG_SYNC= "now WAIT_FOR master4_ready";
SELECT @@global.gtid_binlog_pos as 'Before the crash and never logged trx';
Before the crash and never logged trx
0-1-24
connection default;
# Kill the server
disconnect master1;
disconnect master2;
disconnect master3;
disconnect master4;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
# *** Expect NOT FOUND /Successfully truncated ... which is correct. ***
NOT FOUND /Successfully truncated.*to remove transactions starting from GTID 0-1-2[1-4]/ in mysqld.1.err
Pre-crash binlog file content:
include/show_binlog_events.inc
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000005 # Gtid # # BEGIN GTID #-#-#
master-bin.000005 # Query # # use `test`; DELETE FROM t
master-bin.000005 # Xid # # COMMIT /* XID */
master-bin.000005 # Gtid # # BEGIN GTID #-#-#
master-bin.000005 # Query # # use `test`; INSERT INTO t VALUES (10)
master-bin.000005 # Xid # # COMMIT /* XID */
master-bin.000005 # Gtid # # BEGIN GTID #-#-#
master-bin.000005 # Query # # use `test`; INSERT INTO tm VALUES (10)
master-bin.000005 # Query # # COMMIT
master-bin.000005 # Gtid # # XA START X'786964',X'',1 GTID #-#-#
master-bin.000005 # Query # # use `test`; DELETE FROM t WHERE f = 10
master-bin.000005 # Query # # XA END X'786964',X'',1
master-bin.000005 # XA_prepare # # XA PREPARE X'786964',X'',1
master-bin.000005 # Gtid # # BEGIN GTID #-#-#
master-bin.000005 # Query # # use `test`; INSERT INTO t2 VALUES (20)
master-bin.000005 # Xid # # COMMIT /* XID */
SELECT @@global.gtid_binlog_pos as 'After the crash';
After the crash
0-1-24
"One row should be present in table 't'"
SELECT * FROM t;
f
10
"No row should be present in table 't4'"
SELECT * FROM t4;
f
# 'xid' exists in
XA RECOVER;
formatID gtrid_length bqual_length data
1 3 0 xid
# Case F.
connect master1,localhost,root,,;
connect master2,localhost,root,,;
connect master3,localhost,root,,;
connect master4,localhost,root,,;
connection default;
INSERT INTO t VALUES (10);
INSERT INTO tm VALUES (10);
connection master1;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA ROLLBACK 'xid';;
connection master2;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready";
INSERT INTO t2 VALUES (30);
connection master3;
SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
SELECT @@global.gtid_binlog_pos as 'Before the crash';
Before the crash
0-1-28
connection master4;
SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master4_ready WAIT_FOR signal_never_arrives";
INSERT INTO t4 VALUES (13);
connection master3;
SET DEBUG_SYNC= "now WAIT_FOR master4_ready";
SELECT @@global.gtid_binlog_pos as 'Before the crash and never logged trx';
Before the crash and never logged trx
0-1-28
connection default;
# Kill the server
disconnect master1;
disconnect master2;
disconnect master3;
disconnect master4;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-27/ in mysqld.1.err
Pre-crash binlog file content:
include/show_binlog_events.inc
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000006 # Gtid # # BEGIN GTID #-#-#
master-bin.000006 # Query # # use `test`; INSERT INTO t VALUES (10)
master-bin.000006 # Xid # # COMMIT /* XID */
master-bin.000006 # Gtid # # BEGIN GTID #-#-#
master-bin.000006 # Query # # use `test`; INSERT INTO tm VALUES (10)
master-bin.000006 # Query # # COMMIT
SELECT @@global.gtid_binlog_pos as 'After the crash';
After the crash
0-1-26
"One row should be present in table 't'"
SELECT * FROM t;
f
10
10
"No row should be present in table 't4'"
SELECT * FROM t4;
f
# 'xid' exists in
XA RECOVER;
formatID gtrid_length bqual_length data
1 3 0 xid
# Case G.
connect master1,localhost,root,,;
connect master2,localhost,root,,;
connect master3,localhost,root,,;
connect master4,localhost,root,,;
connection default;
INSERT INTO t VALUES (10);
INSERT INTO tm VALUES (10);
connection master1;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA COMMIT 'xid';;
connection master2;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready";
INSERT INTO t2 VALUES (30);
connection master3;
SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
SELECT @@global.gtid_binlog_pos as 'Before the crash';
Before the crash
0-1-30
connection master4;
SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master4_ready WAIT_FOR signal_never_arrives";
INSERT INTO t4 VALUES (13);
connection master3;
SET DEBUG_SYNC= "now WAIT_FOR master4_ready";
SELECT @@global.gtid_binlog_pos as 'Before the crash and never logged trx';
Before the crash and never logged trx
0-1-30
connection default;
# Kill the server
disconnect master1;
disconnect master2;
disconnect master3;
disconnect master4;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-29/ in mysqld.1.err
Pre-crash binlog file content:
include/show_binlog_events.inc
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000007 # Gtid # # BEGIN GTID #-#-#
master-bin.000007 # Query # # use `test`; INSERT INTO t VALUES (10)
master-bin.000007 # Xid # # COMMIT /* XID */
master-bin.000007 # Gtid # # BEGIN GTID #-#-#
master-bin.000007 # Query # # use `test`; INSERT INTO tm VALUES (10)
master-bin.000007 # Query # # COMMIT
SELECT @@global.gtid_binlog_pos as 'After the crash';
After the crash
0-1-28
"One row should be present in table 't'"
SELECT * FROM t;
f
10
10
10
"No row should be present in table 't4'"
SELECT * FROM t4;
f
# 'xid' exists in
XA RECOVER;
formatID gtrid_length bqual_length data
1 3 0 xid
XA COMMIT 'xid';
DROP PROCEDURE sp_xa; DROP PROCEDURE sp_xa;
# Cleanup # Cleanup
DROP TABLE t,t2,tm; DROP TABLE t,t2,tm;
......
call mtr.add_suppression("Can.t init tc log");
call mtr.add_suppression("Aborting");
call mtr.add_suppression("Found.*prepared [XA ]*transaction[s]*");
SET @@global.max_binlog_size= 4096;
SET @@global.sync_binlog= 1;
RESET MASTER;
FLUSH LOGS;
CREATE TABLE ti (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb;
CREATE PROCEDURE sp_xa()
BEGIN
XA START 'xid';
INSERT INTO ti VALUES (2, REPEAT("x", 4100));
XA END 'xid';
XA PREPARE 'xid';
END|
# case A. binlog input p1,g2,g3: p1 gets truncated along with g2,g3
connect master1,localhost,root,,;
"List of binary logs before rotation"
show binary logs;
Log_name File_size
master-bin.000001 #
master-bin.000002 #
INSERT INTO ti VALUES(1,"I am gonna survive");
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL master1_ready WAIT_FOR master1_go_never_arrives";
call sp_xa();
connect master2,localhost,root,,;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master2_ready WAIT_FOR master2_go_never_arrives";
INSERT INTO ti VALUES (3, "not gonna survive");
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
connect master3,localhost,root,,;
SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master3_ready WAIT_FOR master3_go_never_arrives";
INSERT INTO ti VALUES (4, "not gonna be logged therefore survive"),(5, "ditto");
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master3_ready";
"List of binary logs before crash"
show binary logs;
Log_name File_size
master-bin.000001 #
master-bin.000002 #
master-bin.000003 #
# The gtid binlog state prior the crash will be truncated at the end of the test
SELECT @@global.gtid_binlog_state;
@@global.gtid_binlog_state
0-1-5
connection default;
# Kill the server
disconnect master1;
disconnect master2;
disconnect master3;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-4/ in mysqld.1.err
FOUND 1 /truncated binlog file:.*master.*000002/ in mysqld.1.err
# One record should be present in table
SELECT * FROM ti;
a b
1 I am gonna survive
# The truncated gtid binlog state
SELECT @@global.gtid_binlog_state;
@@global.gtid_binlog_state
0-1-3
SELECT @@global.gtid_binlog_pos;
@@global.gtid_binlog_pos
0-1-3
DELETE FROM ti;
# case B. binlog input g1,p2,g3: p2 gets truncated along with g1,g3
connect master1,localhost,root,,;
"List of binary logs before rotation"
show binary logs;
Log_name File_size
master-bin.000001 #
master-bin.000002 #
master-bin.000003 #
INSERT INTO ti VALUES(1,"I am gonna survive");
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL master1_ready WAIT_FOR master1_go_never_arrives";
INSERT INTO ti VALUES (3, "not gonna survive");
connect master2,localhost,root,,;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master2_ready WAIT_FOR master2_go_never_arrives";
call sp_xa();
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
connect master3,localhost,root,,;
SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master3_ready WAIT_FOR master3_go_never_arrives";
INSERT INTO ti VALUES (4, "not gonna be logged therefore survive"),(5, "ditto");
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master3_ready";
"List of binary logs before crash"
show binary logs;
Log_name File_size
master-bin.000001 #
master-bin.000002 #
master-bin.000003 #
# The gtid binlog state prior the crash will be truncated at the end of the test
SELECT @@global.gtid_binlog_state;
@@global.gtid_binlog_state
0-1-7
connection default;
# Kill the server
disconnect master1;
disconnect master2;
disconnect master3;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-6/ in mysqld.1.err
FOUND 1 /truncated binlog file:.*master.*000003/ in mysqld.1.err
# One record should be present in table
SELECT * FROM ti;
a b
1 I am gonna survive
# The truncated gtid binlog state
SELECT @@global.gtid_binlog_state;
@@global.gtid_binlog_state
0-1-5
SELECT @@global.gtid_binlog_pos;
@@global.gtid_binlog_pos
0-1-5
DELETE FROM ti;
# case C. binlog input G1,P2,G3: P2 remains prepared.
connect master1,localhost,root,,;
INSERT INTO ti VALUES(1,"I am gonna survive");
connect master2,localhost,root,,;
call sp_xa();
connect master3,localhost,root,,;
INSERT INTO ti VALUES(3,"Me too");
connection default;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
disconnect master1;
disconnect master2;
disconnect master3;
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
# xid must be in
XA RECOVER;
formatID gtrid_length bqual_length data
1 3 0 xid
# case D. prove Binlog-checkpoint based recovery for XA-"complete"
XA COMMIT 'xid';
FLUSH LOGS;
# Kill the server
# restart: --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
# no xid must be in
XA RECOVER;
formatID gtrid_length bqual_length data
SELECT count(*) = 3 FROM ti;
count(*) = 3
1
SELECT @@global.gtid_binlog_state;
@@global.gtid_binlog_state
0-1-10
SELECT @@global.gtid_binlog_pos;
@@global.gtid_binlog_pos
0-1-10
# Cleanup
DROP PROCEDURE sp_xa;
DROP TABLE ti;
SET @@global.sync_binlog= default;
# End of the tests
...@@ -2,61 +2,110 @@ call mtr.add_suppression("Found 1 prepared XA transactions"); ...@@ -2,61 +2,110 @@ call mtr.add_suppression("Found 1 prepared XA transactions");
call mtr.add_suppression("unknown option"); call mtr.add_suppression("unknown option");
CREATE TABLE t31949 (a INT PRIMARY KEY) ENGINE=Innodb; CREATE TABLE t31949 (a INT PRIMARY KEY) ENGINE=Innodb;
CREATE VIEW v_processlist as SELECT * FROM performance_schema.threads where type = 'FOREGROUND'; CREATE VIEW v_processlist as SELECT * FROM performance_schema.threads where type = 'FOREGROUND';
XA START '1'; # case 1. only in engine prepared XA is rolled back
connect master1,localhost,root,,;
XA START 'A';
INSERT INTO t31949 SET a=1; INSERT INTO t31949 SET a=1;
XA END '1'; XA END 'A';
XA PREPARE '1'; SET DEBUG_SYNC= "commit_before_get_LOCK_log SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA PREPARE 'A';
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
# Kill and restart
disconnect master1;
# Proof 1:
XA RECOVER;
formatID gtrid_length bqual_length data
SELECT count(*) = 0 from t31949;
count(*) = 0
1
# case 2. both engine prepared and binlog written XA remains
connect master1,localhost,root,,;
XA START 'A';
INSERT INTO t31949 SET a=1;
XA END 'A';
XA PREPARE 'A';
connection default;
# Kill and restart # Kill and restart
# xid '1' most be recovered disconnect master1;
# Proof 2:
# xid 'A' must be recovered
XA RECOVER; XA RECOVER;
formatID gtrid_length bqual_length data formatID gtrid_length bqual_length data
1 1 0 1 1 1 0 A
# COMMIT from an external connection SELECT count(*) = 0 from t31949;
XA COMMIT '1'; count(*) = 0
1
# case 3. XA-COMMIT from an external connection after binlogging recovers to COMPLETE
connect master1,localhost,root,,;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA COMMIT 'A';
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
# Kill and restart # Kill and restart
# xid '1' must be committed disconnect master1;
# Proof 3:
# xid 'A' must be recovered to commit
XA RECOVER; XA RECOVER;
formatID gtrid_length bqual_length data formatID gtrid_length bqual_length data
select count(*) = 1 from t31949; select count(*) = 1 from t31949;
count(*) = 1 count(*) = 1
1 1
XA START '2'; # case 4. XA-ROLLBACK from the native connection after binlogging recovers to COMPLETE
connect master2,localhost,root,,;
XA START 'B';
INSERT INTO t31949 SET a=2; INSERT INTO t31949 SET a=2;
XA END '2'; XA END 'B';
XA PREPARE '2'; XA PREPARE 'B';
# ROLLBACK from the native connection SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA ROLLBACK '2'; XA ROLLBACK 'B';
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
# Kill and restart # Kill and restart
# xid '1' must be rolled back disconnect master2;
# Proof 4:
# xid 'B' must be recovered to roll back
XA RECOVER; XA RECOVER;
formatID gtrid_length bqual_length data formatID gtrid_length bqual_length data
select count(*) = 1 from t31949; select count(*) = 1 from t31949;
count(*) = 1 count(*) = 1
1 1
XA START '3'; # case 5. ditto to XA-COMMIT
connect master3,localhost,root,,;
XA START 'C';
INSERT INTO t31949 SET a=3; INSERT INTO t31949 SET a=3;
XA END '3'; XA END 'C';
XA PREPARE '3'; XA PREPARE 'C';
# COMMIT from the native connection SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA COMMIT '3'; XA COMMIT 'C';
connection default;
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
# Kill and restart # Kill and restart
# xid '3' must be committed disconnect master3;
# Proof 5:
# xid 'C' must be recovered to commit
XA RECOVER; XA RECOVER;
formatID gtrid_length bqual_length data formatID gtrid_length bqual_length data
select count(*) = 2 from t31949; select count(*) = 2 from t31949;
count(*) = 2 count(*) = 2
1 1
connect con4,127.0.0.1,root,,test,$MASTER_MYPORT,; # case 6. XA-ROLLBACK from an external connection after binlogging recovers to COMPLETE
XA START '4'; connect conn$case,localhost,root,,;
XA START 'D';
INSERT INTO t31949 SET a=4; INSERT INTO t31949 SET a=4;
XA END '4'; XA END 'D';
XA PREPARE '4'; XA PREPARE 'D';
disconnect con4; disconnect conn6;
connection default;
connect master4,localhost,root,,;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA ROLLBACK 'D';
connection default; connection default;
# ROLLBACK from an external connection SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
XA ROLLBACK '4';
# Kill and restart # Kill and restart
# xid '4' must be rolled back disconnect master4;
# Proof 6:
# xid 'D' must be recovered to roll back
XA RECOVER; XA RECOVER;
formatID gtrid_length bqual_length data formatID gtrid_length bqual_length data
select count(*) = 2 from t31949; select count(*) = 2 from t31949;
......
#
# Meanings of parameters:
#
# $query1 a query that creates a group of events that goes into binlog
# $query2 a query that follows $query1 in binlog without having done
# engine commit
# $query20 an optinal $query2 that durably commits in engine
# $delete when 1 deletes from t
# $no_truncate when 1 NOT FOUND off search_pattern_in_file.inc is "good".
#
connect(master1,localhost,root,,); connect(master1,localhost,root,,);
connect(master2,localhost,root,,); connect(master2,localhost,root,,);
connect(master3,localhost,root,,); connect(master3,localhost,root,,);
...@@ -10,14 +20,29 @@ INSERT INTO t VALUES (10); ...@@ -10,14 +20,29 @@ INSERT INTO t VALUES (10);
INSERT INTO tm VALUES (10); INSERT INTO tm VALUES (10);
--connection master1 --connection master1
# Hold insert after write to binlog and before "run_commit_ordered" in engine if (!$query20)
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives"; {
--send_eval $query1 # Hold insert after write to binlog and before "run_commit_ordered" in engine
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
--send_eval $query1
}
if ($query20)
{
# do not hold the $query2 insert to let it binlog past $query1 "us"
--eval $query1
}
--connection master2 --connection master2
SET DEBUG_SYNC= "now WAIT_FOR master1_ready"; if (!$query20)
SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready"; {
--send_eval $query2 SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready"; --send_eval $query2
}
if ($query20)
{
SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready";
--eval $query20
}
--connection master3 --connection master3
SET DEBUG_SYNC= "now WAIT_FOR master2_ready"; SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
...@@ -46,6 +71,10 @@ SELECT @@global.gtid_binlog_pos as 'Before the crash and never logged trx'; ...@@ -46,6 +71,10 @@ SELECT @@global.gtid_binlog_pos as 'Before the crash and never logged trx';
--source include/start_mysqld.inc --source include/start_mysqld.inc
# Check error log for a successful truncate message. # Check error log for a successful truncate message.
if ($no_truncate)
{
--echo # *** Expect NOT FOUND /Successfully truncated ... which is correct. ***
}
--let $log_error_ = $MYSQLTEST_VARDIR/log/mysqld.1.err --let $log_error_ = $MYSQLTEST_VARDIR/log/mysqld.1.err
--let SEARCH_FILE=$log_error_ --let SEARCH_FILE=$log_error_
...@@ -67,4 +96,7 @@ SELECT * FROM t4; ...@@ -67,4 +96,7 @@ SELECT * FROM t4;
--inc $binlog_file_index --inc $binlog_file_index
# Local cleanup # Local cleanup
DELETE FROM t; if ($delete)
{
DELETE FROM t;
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
# ==== References ==== # ==== References ====
# #
# MDEV-21117: recovery for --rpl-semi-sync-slave-enabled server # MDEV-21117: recovery for --rpl-semi-sync-slave-enabled server
# MDEV-33168 XA crash-recovery base on engines prepare first rule
--source include/have_innodb.inc --source include/have_innodb.inc
--source include/have_aria.inc --source include/have_aria.inc
...@@ -15,11 +16,14 @@ ...@@ -15,11 +16,14 @@
call mtr.add_suppression("Can.t init tc log"); call mtr.add_suppression("Can.t init tc log");
call mtr.add_suppression("Aborting"); call mtr.add_suppression("Aborting");
call mtr.add_suppression("Found.*prepared [XA ]*transaction[s]*");
# The following cases are tested: # The following cases are tested:
# A. 2pc transaction is followed by a blank "zero-engines" one # A. 2pc transaction is followed by a blank "zero-engines" one
# B. 2pc transaction follows the blank one # B. 2pc transaction follows the blank one
# C. Similarly to A, with the XA blank transaction # C. Similarly to A, with the XA blank transaction
#
# D-G verify MDEV-33168.
RESET MASTER; RESET MASTER;
SET @@global.sync_binlog=1; SET @@global.sync_binlog=1;
...@@ -31,6 +35,8 @@ CREATE TABLE tm (f INT) ENGINE=Aria; ...@@ -31,6 +35,8 @@ CREATE TABLE tm (f INT) ENGINE=Aria;
# Old (pre-crash) binlog file index initial value. # Old (pre-crash) binlog file index initial value.
# It keeps incremented at the end of each case. # It keeps incremented at the end of each case.
--let $binlog_file_index=1 --let $binlog_file_index=1
# parameter to binlog_truncate_active_log.inc
--let $delete=1
--echo # Case A. --echo # Case A.
# Using 'debug_sync' hold 'query1' execution after 'query1' is flushed and # Using 'debug_sync' hold 'query1' execution after 'query1' is flushed and
...@@ -88,15 +94,71 @@ BEGIN ...@@ -88,15 +94,71 @@ BEGIN
END| END|
delimiter ;| delimiter ;|
# The same as in B with $query1 being the prepared XA transaction. # $query1 being the prepared XA transaction.
# Truncation must occurs at $query2. # Truncation must occurs at $query1 because $query2 is not going to commit
--let $truncate_gtid_pos = 0-1-21 # therefore at recovery it will be in-doubt and thus be rolled back by
# --rpl-semi-sync-slave-enabled=1. As $query2 could not be proved
# as acknowlegded by slave, $query1 has to be pessimistically regarded the same.
--let $truncate_gtid_pos = 0-1-20
--let $query1 = CALL sp_xa --let $query1 = CALL sp_xa
--let $query2 = INSERT INTO t2 VALUES (20) --let $query2 = INSERT INTO t2 VALUES (20)
--source binlog_truncate_active_log.inc --source binlog_truncate_active_log.inc
# Proof:
--echo # no 'xid' in
XA RECOVER;
SELECT count(*) > 0 as "because of rolled back" FROM t WHERE f = 10;
DROP PROCEDURE sp_xa; --echo # Case E.
# The same as D, but XAP of $query1 survives the crash-recovery thanks to
# $query20 is going to durably commit prior to the crash.
--let $truncate_gtid_pos = 0-1-2[1-4]
--let $query1 = CALL sp_xa
let $query2 =;
--let $query20 = INSERT INTO t2 VALUES (20)
--let $delete=0
--let $no_truncate=1
--source binlog_truncate_active_log.inc
# Proof:
--echo # 'xid' exists in
XA RECOVER;
# restore to the default
let $query20 =;
--let $no_truncate=0
--echo # Case F.
# Recovery of XA-ROLLBACK. The conditions are like in D. The outcome
# is truncation of both $query1,2.
# Therefore `xid` must remain in prepared state.
--let $truncate_gtid_pos = 0-1-27
--let $query1 = XA ROLLBACK 'xid';
--let $query2 = INSERT INTO t2 VALUES (30)
--let $delete=0
--source binlog_truncate_active_log.inc
# Proof:
--echo # 'xid' exists in
XA RECOVER;
--echo # Case G.
# Recovery of XA-COMMIT. The conditions are like in F. The outcome
# is truncation of both $query1,2.
# Therefore `xid` must remain in prepared state.
--let $truncate_gtid_pos = 0-1-29
--let $query1 = XA COMMIT 'xid';
--let $query2 = INSERT INTO t2 VALUES (30)
--let $delete=0
--source binlog_truncate_active_log.inc
# Proof:
--echo # 'xid' exists in
XA RECOVER;
XA COMMIT 'xid';
DROP PROCEDURE sp_xa;
--echo # Cleanup --echo # Cleanup
DROP TABLE t,t2,tm; DROP TABLE t,t2,tm;
......
# $query1 a query that creates a group of events that goes into binlog
# $query2 a query that follows $query1 in binlog without having done
# engine commit
# $truncate_index binlog file index that undergoes truncation
# $truncate_gtid the first gtid in the cut off tail part of
# the binlog index' file
connect(master1,localhost,root,,);
--echo "List of binary logs before rotation"
--source include/show_binary_logs.inc
# Some load to either non- and transactional egines
# that should not affect the following recovery:
INSERT INTO ti VALUES(1,"I am gonna survive");
# XAP_1 will be holding a mutex ..
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL master1_ready WAIT_FOR master1_go_never_arrives";
--send_eval $query1
connect(master2,localhost,root,,);
# .. to not let the 2nd trx to commit.
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master2_ready WAIT_FOR master2_go_never_arrives";
--send_eval $query2
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master2_ready";
connect(master3,localhost,root,,);
# The 3nd trx will be considered at recovery, but as it won't get into binlog
# it won't recover.
SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master3_ready WAIT_FOR master3_go_never_arrives";
--send INSERT INTO ti VALUES (4, "not gonna be logged therefore survive"),(5, "ditto")
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master3_ready";
--echo "List of binary logs before crash"
--source include/show_binary_logs.inc
--echo # The gtid binlog state prior the crash will be truncated at the end of the test
SELECT @@global.gtid_binlog_state;
--connection default
--source include/kill_mysqld.inc
--disconnect master1
--disconnect master2
--disconnect master3
#
# Server restart
#
--let $restart_parameters= --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
--source include/start_mysqld.inc
# Check error log for a successful truncate message.
let $log_error_ = $MYSQLTEST_VARDIR/log/mysqld.1.err;
--let SEARCH_FILE=$log_error_
--let SEARCH_PATTERN=Successfully truncated.*to remove transactions starting from GTID $truncate_gtid
--source include/search_pattern_in_file.inc
--let SEARCH_PATTERN=truncated binlog file:.*master.*00000$truncate_index
--source include/search_pattern_in_file.inc
--echo # One record should be present in table
SELECT * FROM ti;
--echo # The truncated gtid binlog state
SELECT @@global.gtid_binlog_state;
SELECT @@global.gtid_binlog_pos;
DELETE FROM ti;
# ==== Purpose ====
#
# Test verifies truncation of multiple binary logs.
#
# ==== References ====
# MDEV-21117: recovery for --rpl-semi-sync-slave-enabled server
# MDEV-33168 XA crash-recovery base on engines prepare first rule
--source include/have_innodb.inc
--source include/have_debug_sync.inc
--source include/have_binlog_format_row.inc
call mtr.add_suppression("Can.t init tc log");
call mtr.add_suppression("Aborting");
call mtr.add_suppression("Found.*prepared [XA ]*transaction[s]*");
SET @@global.max_binlog_size= 4096;
SET @@global.sync_binlog= 1;
RESET MASTER;
FLUSH LOGS;
CREATE TABLE ti (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb;
delimiter |;
CREATE PROCEDURE sp_xa()
BEGIN
XA START 'xid';
INSERT INTO ti VALUES (2, REPEAT("x", 4100));
XA END 'xid';
XA PREPARE 'xid';
END|
delimiter ;|
# Notations from MDEV-21117:
# the small lettercase `g` stands for non-committed in doubt transaction
# Similarly `p` stands for in-doubt user prepared XA.
# The uppercased G:s,P that stand for the fact of resolved status (to complete
# the transaction (both kinds) or keep xa as prepared).
--echo # case A. binlog input p1,g2,g3: p1 gets truncated along with g2,g3
--let $truncate_index=2
--let $truncate_gtid=0-1-4
--let $query1=call sp_xa()
--let $query2=INSERT INTO ti VALUES (3, "not gonna survive")
--source binlog_truncate_multi_log_xa.inc
--echo # case B. binlog input g1,p2,g3: p2 gets truncated along with g1,g3
--let $truncate_index=3
--let $truncate_gtid=0-1-6
--let $query1=INSERT INTO ti VALUES (3, "not gonna survive")
--let $query2=call sp_xa()
--source binlog_truncate_multi_log_xa.inc
--echo # case C. binlog input G1,P2,G3: P2 remains prepared.
connect(master1,localhost,root,,);
INSERT INTO ti VALUES(1,"I am gonna survive");
connect(master2,localhost,root,,);
call sp_xa();
connect(master3,localhost,root,,);
INSERT INTO ti VALUES(3,"Me too");
--connection default
--source include/restart_mysqld.inc
--disconnect master1
--disconnect master2
--disconnect master3
#
# Server restart
#
--let $restart_parameters= --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
--source include/start_mysqld.inc
--echo # xid must be in
XA RECOVER;
--echo # case D. prove Binlog-checkpoint based recovery for XA-"complete"
# crash after XA-COMMIT followed by Rotate and Binlog-Checkpoint
# must be sufficient for the XA transaction be durably committed.
# It can't be found in prepared state at recovery.
XA COMMIT 'xid';
FLUSH LOGS;
--source include/wait_for_binlog_checkpoint.inc
--source include/kill_mysqld.inc
--let $restart_parameters= --rpl-semi-sync-slave-enabled=1 --sync-binlog=1 --log-warnings=3
--source include/start_mysqld.inc
--echo # no xid must be in
XA RECOVER;
SELECT count(*) = 3 FROM ti;
SELECT @@global.gtid_binlog_state;
SELECT @@global.gtid_binlog_pos;
--echo # Cleanup
DROP PROCEDURE sp_xa;
DROP TABLE ti;
SET @@global.sync_binlog= default;
--echo # End of the tests
--source include/have_innodb.inc --source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_binlog_format_row.inc --source include/have_binlog_format_row.inc
# MDEV-31949 slow xa on parallel slave # MDEV-31949 slow xa on parallel slave
...@@ -11,68 +12,130 @@ call mtr.add_suppression("unknown option"); ...@@ -11,68 +12,130 @@ call mtr.add_suppression("unknown option");
CREATE TABLE t31949 (a INT PRIMARY KEY) ENGINE=Innodb; CREATE TABLE t31949 (a INT PRIMARY KEY) ENGINE=Innodb;
CREATE VIEW v_processlist as SELECT * FROM performance_schema.threads where type = 'FOREGROUND'; CREATE VIEW v_processlist as SELECT * FROM performance_schema.threads where type = 'FOREGROUND';
XA START '1'; --let $case=1
--echo # case $case. only in engine prepared XA is rolled back
--connect(master1,localhost,root,,)
XA START 'A';
INSERT INTO t31949 SET a=1; INSERT INTO t31949 SET a=1;
XA END '1'; XA END 'A';
XA PREPARE '1'; SET DEBUG_SYNC= "commit_before_get_LOCK_log SIGNAL master1_ready WAIT_FOR signal_never_arrives";
--send XA PREPARE 'A'
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
--source include/kill_and_restart_mysqld.inc --source include/kill_and_restart_mysqld.inc
--disconnect master1
--echo # xid '1' most be recovered --echo # Proof $case:
XA RECOVER; XA RECOVER;
--echo # COMMIT from an external connection SELECT count(*) = 0 from t31949;
XA COMMIT '1';
--inc $case
--echo # case $case. both engine prepared and binlog written XA remains
--connect(master1,localhost,root,,)
XA START 'A';
INSERT INTO t31949 SET a=1;
XA END 'A';
XA PREPARE 'A';
--connection default
--source include/kill_and_restart_mysqld.inc --source include/kill_and_restart_mysqld.inc
--disconnect master1
--echo # xid '1' must be committed --echo # Proof $case:
--echo # xid 'A' must be recovered
XA RECOVER; XA RECOVER;
select count(*) = 1 from t31949; SELECT count(*) = 0 from t31949;
--inc $case
--echo # case $case. XA-COMMIT from an external connection after binlogging recovers to COMPLETE
--connect(master1,localhost,root,,)
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
--send XA COMMIT 'A'
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
--source include/kill_and_restart_mysqld.inc
--disconnect master1
XA START '2'; --echo # Proof $case:
--echo # xid 'A' must be recovered to commit
XA RECOVER;
select count(*) = 1 from t31949;
--inc $case
--echo # case $case. XA-ROLLBACK from the native connection after binlogging recovers to COMPLETE
--connect(master2,localhost,root,,)
XA START 'B';
INSERT INTO t31949 SET a=2; INSERT INTO t31949 SET a=2;
XA END '2'; XA END 'B';
XA PREPARE '2'; XA PREPARE 'B';
--echo # ROLLBACK from the native connection SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA ROLLBACK '2'; --send XA ROLLBACK 'B'
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
--source include/kill_and_restart_mysqld.inc --source include/kill_and_restart_mysqld.inc
--echo # xid '1' must be rolled back --disconnect master2
--echo # Proof $case:
--echo # xid 'B' must be recovered to roll back
XA RECOVER; XA RECOVER;
select count(*) = 1 from t31949; select count(*) = 1 from t31949;
XA START '3'; --inc $case
--echo # case $case. ditto to XA-COMMIT
--connect(master3,localhost,root,,)
XA START 'C';
INSERT INTO t31949 SET a=3; INSERT INTO t31949 SET a=3;
XA END '3'; XA END 'C';
XA PREPARE '3'; XA PREPARE 'C';
--echo # COMMIT from the native connection SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
XA COMMIT '3'; --send XA COMMIT 'C'
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
--source include/kill_and_restart_mysqld.inc --source include/kill_and_restart_mysqld.inc
--disconnect master3
--echo # xid '3' must be committed --echo # Proof $case:
--echo # xid 'C' must be recovered to commit
XA RECOVER; XA RECOVER;
select count(*) = 2 from t31949; select count(*) = 2 from t31949;
--connect(con4,127.0.0.1,root,,test,$MASTER_MYPORT,)
--let $conn1_id=`SELECT connection_id()` --inc $case
XA START '4'; --echo # case $case. XA-ROLLBACK from an external connection after binlogging recovers to COMPLETE
--connect(conn$case,localhost,root,,)
--let $conn_id=`SELECT connection_id()`
XA START 'D';
INSERT INTO t31949 SET a=4; INSERT INTO t31949 SET a=4;
XA END '4'; XA END 'D';
XA PREPARE '4'; XA PREPARE 'D';
--disconnect conn$case
--disconnect con4
--connection default --connection default
--let $wait_condition= SELECT count(*) = 0 FROM v_processlist WHERE PROCESSLIST_ID = $conn1_id --let $wait_condition= SELECT count(*) = 0 FROM v_processlist WHERE PROCESSLIST_ID = $conn_id
--source include/wait_condition.inc --source include/wait_condition.inc
--echo # ROLLBACK from an external connection --connect(master4,localhost,root,,)
XA ROLLBACK '4'; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives";
--send XA ROLLBACK 'D'
--connection default
SET DEBUG_SYNC= "now WAIT_FOR master1_ready";
--source include/kill_and_restart_mysqld.inc --source include/kill_and_restart_mysqld.inc
--disconnect master4
--echo # xid '4' must be rolled back --echo # Proof $case:
--echo # xid 'D' must be recovered to roll back
XA RECOVER; XA RECOVER;
select count(*) = 2 from t31949; select count(*) = 2 from t31949;
# cleanup # cleanup
DROP TABLE t31949; DROP TABLE t31949;
DROP VIEW v_processlist; DROP VIEW v_processlist;
......
...@@ -35,11 +35,16 @@ connection server_1; ...@@ -35,11 +35,16 @@ connection server_1;
# 0-1-4 (Not committed) | 0-1-4 (Received through semi-sync | # 0-1-4 (Not committed) | 0-1-4 (Received through semi-sync |
# | replication and applied) | # | replication and applied) |
#================================================================= #=================================================================
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,; connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (2, REPEAT("x", 4100)); INSERT INTO t1 VALUES (2, REPEAT("x", 4100));
connection server_1; connection server_1;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
select @@global.gtid_binlog_pos as "server_1 state";
server_1 state
0-1-4
# Kill the server # Kill the server
connection server_2; connection server_2;
include/wait_for_slave_param.inc [Slave_SQL_Running_State] include/wait_for_slave_param.inc [Slave_SQL_Running_State]
...@@ -48,11 +53,13 @@ include/assert.inc [Table t1 should have 2 rows.] ...@@ -48,11 +53,13 @@ include/assert.inc [Table t1 should have 2 rows.]
SELECT @@GLOBAL.gtid_current_pos; SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos @@GLOBAL.gtid_current_pos
0-1-4 0-1-4
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 # restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_1; connection server_1;
include/assert.inc [Table t1 should have 1 rows.] include/assert.inc [Table t1 should have 1 rows.]
FOUND 1 /truncated binlog file:.*master.*000001/ in mysqld.1.err FOUND 1 /truncated binlog file:.*master.*000001/ in mysqld.1.err
disconnect conn_client; disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_2; connection server_2;
set global rpl_semi_sync_master_enabled = 1; set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC; set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
...@@ -60,6 +67,9 @@ connection server_1; ...@@ -60,6 +67,9 @@ connection server_1;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS; CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1; set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos; set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-1-3
include/start_slave.inc include/start_slave.inc
# #
# Server_2 promoted as master will send 0-1-4 to new slave Server_1 # Server_2 promoted as master will send 0-1-4 to new slave Server_1
...@@ -109,10 +119,12 @@ connection server_2; ...@@ -109,10 +119,12 @@ connection server_2;
# 0-2-7 (Not commited) | 0-2-7 (Received through semi-sync | # 0-2-7 (Not commited) | 0-2-7 (Received through semi-sync |
# | replication and applied) | # | replication and applied) |
#================================================================= #=================================================================
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,; connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
SET STATEMENT server_id=1 FOR INSERT INTO t1 VALUES (4, REPEAT("x", 4100)); SET STATEMENT server_id=1 FOR INSERT INTO t1 VALUES (4, REPEAT("x", 4100));
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,; connection conn_client_2;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
SET GLOBAL debug_dbug="d,Notify_binlog_EOF"; SET GLOBAL debug_dbug="d,Notify_binlog_EOF";
INSERT INTO t1 VALUES (5, REPEAT("x", 4100)); INSERT INTO t1 VALUES (5, REPEAT("x", 4100));
...@@ -126,11 +138,13 @@ include/assert.inc [Table t1 should have 5 rows.] ...@@ -126,11 +138,13 @@ include/assert.inc [Table t1 should have 5 rows.]
SELECT @@GLOBAL.gtid_current_pos; SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos @@GLOBAL.gtid_current_pos
0-2-7 0-2-7
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 # restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_2; connection server_2;
include/assert.inc [Table t1 should have 3 rows.] include/assert.inc [Table t1 should have 3 rows.]
FOUND 1 /truncated binlog file:.*slave.*000002.* to remove transactions starting from GTID 0-1-6/ in mysqld.2.err FOUND 1 /truncated binlog file:.*slave.*000002.* to remove transactions starting from GTID 0-1-6/ in mysqld.2.err
disconnect conn_client; disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_1; connection server_1;
set global rpl_semi_sync_master_enabled = 1; set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC; set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
...@@ -138,6 +152,9 @@ connection server_2; ...@@ -138,6 +152,9 @@ connection server_2;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS; CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1; set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos; set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-2-5
include/start_slave.inc include/start_slave.inc
# #
# Server_1 promoted as master will send 0-1-6 and 0-2-7 to slave Server_2 # Server_1 promoted as master will send 0-1-6 and 0-2-7 to slave Server_2
...@@ -188,15 +205,22 @@ connection server_1; ...@@ -188,15 +205,22 @@ connection server_1;
# 0-1-10 (Not commited - | | # 0-1-10 (Not commited - | |
# never sent to slave) | | # never sent to slave) | |
#================================================================= #=================================================================
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,; connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (7, REPEAT("x", 4100)); INSERT INTO t1 VALUES (7, REPEAT("x", 4100));
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,; connection conn_client_3;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
include/save_master_gtid.inc
connection server_2;
include/sync_with_master_gtid.inc
connection conn_client_3;
SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (8, REPEAT("x", 4100)); INSERT INTO t1 VALUES (8, REPEAT("x", 4100));
connection server_1; connection server_1;
SET DEBUG_SYNC= "now WAIT_FOR con3_ready"; SET DEBUG_SYNC= "now WAIT_FOR con3_ready";
connection server_1;
# Kill the server # Kill the server
connection server_2; connection server_2;
include/wait_for_slave_param.inc [Slave_SQL_Running_State] include/wait_for_slave_param.inc [Slave_SQL_Running_State]
...@@ -205,11 +229,13 @@ include/assert.inc [Table t1 should have 7 rows.] ...@@ -205,11 +229,13 @@ include/assert.inc [Table t1 should have 7 rows.]
SELECT @@GLOBAL.gtid_current_pos; SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos @@GLOBAL.gtid_current_pos
0-1-9 0-1-9
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 # restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_1; connection server_1;
include/assert.inc [Table t1 should have 6 rows.] include/assert.inc [Table t1 should have 6 rows.]
FOUND 1 /truncated binlog file:.*master.*000002.* to remove transactions starting from GTID 0-1-9/ in mysqld.1.err FOUND 1 /truncated binlog file:.*master.* to remove transactions starting from GTID 0-1-9/ in mysqld.1.err
disconnect conn_client; disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_2; connection server_2;
set global rpl_semi_sync_master_enabled = 1; set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC; set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
...@@ -217,6 +243,9 @@ connection server_1; ...@@ -217,6 +243,9 @@ connection server_1;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS; CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1; set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos; set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-1-8
include/start_slave.inc include/start_slave.inc
# #
# Server_2 promoted as master will send 0-1-9 to slave Server_1 # Server_2 promoted as master will send 0-1-9 to slave Server_1
...@@ -249,10 +278,11 @@ gtid_binlog_pos 0-2-10 ...@@ -249,10 +278,11 @@ gtid_binlog_pos 0-2-10
SHOW VARIABLES LIKE 'gtid_binlog_state'; SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value Variable_name Value
gtid_binlog_state 0-1-9,0-2-10 gtid_binlog_state 0-1-9,0-2-10
include/stop_slave.inc
# #
# Cleanup # Cleanup
# #
include/stop_slave.inc connection server_1;
set global rpl_semi_sync_slave_enabled = 0; set global rpl_semi_sync_slave_enabled = 0;
set global rpl_semi_sync_master_enabled = 0; set global rpl_semi_sync_master_enabled = 0;
set global rpl_semi_sync_master_wait_point=default; set global rpl_semi_sync_master_wait_point=default;
......
include/master-slave.inc
[connection master]
connection server_2;
include/stop_slave.inc
connection server_1;
RESET MASTER;
SET @@global.max_binlog_size= 4096;
connection server_2;
RESET MASTER;
SET @@global.max_binlog_size= 4096;
set @@global.rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos = "";
CHANGE MASTER TO master_use_gtid= slave_pos;
include/start_slave.inc
connection server_1;
ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
set @@global.rpl_semi_sync_master_enabled = 1;
set @@global.rpl_semi_sync_master_wait_point=AFTER_SYNC;
CREATE TABLE t1 (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb;
INSERT INTO t1 VALUES (1, 'dummy1');
connection server_1;
CREATE PROCEDURE sp_xa(val int)
BEGIN
XA START 'xid';
INSERT INTO t1 VALUES (val, REPEAT("x", 4100));
XA END 'xid';
XA PREPARE 'xid';
END|
connection server_2;
#
# Case:1.A
#
# CRASH server_1/master server_1 after XA PREPARE semisynced
# FAILOVER to server_2/slave
connection server_1;
# CALL sp_xa(2)
# Expected State post crash:
select @@global.gtid_binlog_pos as "initial server_1 state";
initial server_1 state
0-1-4
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go";
CALL sp_xa(2);
connection server_1;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
select @@global.gtid_binlog_pos as "server_1 state";
server_1 state
0-1-5
# Kill the server
connection server_2;
include/wait_for_slave_param.inc [Slave_SQL_Running_State]
include/stop_slave.inc
include/assert.inc [Table t1 should have 1 rows.]
SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos
0-1-5
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_1;
include/assert.inc [Table t1 should have 1 rows.]
FOUND 1 /truncated binlog file.*to remove transactions starting from GTID 0-1-5/ in mysqld.1.err
disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_2;
set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
connection server_1;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-1-4
include/start_slave.inc
#
# server_2 promoted to the primary/master will serve new replica/slave server_1 starting from 0-1-5
#
connection server_2;
XA COMMIT 'xid';
# The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-2-6
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-1-5,0-2-6
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-1-5
connection server_1;
SELECT COUNT(*) = 2 as 'true' FROM t1;
true
1
# ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-2-6
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-2-6
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-1-5,0-2-6
include/diff_tables.inc [server_1:t1, server_2:t1]
#
# Case:1.B
#
# CRASH the primary/master after a sequence of
# XA PREPARE, XA COMMIT (XAC becomes durable in Engine), Trx is binlogged
# but remains only prepared by crash time.
# FAILOVER to server_1.
connection server_2;
CALL sp_xa(3);
connection server_1;
connection server_2;
XA COMMIT 'xid';
FLUSH LOGS;
# INSERT INTO t1 VALUES (4, REPEAT("x", 4100))
# Expected State post crash:
select @@global.gtid_binlog_pos as "initial server_2 state";
initial server_2 state
0-2-8
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (4, REPEAT("x", 4100));
connection server_2;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
select @@global.gtid_binlog_pos as "server_2 state";
server_2 state
0-2-9
# Kill the server
connection server_1;
include/wait_for_slave_param.inc [Slave_SQL_Running_State]
include/stop_slave.inc
include/assert.inc [Table t1 should have 4 rows.]
SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos
0-2-9
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_2;
include/assert.inc [Table t1 should have 3 rows.]
FOUND 1 /truncated binlog file.*to remove transactions starting from GTID 0-2-9/ in mysqld.2.err
disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_1;
set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
connection server_2;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-2-8
include/start_slave.inc
#
# server_1 promoted to the primary/master will serve new replica/slave server_2 starting from 0-2-9
#
connection server_1;
DELETE FROM t1 WHERE a = 4;
# The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-1-10
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-2-9,0-1-10
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-2-9
connection server_2;
SELECT COUNT(*) = 3 as 'true' FROM t1;
true
1
# ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-1-10
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-1-10
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-2-9,0-1-10
include/diff_tables.inc [server_1:t1, server_2:t1]
#
# Case:1.C
#
# CRASH the primary/master after a sequence of
# XA PREPARE, XA ROLLBACK. XAR won't be durable in Engine and will be
# discarded from truncated binlog.
# XAP remains prepared after crash-recovery on the former
# demoted-to-slave master.
# FAILOVER to server_2.
connection server_1;
call mtr.add_suppression("Found 1 prepared XA transactions");
# XA ROLLBACK 'xid'
# Expected State post crash:
select @@global.gtid_binlog_pos as "initial server_1 state";
initial server_1 state
0-1-11
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
CALL sp_xa(4);
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go";
XA ROLLBACK 'xid';
connection server_1;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
select @@global.gtid_binlog_pos as "server_1 state";
server_1 state
0-1-13
# Kill the server
connection server_2;
include/wait_for_slave_param.inc [Slave_SQL_Running_State]
include/stop_slave.inc
include/assert.inc [Table t1 should have 3 rows.]
SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos
0-1-13
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_1;
include/assert.inc [Table t1 should have 3 rows.]
FOUND 1 /truncated binlog file.*to remove transactions starting from GTID 0-1-13/ in mysqld.1.err
disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_2;
set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
connection server_1;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-1-12
include/start_slave.inc
#
# server_2 promoted to the primary/master will serve new replica/slave server_1 starting from 0-1-13
#
connection server_2;
INSERT INTO t1 VALUES (4, REPEAT("x", 4100));;
# The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-2-14
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-1-13,0-2-14
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-1-13
connection server_1;
SELECT COUNT(*) = 4 as 'true' FROM t1;
true
1
# ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-2-14
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-2-14
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-1-13,0-2-14
include/diff_tables.inc [server_1:t1, server_2:t1]
#
# Case:3.A
#
# CRASH the primary/master after a sequence of
# XA PREPARE, Trx, Rotate.
# Both XAP and Trx are discarded from truncated binlog.
# FAILOVER to server_1.
connection server_2;
# CALL sp_xa(5)
# Expected State post crash:
select @@global.gtid_binlog_pos as "initial server_2 state";
initial server_2 state
0-2-14
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
CALL sp_xa(5);
connection conn_client_3;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
include/save_master_gtid.inc
connection server_1;
include/sync_with_master_gtid.inc
connection conn_client_3;
SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (5+1, REPEAT("x", 4100));
connection server_2;
SET DEBUG_SYNC= "now WAIT_FOR con3_ready";
# Kill the server
connection server_1;
include/wait_for_slave_param.inc [Slave_SQL_Running_State]
include/stop_slave.inc
include/assert.inc [Table t1 should have 4 rows.]
SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos
0-2-15
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_2;
include/assert.inc [Table t1 should have 4 rows.]
FOUND 1 /truncated binlog file.*to remove transactions starting from GTID 0-2-15/ in mysqld.2.err
disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_1;
set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
connection server_2;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-2-14
include/start_slave.inc
connection server_2;
# no xid in the list
XA RECOVER;
formatID gtrid_length bqual_length data
1 3 0 xid
connection server_1;
XA COMMIT 'xid';
# The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-1-16
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-2-15,0-1-16
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-2-15
connection server_2;
SELECT COUNT(*) = 5 as 'true' FROM t1;
true
1
# ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-1-16
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-1-16
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-2-15,0-1-16
include/diff_tables.inc [server_1:t1, server_2:t1]
#
# Case:3.B
#
# CRASH the primary/master after a sequence of
# XA PREPARE, Rotate, Trx, Rotate, XA COMMIT.
# Trx and XA-COMMIT are discarded from truncated binlog.
# FAILOVER to server_2.
connection server_1;
# CALL sp_xa(6)
# INSERT INTO t1 VALUES (6+1, REPEAT("x", 4100))
# XA COMMIT 'xid'
# Expected State post crash:
select @@global.gtid_binlog_pos as "initial server_1 state";
initial server_1 state
0-1-16
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,;
connection conn_client_3;
CALL sp_xa(6);
connection conn_client;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (6+1, REPEAT("x", 4100));
connection conn_client_3;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
include/save_master_gtid.inc
connection server_2;
include/sync_with_master_gtid.inc
connection conn_client_3;
SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go";
XA COMMIT 'xid';
connection server_1;
SET DEBUG_SYNC= "now WAIT_FOR con3_ready";
# Kill the server
connection server_2;
include/wait_for_slave_param.inc [Slave_SQL_Running_State]
include/stop_slave.inc
include/assert.inc [Table t1 should have 6 rows.]
SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos
0-1-18
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --max-binlog-size=4096
connection server_1;
include/assert.inc [Table t1 should have 5 rows.]
FOUND 1 /truncated binlog file.*to remove transactions starting from GTID 0-1-18/ in mysqld.1.err
disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_2;
set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
connection server_1;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-1-17
include/start_slave.inc
#
# server_2 promoted to the primary/master will serve new replica/slave server_1 starting from 0-1-18
#
connection server_2;
XA COMMIT 'xid';
# The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-2-19
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-1-18,0-2-19
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-1-18
connection server_1;
SELECT COUNT(*) = 5 as 'true' FROM t1;
true
0
# ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-2-19
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-2-19
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-1-18,0-2-19
include/diff_tables.inc [server_1:t1, server_2:t1]
#
# Case:3.C
#
# CRASH the primary/master after a sequence of
# XAP_1, XAC_2, XAP_3, Rotate(1->2), BCP(1), Trx_4, Rotate(2->3), XAR_5
# FAILOVER to server_1.
connection server_2;
SET GLOBAL debug_dbug="+d,sleep_at_mark_xid_done";
CALL sp_xa(8);
XA COMMIT 'xid';
# XAP_1
# XAC_2
# CALL sp_xa(9)
# INSERT INTO t1 VALUES (9+1, REPEAT("4", 4100))
# XA ROLLBACK 'xid'
# Expected State post crash:
select @@global.gtid_binlog_pos as "initial server_2 state";
initial server_2 state
0-2-21
connect conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connect conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,;
connection conn_client_3;
CALL sp_xa(9);
connection conn_client;
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
INSERT INTO t1 VALUES (9+1, REPEAT("4", 4100));
connection conn_client_3;
SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
include/save_master_gtid.inc
connection server_1;
include/sync_with_master_gtid.inc
connection conn_client_3;
SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go";
XA ROLLBACK 'xid';
connection server_2;
SET DEBUG_SYNC= "now WAIT_FOR con3_ready";
# Kill the server
connection server_1;
include/wait_for_slave_param.inc [Slave_SQL_Running_State]
include/stop_slave.inc
include/assert.inc [Table t1 should have 9 rows.]
SELECT @@GLOBAL.gtid_current_pos;
@@GLOBAL.gtid_current_pos
0-2-23
# restart: --skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --debug-dbug=+d,simulate_stale_binlog_checkpoint --max-binlog-size=4096
connection server_2;
include/assert.inc [Table t1 should have 8 rows.]
FOUND 1 /truncated binlog file.*to remove transactions starting from GTID 0-2-22/ in mysqld.2.err
disconnect conn_client;
disconnect conn_client_2;
disconnect conn_client_3;
connection server_1;
set global rpl_semi_sync_master_enabled = 1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC;
connection server_2;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
@@global.gtid_slave_pos
0-2-21
include/start_slave.inc
#
# server_1 promoted to the primary/master will serve new replica/slave server_2 starting from 0-2-22
#
connection server_1;
XA ROLLBACK 'xid';
# The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-1-24
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-2-23,0-1-24
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-2-23
connection server_2;
SELECT COUNT(*) = 5 as 'true' FROM t1;
true
0
# ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
Variable_name Value
gtid_slave_pos 0-1-24
SHOW VARIABLES LIKE 'gtid_binlog_pos';
Variable_name Value
gtid_binlog_pos 0-1-24
SHOW VARIABLES LIKE 'gtid_binlog_state';
Variable_name Value
gtid_binlog_state 0-2-23,0-1-24
include/diff_tables.inc [server_1:t1, server_2:t1]
# end of the cases
connection server_1;
drop procedure sp_xa;
connection server_2;
include/stop_slave.inc
#
# Cleanup
#
connection server_1;
set global rpl_semi_sync_slave_enabled = 0;
set global rpl_semi_sync_master_enabled = 0;
set global rpl_semi_sync_master_wait_point=default;
RESET MASTER;
RESET SLAVE;
connection server_2;
RESET MASTER;
RESET SLAVE;
set @@global.rpl_semi_sync_master_enabled = 0;
set @@global.rpl_semi_sync_slave_enabled = 0;
set @@global.rpl_semi_sync_master_wait_point=default;
CHANGE MASTER TO master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root', master_use_gtid=SLAVE_POS;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
include/start_slave.inc
connection server_1;
DROP TABLE t1;
connection server_2;
connection default;
include/rpl_end.inc
if ($primary)
{
--eval select @@global.gtid_binlog_pos as "initial $primary state"
}
if ($failover_to_slave) if ($failover_to_slave)
{ {
--let $server_to_crash=1 --let $server_to_crash=1
--let $server_to_promote=2 --let $server_to_promote=2
--let $new_master_port=$SERVER_MYPORT_2 --let $new_master_port=$SERVER_MYPORT_2
--let $client_port=$SERVER_MYPORT_1 --let $client_port=$SERVER_MYPORT_1
--connect (conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_1,)
--connect (conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,)
--connect (conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,) --connect (conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_1,)
} }
if (!$failover_to_slave) if (!$failover_to_slave)
...@@ -14,6 +19,8 @@ if (!$failover_to_slave) ...@@ -14,6 +19,8 @@ if (!$failover_to_slave)
--let $new_master_port=$SERVER_MYPORT_1 --let $new_master_port=$SERVER_MYPORT_1
--let $client_port=$SERVER_MYPORT_2 --let $client_port=$SERVER_MYPORT_2
--connect (conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,)
--connect (conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_2,)
--connect (conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,) --connect (conn_client,127.0.0.1,root,,test,$SERVER_MYPORT_2,)
} }
...@@ -23,10 +30,15 @@ if (!$failover_to_slave) ...@@ -23,10 +30,15 @@ if (!$failover_to_slave)
if ($case == 1) if ($case == 1)
{ {
if ($pre_query_to_crash)
{
--eval $pre_query_to_crash
}
SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_after_release_LOCK_after_binlog_sync SIGNAL con1_ready WAIT_FOR con1_go";
--send_eval $query_to_crash --send_eval $query_to_crash
--connection server_$server_to_crash --connection server_$server_to_crash
SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
--eval select @@global.gtid_binlog_pos as "server_$server_to_crash state"
--source include/kill_mysqld.inc --source include/kill_mysqld.inc
} }
...@@ -35,7 +47,7 @@ if ($case == 2) ...@@ -35,7 +47,7 @@ if ($case == 2)
{ {
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
--send_eval $query_to_crash --send_eval $query_to_crash
--connect (conn_client_2,127.0.0.1,root,,test,$SERVER_MYPORT_2,) --connection conn_client_2
# use the same signal with $query_to_crash # use the same signal with $query_to_crash
SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
SET GLOBAL debug_dbug="d,Notify_binlog_EOF"; SET GLOBAL debug_dbug="d,Notify_binlog_EOF";
...@@ -48,15 +60,40 @@ if ($case == 2) ...@@ -48,15 +60,40 @@ if ($case == 2)
# complicate recovery with an extra binlog file # complicate recovery with an extra binlog file
if ($case == 3) if ($case == 3)
{ {
if ($pre_query_to_crash)
{
--connection conn_client_3
--eval $pre_query_to_crash
if ($pre_query_to_crash_2)
{
--eval $pre_query_to_crash_2
}
--connection conn_client
}
SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL con1_ready WAIT_FOR con1_go";
--send_eval $query_to_crash --send_eval $query_to_crash
--connect (conn_client_3,127.0.0.1,root,,test,$SERVER_MYPORT_1,) --connection conn_client_3
SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; SET DEBUG_SYNC= "now WAIT_FOR con1_ready";
--source include/save_master_gtid.inc
# This guarantees the $expected_rows_on_slave assert
--connection server_$server_to_promote
--source include/sync_with_master_gtid.inc
--connection conn_client_3
# use the same signal with $query_to_crash # use the same signal with $query_to_crash
SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go"; SET DEBUG_SYNC= "commit_before_update_binlog_end_pos SIGNAL con3_ready WAIT_FOR con1_go";
--send_eval $query2_to_crash --send_eval $query2_to_crash
--connection server_$server_to_crash --connection server_$server_to_crash
SET DEBUG_SYNC= "now WAIT_FOR con3_ready"; SET DEBUG_SYNC= "now WAIT_FOR con3_ready";
--disable_query_log
--disable_result_log
if ($debug_pre_crash)
{
--eval $debug_pre_crash
}
--enable_result_log
--enable_query_log
--source include/kill_mysqld.inc --source include/kill_mysqld.inc
} }
...@@ -67,14 +104,13 @@ source include/wait_for_slave_param.inc; ...@@ -67,14 +104,13 @@ source include/wait_for_slave_param.inc;
--error 2003 --error 2003
--source include/stop_slave.inc --source include/stop_slave.inc
--let $assert_cond= COUNT(*) = $expected_rows_on_slave FROM t1 --let $assert_cond= COUNT(*) = $expected_rows_on_slave FROM t1
--let $assert_text= Table t1 should have $expected_rows_on_slave rows. --let $assert_text= Table t1 should have $expected_rows_on_slave rows.
--source include/assert.inc --source include/assert.inc
SELECT @@GLOBAL.gtid_current_pos; SELECT @@GLOBAL.gtid_current_pos;
--let $restart_parameters=--skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 --let $restart_parameters=--skip-slave-start=1 --rpl-semi-sync-slave-enabled=1 $dbug_restart --max-binlog-size=4096
--let $allow_rpl_inited=1 --let $allow_rpl_inited=1
--source include/start_mysqld.inc --source include/start_mysqld.inc
--connection server_$server_to_crash --connection server_$server_to_crash
...@@ -92,6 +128,8 @@ let $log_error_ = $MYSQLTEST_VARDIR/log/mysqld.$server_to_crash.err; ...@@ -92,6 +128,8 @@ let $log_error_ = $MYSQLTEST_VARDIR/log/mysqld.$server_to_crash.err;
--source include/search_pattern_in_file.inc --source include/search_pattern_in_file.inc
--disconnect conn_client --disconnect conn_client
--disconnect conn_client_2
--disconnect conn_client_3
# #
# FAIL OVER now to new master # FAIL OVER now to new master
...@@ -109,4 +147,12 @@ if (`select $server_to_crash = 2`) ...@@ -109,4 +147,12 @@ if (`select $server_to_crash = 2`)
evalp CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS; evalp CHANGE MASTER TO master_host='127.0.0.1', master_port=$new_master_port, master_user='root', master_use_gtid=SLAVE_POS;
set global rpl_semi_sync_slave_enabled = 1; set global rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos; set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
select @@global.gtid_slave_pos;
--source include/start_slave.inc --source include/start_slave.inc
if ($primary)
{
--let $tmp=$primary
--let $primary=$replica
--let $replica=$tmp
}
--connection server_2
--sync_with_master
--connection server_1
--let $case = 1
--echo #
--echo # Case:$case
--echo #
--echo # CRASH the original master, and FAILOVER to the new
# value 1 for server id 1 -> 2 failover
--let $failover_to_slave=1
--let $query_to_crash= INSERT INTO t1 VALUES (2, REPEAT("x", 4100))
--echo # $query_to_crash
--echo # Row - 2 will be in master's binlog but not committed, gets replicated
--echo # to slave and applied. On crash master should have 1 row and slave
--echo # should have 2 rows.
--echo #
--echo # Expected State post crash:
--echo #=================================================================
--echo # Master | Slave |
--echo # 0-1-4 (Not committed) | 0-1-4 (Received through semi-sync |
--echo # | replication and applied) |
--echo #=================================================================
--let $log_search_pattern=truncated binlog file:.*master.*000001
--let $expected_rows_on_master= 1
--let $expected_rows_on_slave= 2
--source rpl_semi_sync_crash.inc
--echo #
--echo # Server_2 promoted as master will send 0-1-4 to new slave Server_1
--echo #
--connection server_2
--let $rows_so_far=3
--eval INSERT INTO t1 VALUES ($rows_so_far, 'dummy3')
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection server_1
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--connection server_2
--let $case = 2
--echo #
--echo # Case:$case
--echo #
--echo # CRASH the new master, and FAILOVER back to the original
# value 0 for the reverse server id 2 -> 1 failover
--let $failover_to_slave=0
# Additionally through "foreign" server_id verify MDEV-27760's acceptance
# policy on the recient (to be promoted into master) server.
--let $query_to_crash = SET STATEMENT server_id=1 FOR INSERT INTO t1 VALUES (4, REPEAT("x", 4100))
--let $query2_to_crash= INSERT INTO t1 VALUES (5, REPEAT("x", 4100))
--echo # $query_to_crash
--echo # $query2_to_crash
--echo # Rows 4 and 5 will be in master's binlog but not committed, they get
--echo # replicated to slave and applied. On crash master should have 3 rows
--echo # and slave should have 5 rows.
--echo #
--echo # Expected State post crash:
--echo #=================================================================
--echo # Master | Slave |
--echo # 0-1-6 (Not commited) | 0-1-6 (Received through semi-sync |
--echo # | replication and applied) |
--echo # 0-2-7 (Not commited) | 0-2-7 (Received through semi-sync |
--echo # | replication and applied) |
--echo #=================================================================
--let $log_search_pattern=truncated binlog file:.*slave.*000002.* to remove transactions starting from GTID 0-1-6
--let $expected_rows_on_master= 3
--let $expected_rows_on_slave= 5
--source rpl_semi_sync_crash.inc
--echo #
--echo # Server_1 promoted as master will send 0-1-6 and 0-2-7 to slave Server_2
--echo #
--connection server_1
--let $rows_so_far=6
--eval INSERT INTO t1 VALUES ($rows_so_far, 'dummy6')
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection server_2
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
--connection server_1
--let $case = 3
--echo #
--echo # Case:$case
--echo #
--echo # CRASH the master and FAILOVER to slave
--let $failover_to_slave=1
--let $query_to_crash = INSERT INTO t1 VALUES (7, REPEAT("x", 4100))
--let $query2_to_crash= INSERT INTO t1 VALUES (8, REPEAT("x", 4100))
--echo # $query_to_crash
--echo # $query2_to_crash
--echo # Rows 7 and 8 will be in master's binlog but not committed, only 7
--echo # gets replicated to slave and applied. On crash master should have 6
--echo # rows and slave should have 7 rows.
--echo #
--echo # Expected State post crash:
--echo #=================================================================
--echo # Master | Slave |
--echo # 0-1-9 (Not commited) | 0-1-9 (Received through semi-sync |
--echo # | replication and applied) |
--echo # 0-1-10 (Not commited - | |
--echo # never sent to slave) | |
--echo #=================================================================
--let $log_search_pattern=truncated binlog file:.*master.* to remove transactions starting from GTID 0-1-9
--let $expected_rows_on_master= 6
--let $expected_rows_on_slave= 7
--source rpl_semi_sync_crash.inc
--echo #
--echo # Server_2 promoted as master will send 0-1-9 to slave Server_1
--echo #
--connection server_2
--let $rows_so_far=8
--eval INSERT INTO t1 VALUES ($rows_so_far, 'Done')
--source include/save_master_gtid.inc
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection server_1
--source include/sync_with_master_gtid.inc
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--source include/stop_slave.inc
...@@ -10,212 +10,7 @@ ...@@ -10,212 +10,7 @@
--source include/have_binlog_format_row.inc --source include/have_binlog_format_row.inc
--source include/master-slave.inc --source include/master-slave.inc
# Initial slave --let $scenario=rpl_semi_sync_fail_over.inc
--connection server_2 --source rpl_semi_sync_fail_over_gen.inc
--source include/stop_slave.inc
# Initial master
--connection server_1
RESET MASTER;
SET @@global.max_binlog_size= 4096;
--connection server_2
RESET MASTER;
SET @@global.max_binlog_size= 4096;
set @@global.rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos = "";
CHANGE MASTER TO master_use_gtid= slave_pos;
--source include/start_slave.inc
--connection server_1
ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
set @@global.rpl_semi_sync_master_enabled = 1;
set @@global.rpl_semi_sync_master_wait_point=AFTER_SYNC;
CREATE TABLE t1 (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb;
INSERT INTO t1 VALUES (1, 'dummy1');
--save_master_pos
--connection server_2
--sync_with_master
--connection server_1
--let $case = 1
--echo #
--echo # Case:$case
--echo #
--echo # CRASH the original master, and FAILOVER to the new
# value 1 for server id 1 -> 2 failover
--let $failover_to_slave=1
--let $query_to_crash= INSERT INTO t1 VALUES (2, REPEAT("x", 4100))
--echo # $query_to_crash
--echo # Row - 2 will be in master's binlog but not committed, gets replicated
--echo # to slave and applied. On crash master should have 1 row and slave
--echo # should have 2 rows.
--echo #
--echo # Expected State post crash:
--echo #=================================================================
--echo # Master | Slave |
--echo # 0-1-4 (Not committed) | 0-1-4 (Received through semi-sync |
--echo # | replication and applied) |
--echo #=================================================================
--let $log_search_pattern=truncated binlog file:.*master.*000001
--let $expected_rows_on_master= 1
--let $expected_rows_on_slave= 2
--source rpl_semi_sync_crash.inc
--echo #
--echo # Server_2 promoted as master will send 0-1-4 to new slave Server_1
--echo #
--connection server_2
--let $rows_so_far=3
--eval INSERT INTO t1 VALUES ($rows_so_far, 'dummy3')
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection server_1
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--connection server_2
--let $case = 2
--echo #
--echo # Case:$case
--echo #
--echo # CRASH the new master, and FAILOVER back to the original
# value 0 for the reverse server id 2 -> 1 failover
--let $failover_to_slave=0
# Additionally through "foreign" server_id verify MDEV-27760's acceptance
# policy on the recient (to be promoted into master) server.
--let $query_to_crash = SET STATEMENT server_id=1 FOR INSERT INTO t1 VALUES (4, REPEAT("x", 4100))
--let $query2_to_crash= INSERT INTO t1 VALUES (5, REPEAT("x", 4100))
--echo # $query_to_crash
--echo # $query2_to_crash
--echo # Rows 4 and 5 will be in master's binlog but not committed, they get
--echo # replicated to slave and applied. On crash master should have 3 rows
--echo # and slave should have 5 rows.
--echo #
--echo # Expected State post crash:
--echo #=================================================================
--echo # Master | Slave |
--echo # 0-1-6 (Not commited) | 0-1-6 (Received through semi-sync |
--echo # | replication and applied) |
--echo # 0-2-7 (Not commited) | 0-2-7 (Received through semi-sync |
--echo # | replication and applied) |
--echo #=================================================================
--let $log_search_pattern=truncated binlog file:.*slave.*000002.* to remove transactions starting from GTID 0-1-6
--let $expected_rows_on_master= 3
--let $expected_rows_on_slave= 5
--source rpl_semi_sync_crash.inc
--echo #
--echo # Server_1 promoted as master will send 0-1-6 and 0-2-7 to slave Server_2
--echo #
--connection server_1
--let $rows_so_far=6
--eval INSERT INTO t1 VALUES ($rows_so_far, 'dummy6')
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection server_2
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
--connection server_1
--let $case = 3
--echo #
--echo # Case:$case
--echo #
--echo # CRASH the master and FAILOVER to slave
--let $failover_to_slave=1
--let $query_to_crash = INSERT INTO t1 VALUES (7, REPEAT("x", 4100))
--let $query2_to_crash= INSERT INTO t1 VALUES (8, REPEAT("x", 4100))
--echo # $query_to_crash
--echo # $query2_to_crash
--echo # Rows 7 and 8 will be in master's binlog but not committed, only 7
--echo # gets replicated to slave and applied. On crash master should have 6
--echo # rows and slave should have 7 rows.
--echo #
--echo # Expected State post crash:
--echo #=================================================================
--echo # Master | Slave |
--echo # 0-1-9 (Not commited) | 0-1-9 (Received through semi-sync |
--echo # | replication and applied) |
--echo # 0-1-10 (Not commited - | |
--echo # never sent to slave) | |
--echo #=================================================================
--let $log_search_pattern=truncated binlog file:.*master.*000002.* to remove transactions starting from GTID 0-1-9
--let $expected_rows_on_master= 6
--let $expected_rows_on_slave= 7
--source rpl_semi_sync_crash.inc
--echo #
--echo # Server_2 promoted as master will send 0-1-9 to slave Server_1
--echo #
--connection server_2
--let $rows_so_far=8
--eval INSERT INTO t1 VALUES ($rows_so_far, 'Done')
--source include/save_master_gtid.inc
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection server_1
--source include/sync_with_master_gtid.inc
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--echo #
--echo # Cleanup
--echo #
--source include/stop_slave.inc
set global rpl_semi_sync_slave_enabled = 0;
set global rpl_semi_sync_master_enabled = 0;
set global rpl_semi_sync_master_wait_point=default;
RESET MASTER;
RESET SLAVE;
--connection server_2
RESET MASTER;
RESET SLAVE;
set @@global.rpl_semi_sync_master_enabled = 0;
set @@global.rpl_semi_sync_slave_enabled = 0;
set @@global.rpl_semi_sync_master_wait_point=default;
evalp CHANGE MASTER TO master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root', master_use_gtid=SLAVE_POS;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
--source include/start_slave.inc
--connection server_1
DROP TABLE t1;
--save_master_pos
--connection server_2
--sync_with_master
connection default;
--enable_reconnect
--source include/wait_until_connected_again.inc
--source include/rpl_end.inc --source include/rpl_end.inc
# Initial slave
--connection server_2
--source include/stop_slave.inc
# Initial master
--connection server_1
RESET MASTER;
SET @@global.max_binlog_size= 4096;
--connection server_2
RESET MASTER;
SET @@global.max_binlog_size= 4096;
set @@global.rpl_semi_sync_slave_enabled = 1;
set @@global.gtid_slave_pos = "";
CHANGE MASTER TO master_use_gtid= slave_pos;
--source include/start_slave.inc
--connection server_1
ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
set @@global.rpl_semi_sync_master_enabled = 1;
set @@global.rpl_semi_sync_master_wait_point=AFTER_SYNC;
CREATE TABLE t1 (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb;
INSERT INTO t1 VALUES (1, 'dummy1');
--save_master_pos
# the actual slave server will have stopped the slave service at exit
--source $scenario
--echo #
--echo # Cleanup
--echo #
# current slave
--connection server_1
set global rpl_semi_sync_slave_enabled = 0;
set global rpl_semi_sync_master_enabled = 0;
set global rpl_semi_sync_master_wait_point=default;
RESET MASTER;
RESET SLAVE;
--connection server_2
RESET MASTER;
RESET SLAVE;
set @@global.rpl_semi_sync_master_enabled = 0;
set @@global.rpl_semi_sync_slave_enabled = 0;
set @@global.rpl_semi_sync_master_wait_point=default;
evalp CHANGE MASTER TO master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root', master_use_gtid=SLAVE_POS;
set @@global.gtid_slave_pos=@@global.gtid_binlog_pos;
--source include/start_slave.inc
--connection server_1
DROP TABLE t1;
--save_master_pos
--connection server_2
--sync_with_master
connection default;
--enable_reconnect
--source include/wait_until_connected_again.inc
!include suite/rpl/rpl_1slave_base.cnf
!include include/default_client.cnf
[mysqld.1]
log-slave-updates
gtid-strict-mode=1
sync-binlog=1
[mysqld.2]
log-slave-updates
gtid-strict-mode=1
sync-binlog=1
--let $primary=server_1
--let $replica=server_2
--connection $primary
delimiter |;
CREATE PROCEDURE sp_xa(val int)
BEGIN
XA START 'xid';
INSERT INTO t1 VALUES (val, REPEAT("x", 4100));
XA END 'xid';
XA PREPARE 'xid';
END|
delimiter ;|
--connection $replica
--sync_with_master
# $case corresponds to execution env created by rpl_semi_sync_crash.inc
# invoked with $query_to_crash etc.
--let $case = 1
--echo #
--echo # Case:$case.A
--echo #
--echo # CRASH $primary/master $primary after XA PREPARE semisynced
--echo # FAILOVER to $replica/slave
--connection $primary
--let $next_val=`SELECT max(a)+1 FROM t1`
--let $query_to_crash= CALL sp_xa($next_val)
--echo # $query_to_crash
--let $truncate_gtid=0-1-5
--echo # Expected State post crash:
--let $log_search_pattern=truncated binlog file.*to remove transactions starting from GTID $truncate_gtid
--let $expected_rows_on_master= 1
--let $expected_rows_on_slave= 1
--let $failover_to_slave=1
--source rpl_semi_sync_crash.inc
--echo #
--echo # $primary promoted to the primary/master will serve new replica/slave $replica starting from $truncate_gtid
--echo #
--connection $primary
--let $rows_so_far=2
--eval XA COMMIT 'xid'
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection $replica
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
--echo #
--echo # Case:$case.B
--echo #
--echo # CRASH the primary/master after a sequence of
--echo # XA PREPARE, XA COMMIT (XAC becomes durable in Engine), Trx is binlogged
--echo # but remains only prepared by crash time.
--echo # FAILOVER to $replica.
--connection $primary
--let $next_val=`SELECT max(a)+1 FROM t1`
--eval CALL sp_xa($next_val)
--save_master_pos
--connection $replica
--sync_with_master
--connection $primary
XA COMMIT 'xid';
FLUSH LOGS;
--source include/wait_for_binlog_checkpoint.inc
--let $next_val=`SELECT max(a)+1 FROM t1`
--let $query_to_crash= INSERT INTO t1 VALUES ($next_val, REPEAT("x", 4100))
--echo # $query_to_crash
--let $truncate_gtid=0-2-9
--echo # Expected State post crash:
--let $log_search_pattern=truncated binlog file.*to remove transactions starting from GTID $truncate_gtid
--let $expected_rows_on_master= 3
--let $expected_rows_on_slave= 4
--let $failover_to_slave=0
--source rpl_semi_sync_crash.inc
--echo #
--echo # $primary promoted to the primary/master will serve new replica/slave $replica starting from $truncate_gtid
--echo #
--connection $primary
--eval DELETE FROM t1 WHERE a = $next_val
--let $rows_so_far=3
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection $replica
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
--echo #
--echo # Case:$case.C
--echo #
--echo # CRASH the primary/master after a sequence of
--echo # XA PREPARE, XA ROLLBACK. XAR won't be durable in Engine and will be
--echo # discarded from truncated binlog.
--echo # XAP remains prepared after crash-recovery on the former
--echo # demoted-to-slave master.
--echo # FAILOVER to $replica.
--connection $primary
call mtr.add_suppression("Found 1 prepared XA transactions");
--let $next_val=`SELECT max(a)+1 FROM t1`
--let $pre_query_to_crash=CALL sp_xa($next_val)
--let $query_to_crash= XA ROLLBACK 'xid'
--echo # $query_to_crash
--let $truncate_gtid=0-1-13
--echo # Expected State post crash:
--let $log_search_pattern=truncated binlog file.*to remove transactions starting from GTID $truncate_gtid
--let $expected_rows_on_master= 3
--let $expected_rows_on_slave= 3
--let $failover_to_slave=1
--source rpl_semi_sync_crash.inc
let $pre_query_to_crash=;
--echo #
--echo # $primary promoted to the primary/master will serve new replica/slave $replica starting from $truncate_gtid
--echo #
--connection $primary
--let $next_val=`SELECT max(a)+1 FROM t1`
--eval INSERT INTO t1 VALUES ($next_val, REPEAT("x", 4100));
--let $rows_so_far=4
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection $replica
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
--let $case=3
--echo #
--echo # Case:$case.A
--echo #
--echo # CRASH the primary/master after a sequence of
--echo # XA PREPARE, Trx, Rotate.
--echo # Both XAP and Trx are discarded from truncated binlog.
--echo # FAILOVER to $replica.
--connection $primary
--let $next_val=`SELECT max(a)+1 FROM t1`
--let $query_to_crash= CALL sp_xa($next_val)
--let $query2_to_crash=INSERT INTO t1 VALUES ($next_val+1, REPEAT("x", 4100))
--echo # $query_to_crash
--let $truncate_gtid=0-2-15
--echo # Expected State post crash:
--let $log_search_pattern=truncated binlog file.*to remove transactions starting from GTID $truncate_gtid
--let $expected_rows_on_master= 4
--let $expected_rows_on_slave= 4
--let $failover_to_slave=0
--source rpl_semi_sync_crash.inc
--connection $replica
--echo # no xid in the list
XA RECOVER;
--connection $primary
--eval XA COMMIT 'xid'
--let $rows_so_far=5
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection $replica
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
--let $case=3
--echo #
--echo # Case:$case.B
--echo #
--echo # CRASH the primary/master after a sequence of
--echo # XA PREPARE, Rotate, Trx, Rotate, XA COMMIT.
--echo # Trx and XA-COMMIT are discarded from truncated binlog.
--echo # FAILOVER to $replica.
# XAP recovers because it's "made" durable by the binlogged XAC.
# Trx ($query_to_crash) is to truncate from.
--connection $primary
--let $next_val=`SELECT max(a)+1 FROM t1`
# $pre_query_to_crash and $query2_to_crash run by the same connection
--let $pre_query_to_crash=CALL sp_xa($next_val)
--let $query_to_crash=INSERT INTO t1 VALUES ($next_val+1, REPEAT("x", 4100))
--let $query2_to_crash=XA COMMIT 'xid'
--echo # $pre_query_to_crash
--echo # $query_to_crash
--echo # $query2_to_crash
--let $truncate_gtid=0-1-18
--echo # Expected State post crash:
--let $log_search_pattern=truncated binlog file.*to remove transactions starting from GTID $truncate_gtid
--let $expected_rows_on_master= 5
--let $expected_rows_on_slave= 6
--let $failover_to_slave=1
--source rpl_semi_sync_crash.inc
--echo #
--echo # $primary promoted to the primary/master will serve new replica/slave $replica starting from $truncate_gtid
--echo #
--connection $primary
# complete the xa
--eval $query2_to_crash
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection $replica
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
let $pre_query_to_crash=;
let $pre_query_to_crash_2=;
--let $case=3
--echo #
--echo # Case:$case.C
--echo #
--echo # CRASH the primary/master after a sequence of
--echo # XAP_1, XAC_2, XAP_3, Rotate(1->2), BCP(1), Trx_4, Rotate(2->3), XAR_5
--echo # FAILOVER to $replica.
# Simulate no Binlog Check-Point BCP(1) in the rotated (2,3) logs which
# causes the entire sequence involvement into recovery.
# Truncation from Trx_4 as XAR_5 clears doubts about XAP_3 to keep as prepared.
--connection $primary
SET GLOBAL debug_dbug="+d,sleep_at_mark_xid_done";
--let $next_val=`SELECT max(a)+1 FROM t1`
--eval CALL sp_xa($next_val)
--inc $next_val
XA COMMIT 'xid';
# $pre_query_to_crash and $query2_to_crash run by the same connection
--let $pre_query_to_crash =CALL sp_xa($next_val)
--let $query_to_crash= INSERT INTO t1 VALUES ($next_val+1, REPEAT("4", 4100))
--let $query2_to_crash= XA ROLLBACK 'xid'
--echo # XAP_1
--echo # XAC_2
--echo # $pre_query_to_crash
--echo # $query_to_crash
--echo # $query2_to_crash
--let $truncate_gtid=0-2-22
--echo # Expected State post crash:
--let $log_search_pattern=truncated binlog file.*to remove transactions starting from GTID $truncate_gtid
--let $expected_rows_on_master= 8
--let $expected_rows_on_slave= 9
--let $failover_to_slave=`select ($failover_to_slave+1) % 2`
--let $dbug_restart="--debug-dbug=+d,simulate_stale_binlog_checkpoint"
#--let $debug_pre_crash=select sleep(60)
--source rpl_semi_sync_crash.inc
--echo #
--echo # $primary promoted to the primary/master will serve new replica/slave $replica starting from $truncate_gtid
--echo #
--connection $primary
# complete the xa
--eval $query2_to_crash
--save_master_pos
--echo # The gtid state on current master must be equal to ...
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
SHOW VARIABLES LIKE 'gtid_slave_pos';
--connection $replica
--sync_with_master
--eval SELECT COUNT(*) = $rows_so_far as 'true' FROM t1
--echo # ... the gtid states on the slave:
SHOW VARIABLES LIKE 'gtid_slave_pos';
SHOW VARIABLES LIKE 'gtid_binlog_pos';
SHOW VARIABLES LIKE 'gtid_binlog_state';
--let $diff_tables=server_1:t1, server_2:t1
--source include/diff_tables.inc
let $pre_query_to_crash =;
let $pre_query_to_crash_2=;
--echo # end of the cases
--connection $primary
drop procedure sp_xa;
--connection $replica
--sync_with_master
--source include/stop_slave.inc
# ==== References ====
#
# MDEV-21117 recovery for --rpl-semi-sync-slave-enabled server
# MDEV-27760 event may non stop replicate in circular semisync setup
#
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
--source include/have_binlog_format_row.inc
--source include/master-slave.inc
--let $scenario=rpl_semi_sync_fail_over_xa.inc
--source rpl_semi_sync_fail_over_gen.inc
--source include/rpl_end.inc
...@@ -2545,6 +2545,7 @@ struct xarecover_st ...@@ -2545,6 +2545,7 @@ struct xarecover_st
int len, found_foreign_xids, found_my_xids; int len, found_foreign_xids, found_my_xids;
XID *list; XID *list;
HASH *commit_list; HASH *commit_list;
HASH *xa_prepared_list; // prepared user xa list
bool dry_run; bool dry_run;
MEM_ROOT *mem_root; MEM_ROOT *mem_root;
bool error; bool error;
...@@ -2553,13 +2554,14 @@ struct xarecover_st ...@@ -2553,13 +2554,14 @@ struct xarecover_st
/** /**
Inserts a new hash member. Inserts a new hash member.
returns a successfully created and inserted @c xid_recovery_member @return a successfully created and inserted @c xid_recovery_member
into hash @c hash_arg, into hash @c hash_arg,
or NULL. or NULL.
*/ */
static xid_recovery_member* static xid_recovery_member*
xid_member_insert(HASH *hash_arg, my_xid xid_arg, MEM_ROOT *ptr_mem_root, xid_member_insert(HASH *hash_arg, my_xid xid_arg, MEM_ROOT *ptr_mem_root,
XID *full_xid_arg, decltype(::server_id) server_id_arg) XID *full_xid_arg, decltype(::server_id) server_id_arg,
xid_recovery_member::enum_xa_binlog_state xa_binlog_state_arg)
{ {
xid_recovery_member *member= (xid_recovery_member *) xid_recovery_member *member= (xid_recovery_member *)
alloc_root(ptr_mem_root, sizeof(xid_recovery_member)); alloc_root(ptr_mem_root, sizeof(xid_recovery_member));
...@@ -2573,32 +2575,61 @@ xid_member_insert(HASH *hash_arg, my_xid xid_arg, MEM_ROOT *ptr_mem_root, ...@@ -2573,32 +2575,61 @@ xid_member_insert(HASH *hash_arg, my_xid xid_arg, MEM_ROOT *ptr_mem_root,
if (full_xid_arg) if (full_xid_arg)
*xid_full= *full_xid_arg; *xid_full= *full_xid_arg;
*member= xid_recovery_member(xid_arg, 1, false, xid_full, server_id_arg); *member=
xid_recovery_member(xid_arg,
xa_binlog_state_arg == xid_recovery_member::XA_NONE ?
1 : 0, false, xid_full, server_id_arg,
xa_binlog_state_arg);
return return
my_hash_insert(hash_arg, (uchar*) member) ? NULL : member; my_hash_insert(hash_arg, (uchar*) member) ? NULL : member;
} }
/* /**
Inserts a new or updates an existing hash member to increment Inserts a new or updates an existing hash member to increment
the member's prepare counter. the member's engine prepare counter or binlog transaction state.
For normal transactions @c xid_arg must be non-zero, and it's zero
for the user XA.
@c xa_binlog_state_arg is meaningful only for the latter.
returns false on success, @return xid_recovery_member pointer to when success,
true otherwise. NULL otherwise.
*/ */
static bool xid_member_replace(HASH *hash_arg, my_xid xid_arg, xid_recovery_member*
xid_member_replace(HASH *hash_arg, my_xid xid_arg,
MEM_ROOT *ptr_mem_root, MEM_ROOT *ptr_mem_root,
XID *full_xid_arg, XID *full_xid_arg,
decltype(::server_id) server_id_arg) decltype(::server_id) server_id_arg,
xid_recovery_member::enum_xa_binlog_state
xa_binlog_state_arg)
{ {
xid_recovery_member* member; xid_recovery_member* member;
if ((member= (xid_recovery_member *) if ((member= (xid_recovery_member *)
my_hash_search(hash_arg, (uchar *)& xid_arg, sizeof(xid_arg)))) my_hash_search(hash_arg,
(xid_arg == 0) ? full_xid_arg->key() : (uchar *)& xid_arg,
(xid_arg == 0) ?
full_xid_arg->key_length() : sizeof(xid_arg))))
{
if (xa_binlog_state_arg == xid_recovery_member::XA_NONE)
{
DBUG_ASSERT(member->in_engine_prepare > 0);
member->in_engine_prepare++; member->in_engine_prepare++;
}
else else
member= xid_member_insert(hash_arg, xid_arg, ptr_mem_root, full_xid_arg, server_id_arg); {
DBUG_ASSERT(member->xid == 0);
DBUG_ASSERT(xa_binlog_state_arg == xid_recovery_member::XA_COMPLETE ||
xa_binlog_state_arg == xid_recovery_member::XA_PREPARE);
return member == NULL; member->is_state_valid= member->xa_binlog_state != xa_binlog_state_arg;
member->xa_binlog_state= xa_binlog_state_arg;
}
}
else
member= xid_member_insert(hash_arg, xid_arg, ptr_mem_root, full_xid_arg,
server_id_arg, xa_binlog_state_arg);
return member;
} }
/* /*
...@@ -2634,6 +2665,75 @@ static bool xarecover_decide_to_commit(xid_recovery_member* member, ...@@ -2634,6 +2665,75 @@ static bool xarecover_decide_to_commit(xid_recovery_member* member,
true : false); true : false);
} }
/*
Conduct decisions on the user XA:s according to the xa state and
recovery configuration.
ptr_commit_max is NULL implies the normal recovery in which case
the decision is computed along the normal transaction recovery rules.
Otherwise in the semisync recovery when XA is prepared in Engine
and there's no intent in binlog to complete it, then its fate depends on
whether its xid is present in Xid_list_log.
When it's there the prepared XA survives, else it's rolled back.
Completion operations over a prepared XA recorded in binlog are
decided similarly to the normal transaction case, basing on
the operation's binlog offset that is compared against a computed
truncate position.
Returns 0 as do nothing
-1,1 as perform according to xa_binlog_state
*/
static int xarecover_decide_xa(xid_recovery_member* member,
Binlog_offset *ptr_commit_max)
{
int rc= -1; // todo: account Xlle
if (member->xa_binlog_state > xid_recovery_member::XA_NONE)
{
if (member->xa_binlog_state == xid_recovery_member::XA_PREPARE)
{//if (member->in_engine_prepare == 0) {member->is_state_valid= false; return 0;}
if (member->decided_to_commit)
{
rc= 0;
xid_cache_insert(member->full_xid);
}
else if (!ptr_commit_max)
{
member->xa_binlog_state= xid_recovery_member::XA_ROLLBACK;
rc= -1;
}
else if (member->binlog_coord >= *ptr_commit_max)
{
member->xa_binlog_state= xid_recovery_member::XA_ROLLBACK;
rc= -1;
}
else
{
/*
In the semisync slave recovery XA-PREPARE was followed in binlog
with a committed transaction.
*/
DBUG_ASSERT(member->in_engine_prepare > 0);
rc= 0;
xid_cache_insert(member->full_xid);
}
}
else
{
DBUG_ASSERT(member->xa_binlog_state > xid_recovery_member::XA_COMPLETE);
rc= xarecover_decide_to_commit(member, ptr_commit_max) ?
(member->xa_binlog_state == xid_recovery_member::XA_COMMIT ? 1 : -1) : 0;
if (rc == 0)
xid_cache_insert(member->full_xid);
}
}
return rc;
}
/* /*
Helper function for xarecover_do_commit_or_rollback_handlerton. Helper function for xarecover_do_commit_or_rollback_handlerton.
For a given hton decides what to do with a xid passed in the 2nd arg For a given hton decides what to do with a xid passed in the 2nd arg
...@@ -2653,9 +2753,10 @@ static void xarecover_do_commit_or_rollback(handlerton *hton, ...@@ -2653,9 +2753,10 @@ static void xarecover_do_commit_or_rollback(handlerton *hton,
else else
x= *member->full_xid; x= *member->full_xid;
if (member->xid > 0)
{
rc= xarecover_decide_to_commit(member, ptr_commit_max) ? rc= xarecover_decide_to_commit(member, ptr_commit_max) ?
hton->commit_by_xid(hton, &x) : hton->rollback_by_xid(hton, &x); hton->commit_by_xid(hton, &x) : hton->rollback_by_xid(hton, &x);
/* /*
It's fine to have non-zero rc which would be from transaction It's fine to have non-zero rc which would be from transaction
non-participant hton:s. non-participant hton:s.
...@@ -2674,6 +2775,15 @@ static void xarecover_do_commit_or_rollback(handlerton *hton, ...@@ -2674,6 +2775,15 @@ static void xarecover_do_commit_or_rollback(handlerton *hton,
member->decided_to_commit ? "Committed" : member->decided_to_commit ? "Committed" :
"Rolled back", (ulonglong) member->xid); "Rolled back", (ulonglong) member->xid);
} }
}
else
{
int do_it= xarecover_decide_xa(member, ptr_commit_max);
rc = do_it == 0 ? FALSE :
(do_it == 1 ?
hton->commit_by_xid(hton, &x) : hton->rollback_by_xid(hton, &x));
}
} }
/* /*
...@@ -2713,7 +2823,7 @@ static my_bool xarecover_complete_and_count(void *member_arg, ...@@ -2713,7 +2823,7 @@ static my_bool xarecover_complete_and_count(void *member_arg,
if (member->in_engine_prepare) if (member->in_engine_prepare)
{ {
complete_params->count++; complete_params->count++;
if (global_system_variables.log_warnings > 2) if (global_system_variables.log_warnings > 2) // todo: relax to info for xa
sql_print_warning("Found prepared transaction with xid %llu", sql_print_warning("Found prepared transaction with xid %llu",
(ulonglong) member->xid); (ulonglong) member->xid);
} }
...@@ -2786,8 +2896,17 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, ...@@ -2786,8 +2896,17 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
char buf[XIDDATASIZE*4+6]; char buf[XIDDATASIZE*4+6];
_db_doprnt_("ignore xid %s", xid_to_str(buf, info->list[i])); _db_doprnt_("ignore xid %s", xid_to_str(buf, info->list[i]));
}); });
if (!info->dry_run)
{
/* The user xid:s are decided for insertion with binlog */
xid_member_replace(info->xa_prepared_list, x, info->mem_root,
&info->list[i], server_id);
}
else
{
xid_cache_insert(info->list + i); xid_cache_insert(info->list + i);
info->found_foreign_xids++; info->found_foreign_xids++;
}
continue; continue;
} }
if (IF_WSREP(!(wsrep_emulate_bin_log && if (IF_WSREP(!(wsrep_emulate_bin_log &&
...@@ -2808,9 +2927,10 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, ...@@ -2808,9 +2927,10 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
// remember "full" xid too when it's not in mysql format. // remember "full" xid too when it's not in mysql format.
// Also record the transaction's original server_id. It will be used for // Also record the transaction's original server_id. It will be used for
// populating the input XID to be searched in hash. // populating the input XID to be searched in hash.
if (xid_member_replace(info->commit_list, x, info->mem_root, if (!xid_member_replace(info->commit_list, x, info->mem_root,
is_server_xid? NULL : &info->list[i], is_server_xid? NULL : &info->list[i],
is_server_xid? info->list[i].get_trx_server_id() : server_id)) is_server_xid?
info->list[i].get_trx_server_id() : server_id))
{ {
info->error= true; info->error= true;
sql_print_error("Error in memory allocation at xarecover_handlerton"); sql_print_error("Error in memory allocation at xarecover_handlerton");
...@@ -2850,12 +2970,13 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, ...@@ -2850,12 +2970,13 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
return FALSE; return FALSE;
} }
int ha_recover(HASH *commit_list, MEM_ROOT *arg_mem_root) int ha_recover(HASH *commit_list, HASH *xa_prepared_list, MEM_ROOT *arg_mem_root)
{ {
struct xarecover_st info; struct xarecover_st info;
DBUG_ENTER("ha_recover"); DBUG_ENTER("ha_recover");
info.found_foreign_xids= info.found_my_xids= 0; info.found_foreign_xids= info.found_my_xids= 0;
info.commit_list= commit_list; info.commit_list= commit_list;
info.xa_prepared_list= xa_prepared_list;
info.dry_run= (info.commit_list==0 && tc_heuristic_recover==0); info.dry_run= (info.commit_list==0 && tc_heuristic_recover==0);
info.list= NULL; info.list= NULL;
info.mem_root= arg_mem_root; info.mem_root= arg_mem_root;
...@@ -2907,7 +3028,7 @@ int ha_recover(HASH *commit_list, MEM_ROOT *arg_mem_root) ...@@ -2907,7 +3028,7 @@ int ha_recover(HASH *commit_list, MEM_ROOT *arg_mem_root)
if (info.error) if (info.error)
DBUG_RETURN(1); DBUG_RETURN(1);
if (info.commit_list) if (info.commit_list && !info.found_foreign_xids)
sql_print_information("Crash table recovery finished."); sql_print_information("Crash table recovery finished.");
DBUG_RETURN(0); DBUG_RETURN(0);
} }
......
...@@ -965,7 +965,7 @@ typedef struct xid_t XID; ...@@ -965,7 +965,7 @@ typedef struct xid_t XID;
*/ */
typedef uint Binlog_file_id; typedef uint Binlog_file_id;
const Binlog_file_id MAX_binlog_id= UINT_MAX; const Binlog_file_id MAX_binlog_id= UINT_MAX;
const my_off_t MAX_off_t = (~(my_off_t) 0); const my_off_t MAX_binlog_offset = (~(my_off_t) 0);
/* /*
Compound binlog-id and byte offset of transaction's first event Compound binlog-id and byte offset of transaction's first event
in a sequence (e.g the recovery sequence) of binlog files. in a sequence (e.g the recovery sequence) of binlog files.
...@@ -989,15 +989,30 @@ struct xid_recovery_member ...@@ -989,15 +989,30 @@ struct xid_recovery_member
Binlog_offset binlog_coord; Binlog_offset binlog_coord;
XID *full_xid; // needed by wsrep or past it recovery XID *full_xid; // needed by wsrep or past it recovery
decltype(::server_id) server_id; // server id of orginal server decltype(::server_id) server_id; // server id of orginal server
enum enum_xa_binlog_state
{ XA_NONE= 0, XA_PREPARE, XA_COMPLETE, XA_COMMIT, XA_ROLLBACK }
xa_binlog_state;
bool is_state_valid;
xid_recovery_member(my_xid xid_arg, uint prepare_arg, bool decided_arg, xid_recovery_member(my_xid xid_arg, uint prepare_arg, bool decided_arg,
XID *full_xid_arg, decltype(::server_id) server_id_arg) XID *full_xid_arg, decltype(::server_id) server_id_arg,
enum_xa_binlog_state xa_binlog_state_arg)
: xid(xid_arg), in_engine_prepare(prepare_arg), : xid(xid_arg), in_engine_prepare(prepare_arg),
decided_to_commit(decided_arg), decided_to_commit(decided_arg),
binlog_coord(Binlog_offset(MAX_binlog_id, MAX_off_t)), binlog_coord(Binlog_offset(MAX_binlog_id, MAX_binlog_offset)),
full_xid(full_xid_arg), server_id(server_id_arg) {}; full_xid(full_xid_arg), server_id(server_id_arg),
xa_binlog_state(xa_binlog_state_arg), is_state_valid(true) {};
bool is_binlog_set() { return binlog_coord.second != MAX_binlog_offset; }
}; };
xid_recovery_member*
xid_member_replace(HASH *hash_arg, my_xid xid_arg,
MEM_ROOT *ptr_mem_root,
XID *full_xid_arg,
decltype(::server_id) server_id_arg,
xid_recovery_member::enum_xa_binlog_state
xa_binlog_state= xid_recovery_member::XA_NONE);
/* for recover() handlerton call */ /* for recover() handlerton call */
#define MIN_XID_LIST_SIZE 128 #define MIN_XID_LIST_SIZE 128
#define MAX_XID_LIST_SIZE (1024*128) #define MAX_XID_LIST_SIZE (1024*128)
...@@ -5383,7 +5398,8 @@ int ha_commit_one_phase(THD *thd, bool all); ...@@ -5383,7 +5398,8 @@ int ha_commit_one_phase(THD *thd, bool all);
int ha_commit_trans(THD *thd, bool all); int ha_commit_trans(THD *thd, bool all);
int ha_rollback_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all);
int ha_prepare(THD *thd); int ha_prepare(THD *thd);
int ha_recover(HASH *commit_list, MEM_ROOT *mem_root= NULL); int ha_recover(HASH *commit_list, HASH *xa_recover_list= NULL,
MEM_ROOT *mem_root= NULL);
uint ha_recover_complete(HASH *commit_list, Binlog_offset *coord= NULL); uint ha_recover_complete(HASH *commit_list, Binlog_offset *coord= NULL);
/* transactions: these functions never call handlerton functions directly */ /* transactions: these functions never call handlerton functions directly */
......
...@@ -10884,7 +10884,7 @@ class Recovery_context ...@@ -10884,7 +10884,7 @@ class Recovery_context
a truncated tail get rolled back, otherwise they are committed. a truncated tail get rolled back, otherwise they are committed.
Both decisions are contingent on safety to truncate. Both decisions are contingent on safety to truncate.
*/ */
bool complete(MYSQL_BIN_LOG *log, HASH &xids); bool complete(MYSQL_BIN_LOG *log, HASH &xids, HASH &xa_recovery_xids);
/* /*
decides on commit of xid passed through member argument. decides on commit of xid passed through member argument.
...@@ -10955,9 +10955,9 @@ class Recovery_context ...@@ -10955,9 +10955,9 @@ class Recovery_context
Actions: Actions:
truncate_gtid then is set to "nil" as indicated by rpl_gtid::seq_no := 0. truncate_gtid then is set to "nil" as indicated by rpl_gtid::seq_no := 0.
truncate_reset_done takes a note of that fact. truncate_reset_done takes a note of that fact.
binlog_truncate_coord gets reset to the current gtid offset merely to binlog_truncate_coord gets reset to next of the current gtid offset merely
"suggest" any potential future truncate gtid must have a greater offset. to "suggest" any potential future truncate gtid must have its or a greater
gtid_maybe_to_truncate gets emptied into gtid binlog state. offset. gtid_maybe_to_truncate gets emptied into gtid binlog state.
Returns: Returns:
false on success, otherwise false on success, otherwise
...@@ -10979,11 +10979,21 @@ class Recovery_context ...@@ -10979,11 +10979,21 @@ class Recovery_context
enum_binlog_checksum_alg fd_checksum_alg); enum_binlog_checksum_alg fd_checksum_alg);
}; };
bool Recovery_context::complete(MYSQL_BIN_LOG *log, HASH &xids) bool Recovery_context::complete(MYSQL_BIN_LOG *log, HASH &xids, HASH &xa_xids)
{ {
if (!do_truncate || is_safe_to_truncate()) if (!do_truncate || is_safe_to_truncate())
{ {
uint count_in_prepare= uint count_in_prepare=
ha_recover_complete(&xa_xids,
!do_truncate ? NULL :
(truncate_gtid.seq_no > 0 ?
&binlog_truncate_coord : &last_gtid_coord));
if (count_in_prepare > 0 && global_system_variables.log_warnings > 2)
{
sql_print_information("Counted %u number of xa transactions "
"in prepared state.", count_in_prepare);
}
count_in_prepare=
ha_recover_complete(&xids, ha_recover_complete(&xids,
!do_truncate ? NULL : !do_truncate ? NULL :
(truncate_gtid.seq_no > 0 ? (truncate_gtid.seq_no > 0 ?
...@@ -11113,10 +11123,30 @@ bool Recovery_context::decide_or_assess(xid_recovery_member *member, int round, ...@@ -11113,10 +11123,30 @@ bool Recovery_context::decide_or_assess(xid_recovery_member *member, int round,
} }
else if (member->in_engine_prepare < last_gtid_engines) else if (member->in_engine_prepare < last_gtid_engines)
{ {
DBUG_ASSERT(member->in_engine_prepare > 0); DBUG_ASSERT(member->in_engine_prepare > 0 ||
/* XA event over xid that was completed in engines */
(member->xid == 0 &&
member->xa_binlog_state >= xid_recovery_member::XA_PREPARE));
if (member->in_engine_prepare > 0 &&
member->xa_binlog_state == xid_recovery_member::XA_PREPARE)
{
char buf[21];
longlong10_to_str(last_gtid.seq_no, buf, 10);
sql_print_error("Error to recovery multi-engine XA transaction: "
"the number of engines prepared %u is less than "
"the respective number %u in its GTID %u-%u-%s "
"located at file:%s pos:%llu",
member->in_engine_prepare, last_gtid_engines,
last_gtid.domain_id, last_gtid.server_id, buf,
linfo->log_file_name, last_gtid_coord.second);
return true;
}
/* /*
This is an "unlikely" branch of two or more engines in transaction This is an "unlikely" branch of two or more engines in transaction
that is partially committed, so to complete. that is partially committed, or XA prepare that will be further followed
with XA-commit-or-rollback, or XA-"complete" itself.
The decision is then to commit/leave-as-is/complete.
*/ */
member->decided_to_commit= true; member->decided_to_commit= true;
if (do_truncate) if (do_truncate)
...@@ -11144,8 +11174,43 @@ bool Recovery_context::decide_or_assess(xid_recovery_member *member, int round, ...@@ -11144,8 +11174,43 @@ bool Recovery_context::decide_or_assess(xid_recovery_member *member, int round,
member->decided_to_commit= true; member->decided_to_commit= true;
} }
else else
{
if (member->xa_binlog_state > xid_recovery_member::XA_NONE &&
member->is_binlog_set())
{
/*
The xa xid has been found at least once in a miningful binlog state
and is about to change.
When it's the transtion is from the prepared to a complete
state of the same transaction then the former xa's prepared state
is guaranteed to be durable.
Otherwise this is a duplicate xid case. The duplicate xid is one
that has smaller binlog coordinates. Its state then is confirmed
to be durable which fact need not any marking in the member because
it is to keep pointing to the "original" maximum coordinate xid.
*/
if (!truncate_validated)
{
DBUG_ASSERT(member->binlog_coord != last_gtid_coord);
/*
the first of the two options below can occur when round 1 turns to 2
so event sequences across binlog files are like the following
g_current(B_0, xid), ... g_last(B^*, xid),
where `g`:s are xa event groups associated with the same member via
common `xid`. As elsewhere B_0 is the 2nd round's first and,
B^* the 1st round binlogs.
*/
if (reset_truncate_coord(member->binlog_coord < last_gtid_coord ?
member->binlog_coord.second : pos))
return true;
}
if (member->binlog_coord < last_gtid_coord)
member->binlog_coord= last_gtid_coord;
}
else
{ {
member->binlog_coord= last_gtid_coord; member->binlog_coord= last_gtid_coord;
}
last_gtid_valid= false; last_gtid_valid= false;
/* /*
First time truncate position estimate before its validation. First time truncate position estimate before its validation.
...@@ -11245,7 +11310,9 @@ void Recovery_context::process_gtid(int round, Gtid_log_event *gev, ...@@ -11245,7 +11310,9 @@ void Recovery_context::process_gtid(int round, Gtid_log_event *gev,
last_gtid_no2pc= false; last_gtid_no2pc= false;
last_gtid_standalone= last_gtid_standalone=
(gev->flags2 & Gtid_log_event::FL_STANDALONE) ? true : false; (gev->flags2 & Gtid_log_event::FL_STANDALONE) ? true : false;
if (do_truncate && last_gtid_standalone) /* a standalone XA-COMPLETE is truncation-safe */
if (do_truncate && last_gtid_standalone
&& !(gev->flags2 & Gtid_log_event::FL_COMPLETED_XA))
update_binlog_unsafe_coord_if_needed(linfo); update_binlog_unsafe_coord_if_needed(linfo);
/* Update the binlog state with any 'valid' GTID logged after Gtid_list. */ /* Update the binlog state with any 'valid' GTID logged after Gtid_list. */
last_gtid_valid= true; // may flip at Xid when falls to truncate last_gtid_valid= true; // may flip at Xid when falls to truncate
...@@ -11274,7 +11341,7 @@ int Recovery_context::next_binlog_or_round(int& round, ...@@ -11274,7 +11341,7 @@ int Recovery_context::next_binlog_or_round(int& round,
*and* other files (binlog-checkpoint one and so on) do not have any *and* other files (binlog-checkpoint one and so on) do not have any
transaction-in-doubt. transaction-in-doubt.
*/ */
if (truncate_gtid.seq_no == 0 && truncate_set_in_1st) if (truncate_gtid.seq_no == 0 && truncate_set_in_1st && round > 1)
{ {
DBUG_ASSERT(truncate_gtid_1st_round.seq_no > 0); DBUG_ASSERT(truncate_gtid_1st_round.seq_no > 0);
...@@ -11313,6 +11380,13 @@ int Recovery_context::next_binlog_or_round(int& round, ...@@ -11313,6 +11380,13 @@ int Recovery_context::next_binlog_or_round(int& round,
} }
#endif #endif
extern "C" uchar *xid_get_var_key(xid_recovery_member *entry, size_t *length,
my_bool not_used __attribute__((unused)))
{
*length= entry->full_xid->key_length();
return (uchar *) entry->full_xid->key();
}
/* /*
Execute recovery of the binary log Execute recovery of the binary log
...@@ -11339,6 +11413,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11339,6 +11413,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
#ifdef HAVE_REPLICATION #ifdef HAVE_REPLICATION
Recovery_context ctx; Recovery_context ctx;
#endif #endif
HASH xa_recover_list;
DBUG_ENTER("TC_LOG_BINLOG::recover"); DBUG_ENTER("TC_LOG_BINLOG::recover");
/* /*
The for-loop variable is updated by the following rule set: The for-loop variable is updated by the following rule set:
...@@ -11353,13 +11429,14 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11353,13 +11429,14 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
*/ */
int round; int round;
if (! fdle->is_valid() || if (!fdle->is_valid() ||
(my_hash_init(key_memory_binlog_recover_exec, &xids, (my_hash_init(key_memory_binlog_recover_exec, &xids, &my_charset_bin,
&my_charset_bin, TC_LOG_PAGE_SIZE/3, 0, TC_LOG_PAGE_SIZE / 3, 0, sizeof(my_xid), 0, 0, MYF(0))) ||
sizeof(my_xid), 0, 0, MYF(0))) ||
(my_hash_init(key_memory_binlog_recover_exec, &ddl_log_ids, (my_hash_init(key_memory_binlog_recover_exec, &ddl_log_ids,
&my_charset_bin, 64, 0, &my_charset_bin, 64, 0, sizeof(my_xid), 0, 0, MYF(0))) ||
sizeof(my_xid), 0, 0, MYF(0)))) (my_hash_init(key_memory_binlog_recover_exec, &xa_recover_list,
&my_charset_bin, TC_LOG_PAGE_SIZE / 3, 0, 0,
(my_hash_get_key) xid_get_var_key, 0, MYF(0))))
goto err1; goto err1;
init_alloc_root(key_memory_binlog_recover_exec, &mem_root, init_alloc_root(key_memory_binlog_recover_exec, &mem_root,
...@@ -11368,8 +11445,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11368,8 +11445,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
fdle->flags&= ~LOG_EVENT_BINLOG_IN_USE_F; // abort on the first error fdle->flags&= ~LOG_EVENT_BINLOG_IN_USE_F; // abort on the first error
/* finds xids when root is not NULL */ /* finds xids when root is not NULL */
if (do_xa && ha_recover(&xids, &mem_root)) if (do_xa && ha_recover(&xids, &xa_recover_list, &mem_root))
goto err1; goto err2;
/* /*
Scan the binlog for XIDs that need to be committed if still in the Scan the binlog for XIDs that need to be committed if still in the
...@@ -11382,6 +11459,9 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11382,6 +11459,9 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
binlog_checkpoint_found= false; binlog_checkpoint_found= false;
for (round= 1;;) for (round= 1;;)
{ {
#ifdef HAVE_REPLICATION
xid_recovery_member* pending_xa_member= NULL;
#endif
while ((ev= Log_event::read_log_event(round == 1 ? first_log : &log, while ((ev= Log_event::read_log_event(round == 1 ? first_log : &log,
fdle, opt_master_verify_checksum)) fdle, opt_master_verify_checksum))
&& ev->is_valid()) && ev->is_valid())
...@@ -11427,6 +11507,17 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11427,6 +11507,17 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
ctx.last_gtid_no2pc= true; ctx.last_gtid_no2pc= true;
ctx.update_binlog_unsafe_coord_if_needed(linfo); ctx.update_binlog_unsafe_coord_if_needed(linfo);
} }
if (pending_xa_member && pending_xa_member->xa_binlog_state ==
xid_recovery_member::XA_COMPLETE)
{
pending_xa_member->xa_binlog_state=
((Query_log_event *)ev)->is_xa_commit() ?
xid_recovery_member::XA_COMMIT : xid_recovery_member::XA_ROLLBACK;
if (ctx.decide_or_assess(pending_xa_member, round, fdle, linfo,
ev->log_pos))
goto err2;
pending_xa_member= NULL;
}
#endif #endif
break; break;
} }
...@@ -11440,6 +11531,12 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11440,6 +11531,12 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
"long file name found."); "long file name found.");
else else
{ {
DBUG_EXECUTE_IF("simulate_stale_binlog_checkpoint",
{
int idx= atoi(&cev->binlog_file_name[cev->binlog_file_len -2]);
DBUG_ASSERT(idx == 16 || idx == 15);
cev->binlog_file_name[cev->binlog_file_len - 1]= '4'; // idx=14
});
/* /*
Note that we cannot use make_log_name() here, as we have not yet Note that we cannot use make_log_name() here, as we have not yet
initialised MYSQL_BIN_LOG::log_file_name. initialised MYSQL_BIN_LOG::log_file_name.
...@@ -11462,14 +11559,76 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11462,14 +11559,76 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
goto err2; goto err2;
} }
break; break;
#endif
case GTID_EVENT: case GTID_EVENT:
ctx.process_gtid(round, (Gtid_log_event *)ev, linfo); {
Gtid_log_event *gev= (Gtid_log_event *) ev;
#ifdef HAVE_REPLICATION
ctx.process_gtid(round, gev, linfo);
#endif
if (do_xa)
{
xid_recovery_member* member;
bool is_xac= gev->flags2 & Gtid_log_event::FL_COMPLETED_XA;
if (gev->flags2 & (Gtid_log_event::FL_PREPARED_XA |
Gtid_log_event::FL_COMPLETED_XA))
{
#ifndef HAVE_REPLICATION
/*
the user XA events are decided similary to the normal transaction,
difference is just in the timing which is Gtid event read now.
*/
if ((member= (xid_recovery_member *)
my_hash_search(&xa_recover_list, gev->xid->key(),
gev->xid->key_length())))
member->decided_to_commit= true;
#else
if (!(member=
xid_member_replace(&xa_recover_list, 0, &mem_root, &gev->xid,
server_id,
is_xac ? xid_recovery_member::XA_COMPLETE :
xid_recovery_member::XA_PREPARE)))
goto err2;
pending_xa_member= member;
if (!member->is_state_valid)
{
char buf[21];
longlong10_to_str(ctx.last_gtid.seq_no, buf, 10);
sql_print_warning("GTID %u-%u-%s XA transaction state % "
"in binlog file:%s pos:%llu is inconsistent "
"with the former same state; "
"consider filtered binary loggig.",
ctx.last_gtid.domain_id, ctx.last_gtid.server_id,
buf, is_xac ? "'COMPLETE'" : "'PREPARE'",
linfo->log_file_name, ctx.last_gtid_coord.second);
}
}
}
break; break;
}
case XA_PREPARE_LOG_EVENT: case XA_PREPARE_LOG_EVENT:
ctx.last_gtid_no2pc= true; // TODO: complete MDEV-21469 that removes this block if (!pending_xa_member)
ctx.update_binlog_unsafe_coord_if_needed(linfo); {
char buf_0[21], buf_1[21];
longlong10_to_str(ctx.last_gtid.seq_no, buf_0, 10);
longlong10_to_str(ev->log_pos, buf_1, 10);
sql_print_warning("Improper binlog event group: GTID %u-%u-%s "
"transaction is not marked as user XA in binlog "
"file:%s pos:%llu while contains "
"XA_PREPARE_LOG_EVENT at pos:%llu",
ctx.last_gtid.domain_id, ctx.last_gtid.server_id, buf_0,
linfo->log_file_name, ctx.last_gtid_coord.second,
buf_1);
}
if (ctx.decide_or_assess(pending_xa_member, round, fdle, linfo,
ev->log_pos))
goto err2;
pending_xa_member= NULL;
break; break;
#endif #endif
...@@ -11489,7 +11648,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11489,7 +11648,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
if (ctx.last_gtid_valid && if (ctx.last_gtid_valid &&
((ctx.last_gtid_standalone && !ev->is_part_of_group(typ)) || ((ctx.last_gtid_standalone && !ev->is_part_of_group(typ)) ||
(!ctx.last_gtid_standalone && (!ctx.last_gtid_standalone &&
(typ == XID_EVENT || ctx.last_gtid_no2pc)))) (typ == XID_EVENT || typ == XA_PREPARE_LOG_EVENT ||
ctx.last_gtid_no2pc))))
{ {
DBUG_ASSERT(round == 1 || (ctx.do_truncate && !ctx.truncate_validated)); DBUG_ASSERT(round == 1 || (ctx.do_truncate && !ctx.truncate_validated));
DBUG_ASSERT(!ctx.last_gtid_no2pc || DBUG_ASSERT(!ctx.last_gtid_no2pc ||
...@@ -11576,9 +11736,9 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11576,9 +11736,9 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
if (binlog_checkpoint_found) if (binlog_checkpoint_found)
{ {
#ifndef HAVE_REPLICATION #ifndef HAVE_REPLICATION
if (ha_recover_complete(&xids)) if (ha_recover_complete(&xids, &xa_recover_list))
#else #else
if (ctx.complete(this, xids)) if (ctx.complete(this, xids, xa_recover_list))
#endif #endif
goto err2; goto err2;
} }
...@@ -11588,6 +11748,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11588,6 +11748,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
free_root(&mem_root, MYF(0)); free_root(&mem_root, MYF(0));
my_hash_free(&xids); my_hash_free(&xids);
my_hash_free(&ddl_log_ids); my_hash_free(&ddl_log_ids);
my_hash_free(&xa_recover_list);
DBUG_RETURN(0); DBUG_RETURN(0);
err2: err2:
...@@ -11600,6 +11762,7 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -11600,6 +11762,7 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
free_root(&mem_root, MYF(0)); free_root(&mem_root, MYF(0));
my_hash_free(&xids); my_hash_free(&xids);
my_hash_free(&ddl_log_ids); my_hash_free(&ddl_log_ids);
my_hash_free(&xa_recover_list);
err1: err1:
sql_print_error("Crash recovery failed. Either correct the problem " sql_print_error("Crash recovery failed. Either correct the problem "
......
...@@ -2207,6 +2207,10 @@ class Query_log_event: public Log_event ...@@ -2207,6 +2207,10 @@ class Query_log_event: public Log_event
virtual bool is_begin() { return !strcmp(query, "BEGIN"); } virtual bool is_begin() { return !strcmp(query, "BEGIN"); }
virtual bool is_commit() { return !strcmp(query, "COMMIT"); } virtual bool is_commit() { return !strcmp(query, "COMMIT"); }
virtual bool is_rollback() { return !strcmp(query, "ROLLBACK"); } virtual bool is_rollback() { return !strcmp(query, "ROLLBACK"); }
virtual bool is_xa_commit()
{
return !strncasecmp(query, C_STRING_WITH_LEN("XA COMMIT"));
}
}; };
class Query_compressed_log_event:public Query_log_event{ class Query_compressed_log_event:public Query_log_event{
......
...@@ -3324,6 +3324,11 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg, ...@@ -3324,6 +3324,11 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg,
flags2|= thd->lex->sql_command == SQLCOM_XA_PREPARE ? flags2|= thd->lex->sql_command == SQLCOM_XA_PREPARE ?
FL_PREPARED_XA : FL_COMPLETED_XA; FL_PREPARED_XA : FL_COMPLETED_XA;
xid.set(xid_state.get_xid()); xid.set(xid_state.get_xid());
// multi-engine external completion is unaware of the prepared engine #.
extra_engines= thd->transaction->all.ha_list ?
ha_count_rw_2pc(thd_arg,
thd_arg->in_multi_stmt_transaction_mode()) - 1 : 0;
} }
/* count non-zero extra recoverable engines; total = extra + 1 */ /* count non-zero extra recoverable engines; total = extra + 1 */
if (has_xid) if (has_xid)
...@@ -3338,13 +3343,6 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg, ...@@ -3338,13 +3343,6 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg,
{ {
extra_engines= UCHAR_MAX; extra_engines= UCHAR_MAX;
} }
else if (thd->lex->sql_command == SQLCOM_XA_PREPARE)
{
DBUG_ASSERT(thd_arg->in_multi_stmt_transaction_mode());
uint8 count= ha_count_rw_2pc(thd_arg, true);
extra_engines= count > 1 ? 0 : UCHAR_MAX;
}
if (extra_engines > 0) if (extra_engines > 0)
flags_extra|= FL_EXTRA_MULTI_ENGINE; flags_extra|= FL_EXTRA_MULTI_ENGINE;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment