Skip to content

Commit

Permalink
Bug#20901025: SLAVE ASSERTION IN UNPACK_ROW WITH ROLLBACK TO
Browse files Browse the repository at this point in the history
SAVEPOINT IN ERROR HANDLER

Problem:
=======

When issuing a ROLLBACK TO SAVEPOINT within an error
handler, the slave asserts as follows:

mysqlcom-pro-5.6.24/sql/rpl_record.cc:246:
int unpack_row(const Relay_log_info, TABLE, uint,
const uchar, const MY_BITMAP, const uchar, ulong, const
uchar): Assertion `table_found' failed.

Analysis:
========

The above assert is caused in two cases.

CASE 1:
SAVEPOINT and ROLLBACK TO SAVEPOINT are inside a trigger and
followed by other DML statements.

SAVEPOINT, ROLLBACK TO SAVEPOINT statements are logged into
binary log as Query_log_events.
Query_log_event::do_apply_event() wipes out table map.
Events that follow the above Query_log_event will not find
table map event and it results in the above assertion.

CASE 2:

SAVEPOINT and ROLLBACK TO SAVEPOINT are inside a trigger and
and they does not follow with any DML statement.

In the above scenario On master during execution of
savepoint inside the trigger
"mysql_bin_log.write_event(&qinfo)" is called, which intern
invokes thd->binlog_flush_pending_rows_event() call with
"end_stmt" = false. This causes the peding event to be flushed
to the IO_CACHE without a STMT_END_F flag. Since it does't
follow with any DML stements no proper clean up is done. i.e
table maps are not cleared this causes the next query that
follows to be logged as part of query1 without its own table
map event resulting in same assert as mentioned in the bug.

Fix:
====

When a SAVEPOINT is executed inside a stored
function/trigger we force the pending event to be flushed
with a STMT_END_F flag and clear the table maps as well to
ensure that following DMLs will have a clean state to start
with. In the same way when we are rolling back to a
SAVEPOINT we should not only set the position back to
SAVEPOINT but also we must ensure that the same clean state
is preserved. In order to do that we clean the table maps
during rolling back to savepoint.
  • Loading branch information
Sujatha Sivakumar committed Aug 28, 2015
1 parent 854f926 commit 69d4e72
Show file tree
Hide file tree
Showing 6 changed files with 1,273 additions and 3 deletions.
327 changes: 327 additions & 0 deletions mysql-test/extra/rpl_tests/rpl_rollback_to_savepoint.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
###############################################################################
# Bug#76727: SLAVE ASSERTION IN UNPACK_ROW WITH ROLLBACK TO
# SAVEPOINT IN ERROR HANDLER
#
# Problem:
# ========
# "SAVEPOINT", "ROLLBACK TO savepoint" wipe out table map on slave during
# execution binary log events. For trigger the map is written to binary log once
# for all trigger body and if trigger contains "SAVEPOINT" or
# "ROLLBACK TO savepoint" statements any trigger's events after these
# statements will not have table map. This results in an assert on slave.
#
# Test:
# =====
# Test case 1:
# Create a trigger with exception handler which rollsback to a savepoint.
# Test proves that there will not be any assert during execution of rolling
# back to savepoint.
#
# Test case 2:
# Create a trigger which calls a procedure which in turn calls an exception
# handler which rollsback to a savepoint. Prove that it doesn't cause any
# assertion during execution.
#
# Test case 3:
# Create a simple trigger which creates a SAVEPOINT and ROLLSBACK to savepoint
# and doesn't follow with any other DML statement. Prove that it doesn't cause
# any assertion during execution.
#
# Test case 4:
# Create a trigger with SAVEPOINT and follows with a DML without ROLLBACK TO
# savepoint. Ensure that data is replicated properly.
#
# Test case 5:
# Create a trigger with SAVEPOINT and it does nothing. Do few DMLS following
# the trigger ensure that the data is replicated properly
#
# Test case 6:
# Create a stored function which creates a SAVEPOINT and ROLLSBACK to
# savepoint. Do few inserts following the stored function call and ensure that
# no assert is generated on slave and all the rows are replicated to slave.
#
# Test case 7:
# Create a stored function which creates a SAVEPOINT alone and follows with
# DMLS without ROLLBACK TO savepoint. Ensure that data is replicated properly.
#
# Test case 8:
# Create a stored function which has SAVEPOINT inside it and does noting. It
# should follow with other DMLs. Ensure that data is replicated properly.
###############################################################################
--source include/have_innodb.inc
--source include/master-slave.inc

--echo #Test case 1:
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY);
CREATE TABLE t2 (f1 INTEGER PRIMARY KEY);
CREATE TABLE t3 (f1 INTEGER PRIMARY KEY);
DELIMITER |;

CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK TO event_logging_1;
INSERT t3 VALUES (1);
END;

SAVEPOINT event_logging_1;

INSERT INTO t2 VALUES (1);

RELEASE SAVEPOINT event_logging_1;

END|
DELIMITER ;|

INSERT INTO t2 VALUES (1);
INSERT INTO t1 VALUES (1);

--source include/show_binlog_events.inc

--sync_slave_with_master

--source include/rpl_connection_master.inc

DROP TRIGGER tr1;
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;

--echo # Test case 2:

DELIMITER |;

CREATE PROCEDURE p1()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK TO event_logging_2;
INSERT t3 VALUES (3);
END;

SAVEPOINT event_logging_2;

INSERT INTO t2 VALUES (1);

RELEASE SAVEPOINT event_logging_2;
END|

CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW CALL p1()|

DELIMITER ;|

INSERT INTO t2 VALUES (1);
INSERT INTO t1 VALUES (1);

--source include/show_binlog_events.inc

--sync_slave_with_master

--source include/rpl_connection_master.inc

DROP TABLE t1;
DROP TABLE t2;
DROP TABLE t3;

DROP PROCEDURE p1;

--echo # Test case 3:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc

CREATE TABLE t (f1 int(10) unsigned NOT NULL, PRIMARY KEY (f1)) ENGINE=InnoDB;

--delimiter |
CREATE TRIGGER t_insert_trig AFTER INSERT ON t
FOR EACH ROW
BEGIN

SAVEPOINT savepoint_1;
ROLLBACK TO savepoint_1;

END |
--delimiter ;

INSERT INTO t VALUES (2);
INSERT INTO t VALUES (3);

--source include/show_binlog_events.inc

SELECT * FROM t;

--source include/sync_slave_sql_with_master.inc

SELECT * FROM t;

--source include/rpl_connection_master.inc
DROP TABLE t;

--echo # Test case 4:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;
CREATE TABLE t1 (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;

--delimiter |
CREATE TRIGGER t_insert_trig BEFORE INSERT ON t
FOR EACH ROW
BEGIN

SAVEPOINT savepoint_1;
INSERT INTO t1 VALUES (5);
END |
--delimiter ;

INSERT INTO t VALUES (2), (3);
INSERT INTO t1 VALUES (30);
--source include/show_binlog_events.inc

SELECT * FROM t;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc

SELECT * FROM t;
SELECT * FROM t1;

--source include/rpl_connection_master.inc
DROP TABLE t;
DROP TABLE t1;

--echo # Test case 5:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;
CREATE TABLE t1 (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;

--delimiter |
CREATE TRIGGER t_insert_trig BEFORE INSERT ON t
FOR EACH ROW
BEGIN

SAVEPOINT savepoint_1;
END |

--delimiter ;

INSERT INTO t VALUES (2), (3);
INSERT INTO t1 VALUES (30);
--source include/show_binlog_events.inc

SELECT * FROM t;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc

SELECT * FROM t;
SELECT * FROM t1;

--source include/rpl_connection_master.inc
DROP TABLE t;
DROP TABLE t1;

--echo # Test case 6:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t1 (f1 INTEGER ) ENGINE=INNODB;
CREATE TABLE t2 (f1 INTEGER ) ENGINE=INNODB;

--delimiter |

CREATE FUNCTION f1() RETURNS INT
BEGIN
SAVEPOINT event_logging_2;

INSERT INTO t1 VALUES (1);

ROLLBACK TO event_logging_2;
RETURN 0;
END|

--delimiter ;

BEGIN;
INSERT INTO t2 VALUES (1), (f1()), (2), (4);
COMMIT;
INSERT INTO t2 VALUES (10);
--source include/show_binlog_events.inc

--source include/rpl_connection_master.inc
SELECT * FROM t2;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t2;
SELECT * FROM t1;

--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP TABLE t2;
DROP FUNCTION f1;

--echo # Test case 7:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t1 (f1 INTEGER ) ENGINE=INNODB;
CREATE TABLE t2 (f1 INTEGER ) ENGINE=INNODB;

--delimiter |

CREATE FUNCTION f1() RETURNS INT
BEGIN
SAVEPOINT event_logging_2;

INSERT INTO t1 VALUES (1);

RETURN 0;
END|

--delimiter ;

BEGIN;
INSERT INTO t2 VALUES (1), (f1()), (2), (4);
COMMIT;
INSERT INTO t2 VALUES (10);
--source include/show_binlog_events.inc

--source include/rpl_connection_master.inc
SELECT * FROM t2;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t2;
SELECT * FROM t1;

--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP TABLE t2;
DROP FUNCTION f1;

--echo # Test case 8:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t1 (f1 INTEGER ) ENGINE=INNODB;

--delimiter |

CREATE FUNCTION f1() RETURNS INT
BEGIN
SAVEPOINT event_logging_2;
RETURN 0;
END|

--delimiter ;

BEGIN;
INSERT INTO t1 VALUES (1), (f1()), (2), (4);
COMMIT;
INSERT INTO t1 VALUES (10);
--source include/show_binlog_events.inc

--source include/rpl_connection_master.inc
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t1;

--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP FUNCTION f1;

--source include/rpl_end.inc
Loading

0 comments on commit 69d4e72

Please sign in to comment.