#At file:///export/home/x/mysql-trunk-bug11766752/ based on revid:magne.mahre@stripped
3310 Jon Olav Hauglid 2011-03-22
Bug #11766752 (former 59936)
Multiple XA assertions - transactional statement fuzzer
The problem was that the server for several statements did not check
the state of the current XA transaction (if any) before trying to
execute the statement. Specifically, you are not supposed to do
anything other than XA PREPARE / XA COMMIT ONE PHASE when in IDLE state,
or anything other than XA COMMIT / XA ROLLBACK in PREPARED state.
The assertions triggered by the testcase posted in the bug report,
was triggered by trying to access a table or rollback to a savepoint
when the current XA transaction was in PREPARED state.
This patch fixes the problem by reporting ER_XAER_RMFAIL error if
1) A statement is issued which would have caused data updates or
generated row events when XA state is IDLE or PREPARED.
2) SAVEPOINT or ROLLBACK TO SAVEPOINT is executed with an active
XA transaction. (Similar to what is already done for COMMIT/ROLLBACK)
Test case added to xa.test.
Also verified with the C testcase posted on the bug report.
modified:
mysql-test/r/xa.result
mysql-test/t/xa.test
sql/binlog.cc
sql/log_event.cc
sql/log_event.h
sql/sql_class.cc
sql/sql_parse.cc
sql/sql_parse.h
sql/transaction.cc
sql/transaction.h
=== modified file 'mysql-test/r/xa.result'
--- a/mysql-test/r/xa.result 2011-02-14 13:16:31 +0000
+++ b/mysql-test/r/xa.result 2011-03-22 13:56:52 +0000
@@ -166,3 +166,31 @@ ERROR XA102: XA_RBDEADLOCK: Transaction
XA END 'b';
XA ROLLBACK 'b';
DROP TABLE t1;
+#
+# Bug#11766752 59936: multiple xa assertions - transactional
+# statement fuzzer
+#
+CREATE TABLE t1 (a INT) engine=InnoDB;
+XA START 'a';
+INSERT INTO t1 VALUES (1);
+SAVEPOINT savep;
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state
+XA END 'a';
+SELECT * FROM t1;
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state
+INSERT INTO t1 VALUES (2);
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state
+SAVEPOINT savep;
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state
+XA PREPARE 'a';
+SELECT * FROM t1;
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state
+INSERT INTO t1 VALUES (2);
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state
+SAVEPOINT savep;
+ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state
+XA COMMIT 'a';
+SELECT * FROM t1;
+a
+1
+DROP TABLE t1;
=== modified file 'mysql-test/t/xa.test'
--- a/mysql-test/t/xa.test 2011-02-14 13:16:31 +0000
+++ b/mysql-test/t/xa.test 2011-03-22 13:56:52 +0000
@@ -287,6 +287,39 @@ DROP TABLE t1;
disconnect con1;
+--echo #
+--echo # Bug#11766752 59936: multiple xa assertions - transactional
+--echo # statement fuzzer
+--echo #
+
+CREATE TABLE t1 (a INT) engine=InnoDB;
+XA START 'a';
+INSERT INTO t1 VALUES (1);
+
+--error ER_XAER_RMFAIL
+SAVEPOINT savep;
+
+XA END 'a';
+--error ER_XAER_RMFAIL
+SELECT * FROM t1;
+--error ER_XAER_RMFAIL
+INSERT INTO t1 VALUES (2);
+--error ER_XAER_RMFAIL
+SAVEPOINT savep;
+
+XA PREPARE 'a';
+--error ER_XAER_RMFAIL
+SELECT * FROM t1; # used to cause InnoDB assert
+--error ER_XAER_RMFAIL
+INSERT INTO t1 VALUES (2); # used to cause InnoDB assert
+--error ER_XAER_RMFAIL
+SAVEPOINT savep;
+
+XA COMMIT 'a';
+SELECT * FROM t1;
+DROP TABLE t1;
+
+
# Wait till all disconnects are completed
--source include/wait_until_count_sessions.inc
=== modified file 'sql/binlog.cc'
--- a/sql/binlog.cc 2011-03-14 17:55:26 +0000
+++ b/sql/binlog.cc 2011-03-22 13:56:52 +0000
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
+/* Copyright (c) 2009, 2011 Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
#include "log.h"
#include "binlog.h"
#include "log_event.h"
+#include "sql_parse.h"
#include "rpl_filter.h"
#include "rpl_rli.h"
#include "sql_plugin.h"
@@ -5184,7 +5185,7 @@ int THD::decide_logging_format(TABLE_LIS
my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE), MYF(0));
}
else if (variables.binlog_format == BINLOG_FORMAT_ROW &&
- sqlcom_can_generate_row_events(this))
+ can_generate_row_events(lex->sql_command))
{
/*
2. Error: Cannot modify table that uses a storage engine
@@ -5223,7 +5224,7 @@ int THD::decide_logging_format(TABLE_LIS
my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_MODE), MYF(0));
}
else if ((flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0 &&
- sqlcom_can_generate_row_events(this))
+ can_generate_row_events(lex->sql_command))
{
/*
5. Error: Cannot modify table that uses a storage engine
=== modified file 'sql/log_event.cc'
--- a/sql/log_event.cc 2011-03-14 17:09:16 +0000
+++ b/sql/log_event.cc 2011-03-22 13:56:52 +0000
@@ -2824,7 +2824,7 @@ Query_log_event::Query_log_event(THD* th
use_cache= trx_cache= TRUE;
break;
default:
- use_cache= sqlcom_can_generate_row_events(thd);
+ use_cache= can_generate_row_events(thd->lex->sql_command);
break;
}
=== modified file 'sql/log_event.h'
--- a/sql/log_event.h 2011-03-14 17:55:26 +0000
+++ b/sql/log_event.h 2011-03-22 13:56:52 +0000
@@ -4213,7 +4213,6 @@ private:
int append_query_string(const CHARSET_INFO *csinfo,
String const *from, String *to);
-bool sqlcom_can_generate_row_events(const THD *thd);
void handle_rows_query_log_event(Log_event *ev, Relay_log_info *rli);
bool event_checksum_test(uchar *buf, ulong event_len, uint8 alg);
=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc 2011-03-09 20:54:55 +0000
+++ b/sql/sql_class.cc 2011-03-22 13:56:52 +0000
@@ -41,7 +41,6 @@
#include "rpl_record.h"
#include "rpl_slave.h"
#include <my_bitmap.h>
-#include "log_event.h"
#include "sql_audit.h"
#include <m_ctype.h>
#include <sys/stat.h>
@@ -3327,7 +3326,7 @@ extern "C" bool thd_binlog_filter_ok(con
extern "C" bool thd_sqlcom_can_generate_row_events(const MYSQL_THD thd)
{
- return sqlcom_can_generate_row_events(thd);
+ return can_generate_row_events(thd->lex->sql_command);
}
#ifndef EMBEDDED_LIBRARY
=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc 2011-03-11 09:35:38 +0000
+++ b/sql/sql_parse.cc 2011-03-22 13:56:52 +0000
@@ -447,10 +447,11 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS;
}
-bool sqlcom_can_generate_row_events(const THD *thd)
+
+bool can_generate_row_events(enum enum_sql_command command)
{
- return (sql_command_flags[thd->lex->sql_command] &
- CF_CAN_GENERATE_ROW_EVENTS);
+ DBUG_ASSERT(command >= 0 && command <= SQLCOM_END);
+ return (sql_command_flags[command] & CF_CAN_GENERATE_ROW_EVENTS) != 0;
}
bool is_update_query(enum enum_sql_command command)
@@ -1855,6 +1856,10 @@ mysql_execute_command(THD *thd)
context.resolve_in_table_list_only(select_lex->
table_list.first);
+ /* Check if the command can be executed in the current XA state (if any). */
+ if (trans_xa_check_state(thd))
+ DBUG_RETURN(true);
+
/*
Reset warning count for each query that uses tables
A better approach would be to reset this for any commands
=== modified file 'sql/sql_parse.h'
--- a/sql/sql_parse.h 2011-03-09 20:54:55 +0000
+++ b/sql/sql_parse.h 2011-03-22 13:56:52 +0000
@@ -79,6 +79,7 @@ bool check_host_name(LEX_STRING *str);
bool check_identifier_name(LEX_STRING *str, uint max_char_length,
uint err_code, const char *param_for_err_msg);
bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length);
+bool can_generate_row_events(enum enum_sql_command command);
bool is_update_query(enum enum_sql_command command);
bool is_log_table_write_query(enum enum_sql_command command);
bool is_rpl_info_table_write_query(enum enum_sql_command command);
=== modified file 'sql/transaction.cc'
--- a/sql/transaction.cc 2011-02-14 14:15:28 +0000
+++ b/sql/transaction.cc 2011-03-22 13:56:52 +0000
@@ -21,6 +21,7 @@
#include "sql_priv.h"
#include "transaction.h"
#include "rpl_handler.h"
+#include "sql_parse.h"
#include "debug_sync.h" // DEBUG_SYNC
/* Conditions under which the transaction state must not change. */
@@ -362,6 +363,13 @@ bool trans_savepoint(THD *thd, LEX_STRIN
!opt_using_transactions)
DBUG_RETURN(FALSE);
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ if (xa_state != XA_NOTR)
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(TRUE);
+ }
+
sv= find_savepoint(thd, name);
if (*sv) /* old savepoint of the same name exists */
@@ -435,6 +443,13 @@ bool trans_rollback_to_savepoint(THD *th
DBUG_RETURN(TRUE);
}
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ if (xa_state != XA_NOTR)
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ DBUG_RETURN(TRUE);
+ }
+
if (ha_rollback_to_savepoint(thd, sv))
res= TRUE;
else if (((thd->variables.option_bits & OPTION_KEEP_LOG) ||
@@ -743,3 +758,26 @@ bool trans_xa_rollback(THD *thd)
DBUG_RETURN(res);
}
+
+
+/**
+ Check if the current command can be safely executed in the current XA
+ state (if any). If not, report ER_XAER_RMFAIL.
+
+ The check is performed for XA_IDLE and XA_PREPARED states.
+ A command is unsafe if it reads or writes data. Determined using the
+ CF_CHANGES_DATA and CF_CAN_GENERATE_ROW_EVENTS command flags.
+*/
+
+bool trans_xa_check_state(THD *thd)
+{
+ enum xa_states xa_state= thd->transaction.xid_state.xa_state;
+ if ((xa_state == XA_IDLE || xa_state == XA_PREPARED) &&
+ (is_update_query(thd->lex->sql_command) ||
+ can_generate_row_events(thd->lex->sql_command)))
+ {
+ my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
+ return true;
+ }
+ return false;
+}
=== modified file 'sql/transaction.h'
--- a/sql/transaction.h 2010-07-02 02:58:51 +0000
+++ b/sql/transaction.h 2011-03-22 13:56:52 +0000
@@ -1,4 +1,4 @@
-/* Copyright (C) 2008 Sun/MySQL
+/* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -43,4 +43,15 @@ bool trans_xa_prepare(THD *thd);
bool trans_xa_commit(THD *thd);
bool trans_xa_rollback(THD *thd);
+/**
+ Check if the current command can be safely executed in the current XA
+ state (if any). If not, report ER_XAER_RMFAIL.
+
+ @param thd Thread handler
+
+ @retval false Command can safely be executed.
+ @retval true Command can not be executed, ER_XAER_RMFAIL reported.
+*/
+bool trans_xa_check_state(THD *thd);
+
#endif /* TRANSACTION_H */
Attachment: [text/bzr-bundle] bzr/jon.hauglid@oracle.com-20110322135652-c6w92a0r9lr69vbk.bundle
| Thread |
|---|
| • bzr commit into mysql-trunk branch (jon.hauglid:3310) Bug#11766752 | Jon Olav Hauglid | 22 Mar |