Skip to content

Commit ed15c0f

Browse files
author
Pedro Figueiredo
committed
BUG#27442477 ASSERT `THD->GET_HA_DATA...HAS_STATE(XID_STATE::XA_ACTIVE))' AT HANDLER.CC:1396
[post-push fix] Problem ------- After BUG#27442477, even if an XA transaction gets emptied by filters the slave applier will continue to run. While applying on the slave, if a XA transaction becomes empty, the slave applier should stop and the user should be called upon to solve the matter. Analysis & Fix -------------- A client error should be thrown in XA PREPARE, if the transaction is empty. Nothing should be written to the binary log.
1 parent aba55c8 commit ed15c0f

10 files changed

+284
-52
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
include/master-slave.inc
2+
Warnings:
3+
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
4+
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
5+
[connection master]
6+
[connection master]
7+
CREATE TABLE t1 (a INT);
8+
include/sync_slave_sql_with_master.inc
9+
include/stop_slave_sql.inc
10+
CALL mtr.add_suppression(".*The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state.*");
11+
CALL mtr.add_suppression(".*The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave.*");
12+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=(test.t1);
13+
include/start_slave_sql.inc
14+
[connection master]
15+
XA START '1';
16+
INSERT INTO t1 VALUES (1);
17+
XA END '1';
18+
XA PREPARE '1';
19+
XA COMMIT '1';
20+
[connection slave]
21+
include/wait_for_slave_sql_error.inc [errno=3227]
22+
START SLAVE SQL_THREAD;
23+
include/wait_for_slave_sql_error.inc [errno=3227]
24+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=();
25+
include/start_slave_sql.inc
26+
[connection master]
27+
include/sync_slave_sql_with_master.inc
28+
include/assert.inc [table 'test.t1' record count must be 1]
29+
include/assert_grep.inc [Found error message regarding usage of replication filters with XA transactions]
30+
[connection master]
31+
DROP TABLE t1;
32+
include/rpl_end.inc

mysql-test/suite/rpl/r/rpl_xa_with_filters_error_msg.result

+15-21
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,31 @@ Warnings:
33
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
44
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
55
[connection master]
6-
#
7-
# 1) Add a replication filter to the slave node
8-
#
9-
[connection slave]
6+
[connection master]
7+
CREATE TABLE t1 (a INT);
8+
include/sync_slave_sql_with_master.inc
109
include/stop_slave_sql.inc
11-
CALL mtr.add_suppression("Slave SQL.*The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave.*");
12-
CHANGE REPLICATION FILTER Replicate_Do_Table=(test.tab1);
10+
CALL mtr.add_suppression(".*The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state.*");
11+
CALL mtr.add_suppression(".*The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave.*");
12+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=(test.t1);
1313
include/start_slave_sql.inc
1414
[connection master]
15-
#
16-
# 2) Create a table that gets filtered out by the added filter
17-
#
18-
CREATE TABLE t1 (a INT);
19-
#
20-
# 3) Start an XA transaction and execute a statement that is filtered out by
21-
# replication filter
22-
#
2315
XA START '1';
2416
INSERT INTO t1 VALUES (1);
2517
XA END '1';
2618
XA PREPARE '1';
2719
XA COMMIT '1';
20+
[connection slave]
21+
include/wait_for_slave_sql_error.inc [errno=3227]
22+
START SLAVE SQL_THREAD;
23+
include/wait_for_slave_sql_error.inc [errno=3227]
24+
include/assert_binlog_events.inc [Gtid/SET.* # Query/use.* # Gtid/SET.* # Query/XA START.* # Table_map/.* # Write_rows/.* # Query/XA END.* # XA_prepare/XA PREPARE.* # Gtid/SET.* # Query/XA COMMIT.*]
25+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=();
26+
include/start_slave_sql.inc
27+
[connection master]
2828
include/sync_slave_sql_with_master.inc
29-
#
30-
# 4) Ensure that the error message get logged to the slave error log file.
31-
#
29+
include/assert.inc [table 'test.t1' record count must be 1]
3230
include/assert_grep.inc [Found error message regarding usage of replication filters with XA transactions]
3331
[connection master]
3432
DROP TABLE t1;
35-
include/sync_slave_sql_with_master.inc
36-
include/stop_slave_sql.inc
37-
CHANGE REPLICATION FILTER Replicate_Do_Table=();
38-
include/start_slave_sql.inc
3933
include/rpl_end.inc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--log-slave-updates=OFF --skip-log-bin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# ==== Requirements ====
2+
#
3+
# R1. When using replication filter together with XA transactions, output an
4+
# error message when some statement gets filtered within the active XA
5+
# transaction.
6+
#
7+
# ==== Implementation ====
8+
#
9+
# With default setup:
10+
# 1) Create a table that gets filtered out by the added filter
11+
# 2) Add a replication filter to the slave node
12+
# 3) Start an XA transaction and execute a statement that is filtered out by
13+
# replication filter
14+
# 4) Ensure that the error message get logged to the slave error log file.
15+
#
16+
# ==== References ====
17+
#
18+
# BUG#27442477: ASSERT `THD->GET_HA_DATA...HAS_STATE(XID_STATE::XA_ACTIVE))' AT HANDLER.CC:1396
19+
20+
--source include/have_binlog_format_row.inc
21+
--source include/master-slave.inc
22+
23+
--let $gtid_mode_value= `SELECT @@GLOBAL.GTID_MODE = "ON"`
24+
25+
--source include/rpl_connection_master.inc
26+
#
27+
# 1) Create a table that gets filtered out by the added filter
28+
#
29+
CREATE TABLE t1 (a INT);
30+
--source include/sync_slave_sql_with_master.inc
31+
32+
#
33+
# 2) Add a replication filter to the slave node
34+
#
35+
--source include/stop_slave_sql.inc
36+
CALL mtr.add_suppression(".*The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state.*");
37+
CALL mtr.add_suppression(".*The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave.*");
38+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=(test.t1);
39+
--source include/start_slave_sql.inc
40+
41+
#
42+
# 3) Start an XA transaction and execute a statement that is filtered out by
43+
# replication filter
44+
#
45+
--source include/rpl_connection_master.inc
46+
XA START '1'; INSERT INTO t1 VALUES (1); XA END '1'; XA PREPARE '1';
47+
XA COMMIT '1';
48+
--let $gtid_executed_master= `SELECT @@GLOBAL.GTID_EXECUTED`
49+
50+
#
51+
# Wait for the ER_XA_REPLICATION_FILTERS error to be thrown
52+
#
53+
--source include/rpl_connection_slave.inc
54+
--let $slave_sql_errno= convert_error(ER_XA_REPLICATION_FILTERS)
55+
--source include/wait_for_slave_sql_error.inc
56+
57+
#
58+
# Retry the transaction
59+
#
60+
START SLAVE SQL_THREAD;
61+
--let $slave_sql_errno= convert_error(ER_XA_REPLICATION_FILTERS)
62+
--source include/wait_for_slave_sql_error.inc
63+
64+
#
65+
# If GTID_MODE is ON, test that are missing GTID
66+
#
67+
if ($gtid_mode_value == 1)
68+
{
69+
--let $gtid_executed_slave= `SELECT @@GLOBAL.GTID_EXECUTED`
70+
--let $gtid_missing_slave= `SELECT GTID_SUBTRACT('$gtid_executed_master', '$gtid_executed_slave')`
71+
--disable_result_log
72+
--let $include_silent= 1
73+
--let $assert_text= slave has missing GTIDs
74+
--let $assert_cond= "$gtid_missing_slave" != ""
75+
--source include/assert.inc
76+
--let $include_silent=
77+
--enable_result_log
78+
}
79+
80+
#
81+
# Clear replication filters in order for the transaction to succeed.
82+
#
83+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=();
84+
--let $slave_sql_errno=
85+
--source include/start_slave_sql.inc
86+
--source include/rpl_connection_master.inc
87+
--source include/sync_slave_sql_with_master.inc
88+
--let $count = `SELECT COUNT(1) FROM test.t1;`
89+
--let $assert_text = table 'test.t1' record count must be 1
90+
--let $assert_cond = $count = 1
91+
--source include/assert.inc
92+
93+
#
94+
# 4) Ensure that the error message get logged to the slave error log file.
95+
#
96+
--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err
97+
--let $assert_only_after= CURRENT_TEST: rpl:rpl_xa_with_filters_error_msg
98+
--let $assert_match= The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave
99+
--let $assert_select= Slave SQL
100+
--let $assert_text= Found error message regarding usage of replication filters with XA transactions
101+
--source include/assert_grep.inc
102+
103+
--source include/rpl_connection_master.inc
104+
DROP TABLE t1;
105+
106+
--source include/rpl_end.inc

mysql-test/suite/rpl/t/rpl_xa_with_filters_error_msg.test

+74-27
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
# ==== Implementation ====
88
#
99
# With default setup:
10-
# 1) Add a replication filter to the slave node
11-
# 2) Create a table that gets filtered out by the added filter
10+
# 1) Create a table that gets filtered out by the added filter
11+
# 2) Add a replication filter to the slave node
1212
# 3) Start an XA transaction and execute a statement that is filtered out by
1313
# replication filter
1414
# 4) Ensure that the error message get logged to the slave error log file.
@@ -20,47 +20,94 @@
2020
--source include/have_binlog_format_row.inc
2121
--source include/master-slave.inc
2222

23-
--echo #
24-
--echo # 1) Add a replication filter to the slave node
25-
--echo #
26-
--source include/rpl_connection_slave.inc
23+
--let $gtid_mode_value= `SELECT @@GLOBAL.GTID_MODE = "ON"`
24+
25+
--source include/rpl_connection_master.inc
26+
#
27+
# 1) Create a table that gets filtered out by the added filter
28+
#
29+
CREATE TABLE t1 (a INT);
30+
--source include/sync_slave_sql_with_master.inc
31+
32+
#
33+
# 2) Add a replication filter to the slave node
34+
#
2735
--source include/stop_slave_sql.inc
28-
CALL mtr.add_suppression("Slave SQL.*The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave.*");
29-
CHANGE REPLICATION FILTER Replicate_Do_Table=(test.tab1);
36+
CALL mtr.add_suppression(".*The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state.*");
37+
CALL mtr.add_suppression(".*The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave.*");
38+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=(test.t1);
3039
--source include/start_slave_sql.inc
3140

41+
#
42+
# 3) Start an XA transaction and execute a statement that is filtered out by
43+
# replication filter
44+
#
3245
--source include/rpl_connection_master.inc
33-
--echo #
34-
--echo # 2) Create a table that gets filtered out by the added filter
35-
--echo #
36-
CREATE TABLE t1 (a INT);
37-
--echo #
38-
--echo # 3) Start an XA transaction and execute a statement that is filtered out by
39-
--echo # replication filter
40-
--echo #
4146
XA START '1'; INSERT INTO t1 VALUES (1); XA END '1'; XA PREPARE '1';
4247
XA COMMIT '1';
48+
--let $gtid_executed_master= `SELECT @@GLOBAL.GTID_EXECUTED`
4349

50+
#
51+
# Wait for the ER_XA_REPLICATION_FILTERS error to be thrown
52+
#
53+
--source include/rpl_connection_slave.inc
54+
--let $slave_sql_errno= convert_error(ER_XA_REPLICATION_FILTERS)
55+
--source include/wait_for_slave_sql_error.inc
56+
57+
#
58+
# Retry the transaction
59+
#
60+
START SLAVE SQL_THREAD;
61+
--let $slave_sql_errno= convert_error(ER_XA_REPLICATION_FILTERS)
62+
--source include/wait_for_slave_sql_error.inc
63+
64+
#
65+
# If GTID_MODE is ON, test that are missing GTID
66+
#
67+
if ($gtid_mode_value == 1)
68+
{
69+
--let $gtid_executed_slave= `SELECT @@GLOBAL.GTID_EXECUTED`
70+
--let $gtid_missing_slave= `SELECT GTID_SUBTRACT('$gtid_executed_master', '$gtid_executed_slave')`
71+
--disable_result_log
72+
--let $include_silent= 1
73+
--let $assert_text= slave has missing GTIDs
74+
--let $assert_cond= "$gtid_missing_slave" != ""
75+
--source include/assert.inc
76+
--let $include_silent=
77+
--enable_result_log
78+
}
79+
80+
#
81+
# Ensure that the transaction didn't get logged into the binary log
82+
#
83+
--let $event_sequence= Gtid/SET.* # Query/use.* # Gtid/SET.* # Query/XA START.* # Table_map/.* # Write_rows/.* # Query/XA END.* # XA_prepare/XA PREPARE.* # Gtid/SET.* # Query/XA COMMIT.*
84+
--let $invert= 1
85+
--source include/assert_binlog_events.inc
86+
87+
#
88+
# Clear replication filters in order for the transaction to succeed.
89+
#
90+
CHANGE REPLICATION FILTER Replicate_Ignore_Table=();
91+
--let $slave_sql_errno=
92+
--source include/start_slave_sql.inc
93+
--source include/rpl_connection_master.inc
4494
--source include/sync_slave_sql_with_master.inc
95+
--let $count = `SELECT COUNT(1) FROM test.t1;`
96+
--let $assert_text = table 'test.t1' record count must be 1
97+
--let $assert_cond = $count = 1
98+
--source include/assert.inc
4599

46-
--echo #
47-
--echo # 4) Ensure that the error message get logged to the slave error log file.
48-
--echo #
100+
#
101+
# 4) Ensure that the error message get logged to the slave error log file.
102+
#
49103
--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err
50-
--let $assert_only_after= CURRENT_TEST: rpl_gtid:rpl_xa_with_filters_error_msg
104+
--let $assert_only_after= CURRENT_TEST: rpl:rpl_xa_with_filters_error_msg
51105
--let $assert_match= The use of replication filters with XA transactions is not supported, and can lead to an undefined state in the replication slave
52106
--let $assert_select= Slave SQL
53107
--let $assert_text= Found error message regarding usage of replication filters with XA transactions
54108
--source include/assert_grep.inc
55109

56110
--source include/rpl_connection_master.inc
57111
DROP TABLE t1;
58-
--source include/sync_slave_sql_with_master.inc
59-
60-
--let $slave_sql_errno= convert_error(ER_XA_REPLICATION_FILTERS)
61-
62-
--source include/stop_slave_sql.inc
63-
CHANGE REPLICATION FILTER Replicate_Do_Table=();
64-
--source include/start_slave_sql.inc
65112

66113
--source include/rpl_end.inc

sql/binlog.cc

+22
Original file line numberDiff line numberDiff line change
@@ -2926,6 +2926,28 @@ File open_binlog_file(IO_CACHE *log, const char *log_file_name, const char **err
29262926
DBUG_RETURN(-1);
29272927
}
29282928

2929+
bool is_transaction_empty(THD *thd)
2930+
{
2931+
DBUG_ENTER("is_transaction_empty");
2932+
int rw_ha_count= check_trx_rw_engines(thd, Transaction_ctx::SESSION);
2933+
rw_ha_count+= check_trx_rw_engines(thd, Transaction_ctx::STMT);
2934+
DBUG_RETURN(rw_ha_count == 0);
2935+
}
2936+
2937+
int check_trx_rw_engines(THD *thd, Transaction_ctx::enum_trx_scope trx_scope)
2938+
{
2939+
DBUG_ENTER("check_trx_rw_engines");
2940+
2941+
int rw_ha_count= 0;
2942+
Ha_trx_info *ha_list=
2943+
(Ha_trx_info *)thd->get_transaction()->ha_trx_info(trx_scope);
2944+
2945+
for (Ha_trx_info *ha_info= ha_list; ha_info; ha_info= ha_info->next()) {
2946+
if (ha_info->is_trx_read_write())
2947+
++rw_ha_count;
2948+
}
2949+
DBUG_RETURN(rw_ha_count);
2950+
}
29292951

29302952
bool is_empty_transaction_in_binlog_cache(const THD* thd)
29312953
{

sql/binlog.h

+20
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,26 @@ typedef struct st_load_file_info
933933

934934
extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log;
935935

936+
/**
937+
Check if the the transaction is empty.
938+
939+
@param thd The client thread that executed the current statement.
940+
941+
@retval true No changes found in any storage engine
942+
@retval false Otherwise.
943+
944+
**/
945+
bool is_transaction_empty(THD* thd);
946+
/**
947+
Check if the transaction has no rw flag set for any of the storage engines.
948+
949+
@param thd The client thread that executed the current statement.
950+
@param trx_scope The transaction scope to look into.
951+
952+
@retval the number of engines which have actual changes.
953+
*/
954+
int check_trx_rw_engines(THD *thd, Transaction_ctx::enum_trx_scope trx_scope);
955+
936956
/**
937957
Check if at least one of transacaction and statement binlog caches contains
938958
an empty transaction, other one is empty or contains an empty transaction,

sql/log_event.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -7513,7 +7513,9 @@ bool XA_prepare_log_event::do_commit(THD *thd)
75137513
thd->lex->m_sql_cmd= new Sql_cmd_xa_commit(&xid, XA_ONE_PHASE);
75147514
error= thd->lex->m_sql_cmd->execute(thd);
75157515
}
7516-
error|= mysql_bin_log.gtid_end_transaction(thd);
7516+
7517+
if (!error)
7518+
error = mysql_bin_log.gtid_end_transaction(thd);
75177519

75187520
return error;
75197521
}

0 commit comments

Comments
 (0)