Hi Alfranio,
Nice work, thank you!
This bug is targeted 5.0+, which means it should go to 5.0 too, so
please recommit this against 5.0-bugteam, patch approved after recommit.
Alfranio Correia wrote:
> #At
> file:///home/acorreia/workspace.sun/repository.mysql/bzrwork/bug-43929/mysql-5.1-bugteam/
> based on revid:bjorn.munch@stripped
>
> 2912 Alfranio Correia 2009-06-06
> BUG#43929 binlog corruption when max_binlog_cache_size is exceeded
>
> Large transactions and statements may corrupt the binary log if the size of
> the
> cache, which is set by the max_binlog_cache_size, is not enough to store the
> the changes.
>
> In a nutshell, to fix the bug, we save the position of the next character in
> the
> cache before starting processing a statement. If there is a problem, we simply
> restore the position thus removing any effect of the statement from the cache.
> Unfortunately, to avoid corrupting the binary log, we may end up loosing
> changes
> on non-transactional tables if they do not fit in the cache. In such cases, we
> store an Incident_log_event in order to stop the slave and alert users that
> some
> changes were not logged.
>
> Precisely, for every non-transactional changes that do not fit into the cache,
> we do the following:
> a) the statement is *not* logged
> b) an incident event is logged after committing/rolling back the
> transaction,
> if any. Note that if a failure happens before writing the incident event to
> the binary log, the slave will not stop and the master will not have
> reported
> any error.
> c) its respective statement gives an error
>
> For transactional changes that do not fit into the cache, we do the following:
> a) the statement is *not* logged
> b) its respective statement gives an error
>
> To work properly, this patch requires two additional things. Firstly, callers
> to
> MYSQL_BIN_LOG::write and THD::binlog_query must handle any error returned and
> take the appropriate actions such as undoing the effects of a statement. We
> already changed some calls in the sql_insert.cc, sql_update.cc and
> sql_insert.cc
> modules but the remaining calls spread all over the code should be handled in
> BUG#37148. Secondly, statements must be either classified as DDL or DML
> because
> DDLs that do not get into the cache must generate an incident event since they
> cannot be rolled back.
>
> added:
> mysql-test/suite/rpl/r/rpl_binlog_max_cache_size.result
> mysql-test/suite/rpl/t/rpl_binlog_max_cache_size-master.opt
> mysql-test/suite/rpl/t/rpl_binlog_max_cache_size.test
> modified:
> mysql-test/include/commit.inc
> mysql-test/r/commit_1innodb.result
> sql/log.cc
> sql/log.h
> sql/sql_delete.cc
> sql/sql_insert.cc
> sql/sql_update.cc
> === modified file 'mysql-test/include/commit.inc'
> --- a/mysql-test/include/commit.inc 2009-01-23 12:22:05 +0000
> +++ b/mysql-test/include/commit.inc 2009-06-06 13:43:02 +0000
> @@ -669,13 +669,9 @@ call p_verify_status_increment(1, 0, 1,
> insert t1 set a=3;
> call p_verify_status_increment(2, 2, 2, 2);
> savepoint a;
> -call p_verify_status_increment(0, 0, 0, 0);
> +call p_verify_status_increment(1, 0, 0, 0);
> insert t1 set a=4;
> ---echo # Binlog does not register itself this time for other than the 1st
> ---echo # statement of the transaction with MIXED/STATEMENT binlog_format.
> ---echo # It needs registering with the ROW format. Therefore 1,0,2,2 are
> ---echo # the correct arguments to this test after bug#40221 fixed.
> -call p_verify_status_increment(1, 0, 2, 2);
> +call p_verify_status_increment(2, 2, 2, 2);
> release savepoint a;
> rollback;
> call p_verify_status_increment(0, 0, 0, 0);
>
> === modified file 'mysql-test/r/commit_1innodb.result'
> --- a/mysql-test/r/commit_1innodb.result 2009-02-10 14:44:58 +0000
> +++ b/mysql-test/r/commit_1innodb.result 2009-06-06 13:43:02 +0000
> @@ -766,15 +766,11 @@ call p_verify_status_increment(2, 2, 2,
> SUCCESS
>
> savepoint a;
> -call p_verify_status_increment(0, 0, 0, 0);
> +call p_verify_status_increment(1, 0, 0, 0);
> SUCCESS
>
> insert t1 set a=4;
> -# Binlog does not register itself this time for other than the 1st
> -# statement of the transaction with MIXED/STATEMENT binlog_format.
> -# It needs registering with the ROW format. Therefore 1,0,2,2 are
> -# the correct arguments to this test after bug#40221 fixed.
> -call p_verify_status_increment(1, 0, 2, 2);
> +call p_verify_status_increment(2, 2, 2, 2);
> SUCCESS
>
> release savepoint a;
>
> === added file 'mysql-test/suite/rpl/r/rpl_binlog_max_cache_size.result'
> --- a/mysql-test/suite/rpl/r/rpl_binlog_max_cache_size.result 1970-01-01 00:00:00
> +0000
> +++ b/mysql-test/suite/rpl/r/rpl_binlog_max_cache_size.result 2009-06-06 13:43:02
> +0000
> @@ -0,0 +1,135 @@
> +stop slave;
> +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
> +reset master;
> +reset slave;
> +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
> +start slave;
> +CREATE TABLE t1(a INT PRIMARY KEY, data VARCHAR(30000)) ENGINE=Innodb;
> +CREATE TABLE t2(a INT PRIMARY KEY, data VARCHAR(30000)) ENGINE=MyIsam;
> +CREATE TABLE t3(a INT PRIMARY KEY, data VARCHAR(30000)) ENGINE=Innodb;
>
> +########################################################################################
> +# 1 - SINGLE STATEMENT
>
> +########################################################################################
> +*** Single statment on transactional table ***
> +Got one of the listed errors
> +*** Single statment on non-transactional table ***
> +*** After WL#2687 the difference between STATEMENT/MIXED and ROW will not exist.
> ***
> +Got one of the listed errors
> +*** Single statment on both transactional and non-transactional tables. ***
> +*** After WL#2687 we will be able to change the order of the tables. ***
> +Got one of the listed errors
> +SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
> +START SLAVE SQL_THREAD;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +source include/diff_master_slave.inc;
>
> +########################################################################################
> +# 3 - BEGIN - COMMIT
>
> +########################################################################################
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +COMMIT;
> +source include/diff_master_slave.inc;
>
> +########################################################################################
> +# 4 - BEGIN - ROLLBACK
>
> +########################################################################################
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +ROLLBACK;
> +Warnings:
> +Warning 1196 Some non-transactional changed tables couldn't be rolled back
> +source include/diff_master_slave.inc;
>
> +########################################################################################
> +# 5 - PROCEDURE
>
> +########################################################################################
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +CREATE PROCEDURE p1(pd VARCHAR(30000))
> +BEGIN
> +INSERT INTO t1 (a, data) VALUES (1, pd);
> +INSERT INTO t1 (a, data) VALUES (2, pd);
> +INSERT INTO t1 (a, data) VALUES (3, pd);
> +INSERT INTO t1 (a, data) VALUES (4, pd);
> +INSERT INTO t1 (a, data) VALUES (5, 's');
> +END//
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t1;
> +BEGIN;
> +Got one of the listed errors
> +COMMIT;
> +TRUNCATE TABLE t1;
> +BEGIN;
> +Got one of the listed errors
> +ROLLBACK;
> +source include/diff_master_slave.inc;
>
> +########################################################################################
> +# 6 - XID
>
> +########################################################################################
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +ROLLBACK TO sv;
> +Warnings:
> +Warning 1196 Some non-transactional changed tables couldn't be rolled back
> +COMMIT;
> +source include/diff_master_slave.inc;
>
> +########################################################################################
> +# 7 - NON-TRANS TABLE
>
> +########################################################################################
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +BEGIN;
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +Got one of the listed errors
> +COMMIT;
> +BEGIN;
> +Got one of the listed errors
> +COMMIT;
>
> +########################################################################################
> +# CLEAN
>
> +########################################################################################
> +DROP TABLE t1;
> +DROP TABLE t2;
> +DROP TABLE t3;
> +DROP TABLE IF EXISTS t4;
> +DROP TABLE IF EXISTS t5;
> +DROP TABLE IF EXISTS t6;
> +DROP PROCEDURE p1;
> +DROP TABLE t1;
> +DROP TABLE t2;
> +DROP TABLE t3;
> +DROP TABLE IF EXISTS t4;
> +DROP TABLE IF EXISTS t5;
> +DROP TABLE IF EXISTS t6;
> +DROP PROCEDURE p1;
>
> === added file 'mysql-test/suite/rpl/t/rpl_binlog_max_cache_size-master.opt'
> --- a/mysql-test/suite/rpl/t/rpl_binlog_max_cache_size-master.opt 1970-01-01 00:00:00
> +0000
> +++ b/mysql-test/suite/rpl/t/rpl_binlog_max_cache_size-master.opt 2009-06-06 13:43:02
> +0000
> @@ -0,0 +1 @@
> +--binlog_cache_size=4096 --max_binlog_cache_size=7680
>
> === added file 'mysql-test/suite/rpl/t/rpl_binlog_max_cache_size.test'
> --- a/mysql-test/suite/rpl/t/rpl_binlog_max_cache_size.test 1970-01-01 00:00:00
> +0000
> +++ b/mysql-test/suite/rpl/t/rpl_binlog_max_cache_size.test 2009-06-06 13:43:02
> +0000
> @@ -0,0 +1,395 @@
>
> +########################################################################################
> +# This test verifies if the binlog is not corrupted when the cache buffer is not
> +# big enough to accommodate the changes and is divided in five steps:
> +#
> +# 1 - Single Statements:
> +# 1.1 - Single statement on transactional table.
> +# 1.2 - Single statement on non-transactional table.
> +# 1.3 - Single statement on both transactional and non-transactional tables.
> +# In both 1.2 and 1.3, an incident event is logged to notify the user that the
> +# master and slave are diverging.
> +#
> +# 2 - Transactions ended by an implicit commit.
> +#
> +# 3 - Transactions ended by a COMMIT.
> +#
> +# 4 - Transactions ended by a ROLLBACK.
> +#
> +# 5 - Transactions with a failing statement that updates a non-transactional
> +# table. In this case, a failure means that the statement does not get into
> +# the cache and an incident event is logged to notify the user that the master
> +# and slave are diverging.
> +#
>
> +########################################################################################
> +
>
> +########################################################################################
> +# Configuring the environment
>
> +########################################################################################
> +--source include/have_innodb.inc
> +--source include/master-slave.inc
> +--source include/not_embedded.inc
> +--source include/not_windows.inc
> +
> +CREATE TABLE t1(a INT PRIMARY KEY, data VARCHAR(30000)) ENGINE=Innodb;
> +CREATE TABLE t2(a INT PRIMARY KEY, data VARCHAR(30000)) ENGINE=MyIsam;
> +CREATE TABLE t3(a INT PRIMARY KEY, data VARCHAR(30000)) ENGINE=Innodb;
> +
> +let $data = `select concat('"', repeat('a',2000), '"')`;
> +
> +--echo
> ########################################################################################
> +--echo # 1 - SINGLE STATEMENT
> +--echo
> ########################################################################################
> +
> +connection master;
> +
> +--echo *** Single statment on transactional table ***
> +--disable_query_log
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +eval INSERT INTO t1 (a, data) VALUES (1,
> + CONCAT($data, $data, $data, $data, $data));
> +--enable_query_log
> +
> +--echo *** Single statment on non-transactional table ***
> +--echo *** After WL#2687 the difference between STATEMENT/MIXED and ROW will not
> exist. ***
> +--disable_query_log
> +--disable_warnings
> +if (`SELECT @@binlog_format = 'STATEMENT' || @@binlog_format = 'MIXED'`)
> +{
> + eval INSERT INTO t2 (a, data) VALUES (2,
> + CONCAT($data, $data, $data, $data, $data, $data));
> + --echo Got one of the listed errors
> +}
> +if (`SELECT @@binlog_format = 'ROW'`)
> +{
> + --error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> + eval INSERT INTO t2 (a, data) VALUES (2,
> + CONCAT($data, $data, $data, $data, $data, $data));
> +
> + connection slave;
> + --source include/wait_for_slave_sql_to_stop.inc
> + SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
> + START SLAVE SQL_THREAD;
> + --source include/wait_for_slave_sql_to_start.inc
> +}
> +--enable_warnings
> +--enable_query_log
> +
> +connection master;
> +
> +--disable_query_log
> +eval INSERT INTO t1 (a, data) VALUES (3, $data);
> +eval INSERT INTO t1 (a, data) VALUES (4, $data);
> +eval INSERT INTO t1 (a, data) VALUES (5, $data);
> +eval INSERT INTO t2 (a, data) VALUES (3, $data);
> +eval INSERT INTO t2 (a, data) VALUES (4, $data);
> +eval INSERT INTO t2 (a, data) VALUES (5, $data);
> +--enable_query_log
> +
> +--echo *** Single statment on both transactional and non-transactional tables. ***
> +--echo *** After WL#2687 we will be able to change the order of the tables. ***
> +--disable_query_log
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +eval UPDATE t2, t1 SET t2.data = CONCAT($data, $data, $data, $data),
> + t1.data = CONCAT($data, $data, $data, $data);
> +--enable_query_log
> +
> +connection slave;
> +--source include/wait_for_slave_sql_to_stop.inc
> +SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
> +START SLAVE SQL_THREAD;
> +--source include/wait_for_slave_sql_to_start.inc
> +
> +#--echo
> ########################################################################################
> +#--echo # 2 - BEGIN - IMPLICIT COMMIT by DDL
> +#--echo
> ########################################################################################
> +
> +connection master;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (1, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (2, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (3, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (4, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (5, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (6, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (7, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (8, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (9, 's');
> +--enable_query_log
> +
> +--disable_query_log
> +ALTER TABLE t3 ADD COLUMN d int;
> +--enable_query_log
> +
> +--disable_query_log
> +--eval INSERT INTO t2 (a, data) VALUES (10, $data);
> +--eval INSERT INTO t2 (a, data) VALUES (11, $data);
> +--eval INSERT INTO t2 (a, data) VALUES (12, $data);
> +--eval INSERT INTO t2 (a, data) VALUES (13, $data);
> +--enable_query_log
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (14, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (15, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (16, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (17, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (18, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (19, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (20, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (21, 's');
> +--enable_query_log
> +
> +if (`SELECT @@binlog_format = 'STATEMENT' || @@binlog_format = 'MIXED'`)
> +{
> + --disable_query_log
> + CREATE TABLE t4 SELECT * FROM t1;
> + --enable_query_log
> + --echo Got one of the listed errors
> +}
> +if (`SELECT @@binlog_format = 'ROW'`)
> +{
> + --disable_query_log
> + --error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> + CREATE TABLE t4 SELECT * FROM t1;
> + --enable_query_log
> +}
> +
> +--disable_query_log
> +--eval INSERT INTO t2 (a, data) VALUES (15, $data);
> +--enable_query_log
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (22, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (23, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (24, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (25, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (26, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (27, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (28, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (29, 's');
> +--enable_query_log
> +
> +--disable_query_log
> +CREATE TABLE t5 (a int);
> +--enable_query_log
> +
> +let $diff_statement= SELECT * FROM t1;
> +--source include/diff_master_slave.inc
> +
> +--echo
> ########################################################################################
> +--echo # 3 - BEGIN - COMMIT
> +--echo
> ########################################################################################
> +
> +connection master;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (1, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (2, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (3, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (4, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (5, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (6, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (7, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (8, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (9, 's');
> +--enable_query_log
> +COMMIT;
> +
> +let $diff_statement= SELECT * FROM t1;
> +--source include/diff_master_slave.inc
> +
> +--echo
> ########################################################################################
> +--echo # 4 - BEGIN - ROLLBACK
> +--echo
> ########################################################################################
> +
> +connection master;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (1, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (2, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (3, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (4, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (5, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (6, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (7, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (8, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (9, 's');
> +--enable_query_log
> +ROLLBACK;
> +
> +let $diff_statement= SELECT * FROM t1;
> +--source include/diff_master_slave.inc
> +
> +--echo
> ########################################################################################
> +--echo # 5 - PROCEDURE
> +--echo
> ########################################################################################
> +
> +connection master;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +
> +DELIMITER //;
> +
> +CREATE PROCEDURE p1(pd VARCHAR(30000))
> +BEGIN
> + INSERT INTO t1 (a, data) VALUES (1, pd);
> + INSERT INTO t1 (a, data) VALUES (2, pd);
> + INSERT INTO t1 (a, data) VALUES (3, pd);
> + INSERT INTO t1 (a, data) VALUES (4, pd);
> + INSERT INTO t1 (a, data) VALUES (5, 's');
> +END//
> +
> +DELIMITER ;//
> +
> +TRUNCATE TABLE t1;
> +
> +--disable_query_log
> +eval CALL p1($data);
> +--enable_query_log
> +
> +TRUNCATE TABLE t1;
> +
> +BEGIN;
> +--disable_query_log
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +eval CALL p1($data);
> +--enable_query_log
> +COMMIT;
> +
> +TRUNCATE TABLE t1;
> +
> +BEGIN;
> +--disable_query_log
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +eval CALL p1($data);
> +--enable_query_log
> +ROLLBACK;
> +
> +let $diff_statement= SELECT * FROM t1;
> +--source include/diff_master_slave.inc
> +
> +--echo
> ########################################################################################
> +--echo # 6 - XID
> +--echo
> ########################################################################################
> +
> +connection master;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (1, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (2, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (3, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (4, $data);
> +SAVEPOINT sv;
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (5, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (6, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (7, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (8, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (9, 's');
> +--enable_query_log
> +ROLLBACK TO sv;
> +COMMIT;
> +
> +let $diff_statement= SELECT * FROM t1;
> +--source include/diff_master_slave.inc
> +
> +--echo
> ########################################################################################
> +--echo # 7 - NON-TRANS TABLE
> +--echo
> ########################################################################################
> +
> +connection master;
> +TRUNCATE TABLE t1;
> +TRUNCATE TABLE t2;
> +TRUNCATE TABLE t3;
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (1, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (2, $data);
> +--eval INSERT INTO t2 (a, data) VALUES (3, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (4, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (5, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (6, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (7, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval UPDATE t2 SET data= CONCAT($data, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (8, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (9, 's');
> +--eval INSERT INTO t2 (a, data) VALUES (10, 's');
> +--eval INSERT INTO t1 (a, data) VALUES (11, 's');
> +--enable_query_log
> +COMMIT;
> +
> +BEGIN;
> +--disable_query_log
> +--eval INSERT INTO t1 (a, data) VALUES (15, $data);
> +--eval INSERT INTO t1 (a, data) VALUES (16, $data);
> +--eval INSERT INTO t2 (a, data) VALUES (17, $data);
> +--error ER_TRANS_CACHE_FULL, ER_ERROR_ON_WRITE
> +--eval INSERT INTO t1 (a, data) VALUES (18, $data);
> +--enable_query_log
> +COMMIT;
> +
> +connection slave;
> +--source include/wait_for_slave_sql_to_stop.inc
> +
> +--echo
> ########################################################################################
> +--echo # CLEAN
> +--echo
> ########################################################################################
> +
> +--disable_warnings
> +connection master;
> +DROP TABLE t1;
> +DROP TABLE t2;
> +DROP TABLE t3;
> +DROP TABLE IF EXISTS t4;
> +DROP TABLE IF EXISTS t5;
> +DROP TABLE IF EXISTS t6;
> +DROP PROCEDURE p1;
> +connection slave;
> +DROP TABLE t1;
> +DROP TABLE t2;
> +DROP TABLE t3;
> +DROP TABLE IF EXISTS t4;
> +DROP TABLE IF EXISTS t5;
> +DROP TABLE IF EXISTS t6;
> +DROP PROCEDURE p1;
> +--enable_warnings
>
> === modified file 'sql/log.cc'
> --- a/sql/log.cc 2009-04-09 15:25:25 +0000
> +++ b/sql/log.cc 2009-06-06 13:43:02 +0000
> @@ -153,7 +153,8 @@ private:
> class binlog_trx_data {
> public:
> binlog_trx_data()
> - : at_least_one_stmt(0), m_pending(0), before_stmt_pos(MY_OFF_T_UNDEF)
> + : at_least_one_stmt(0), incident(FALSE), m_pending(0),
> + before_stmt_pos(MY_OFF_T_UNDEF)
> {
> trans_log.end_of_file= max_binlog_cache_size;
> }
> @@ -184,6 +185,7 @@ public:
> delete pending();
> set_pending(0);
> reinit_io_cache(&trans_log, WRITE_CACHE, pos, 0, 0);
> + trans_log.end_of_file= max_binlog_cache_size;
> if (pos < before_stmt_pos)
> before_stmt_pos= MY_OFF_T_UNDEF;
>
> @@ -206,6 +208,7 @@ public:
> if (!empty())
> truncate(0);
> before_stmt_pos= MY_OFF_T_UNDEF;
> + incident= FALSE;
> trans_log.end_of_file= max_binlog_cache_size;
> DBUG_ASSERT(empty());
> }
> @@ -222,11 +225,22 @@ public:
>
> IO_CACHE trans_log; // The transaction cache
>
> + void set_incident(void)
> + {
> + incident= TRUE;
> + }
> +
> + bool has_incident(void)
> + {
> + return(incident);
> + }
> +
> /**
> Boolean that is true if there is at least one statement in the
> transaction cache.
> */
> bool at_least_one_stmt;
> + bool incident;
>
> private:
> /*
> @@ -1391,7 +1405,8 @@ binlog_end_trans(THD *thd, binlog_trx_da
> */
> if (end_ev != NULL)
> {
> - thd->binlog_flush_pending_rows_event(TRUE);
> + if (thd->binlog_flush_pending_rows_event(TRUE))
> + DBUG_RETURN(1);
> /*
> Doing a commit or a rollback including non-transactional tables,
> i.e., ending a transaction where we might write the transaction
> @@ -1402,7 +1417,8 @@ binlog_end_trans(THD *thd, binlog_trx_da
> were, we would have to ensure that we're not ending a statement
> inside a stored function.
> */
> - error= mysql_bin_log.write(thd, &trx_data->trans_log, end_ev);
> + error= mysql_bin_log.write(thd, &trx_data->trans_log, end_ev,
> + trx_data->has_incident());
> trx_data->reset();
>
> /*
> @@ -1428,7 +1444,11 @@ binlog_end_trans(THD *thd, binlog_trx_da
> */
> thd->binlog_remove_pending_rows_event(TRUE);
> if (all || !(thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT)))
> + {
> + if (trx_data->has_incident())
> + mysql_bin_log.write_incident(thd, TRUE);
> trx_data->reset();
> + }
> else // ...statement
> trx_data->truncate(trx_data->before_stmt_pos);
>
> @@ -1545,9 +1565,11 @@ static int binlog_rollback(handlerton *h
> YESNO(all),
> YESNO(thd->transaction.all.modified_non_trans_table),
> YESNO(thd->transaction.stmt.modified_non_trans_table)));
> - if (all && thd->transaction.all.modified_non_trans_table ||
> - !all && thd->transaction.stmt.modified_non_trans_table ||
> - (thd->options & OPTION_KEEP_LOG))
> + if ((all && thd->transaction.all.modified_non_trans_table) ||
> + (!all && thd->transaction.stmt.modified_non_trans_table &&
> + !mysql_bin_log.check_write_error(thd)) ||
> + ((thd->options & OPTION_KEEP_LOG) &&
> + !mysql_bin_log.check_write_error(thd)))
> {
> /*
> We write the transaction cache with a rollback last if we have
> @@ -1561,14 +1583,22 @@ static int binlog_rollback(handlerton *h
> qev.error_code= 0; // see comment in MYSQL_LOG::write(THD, IO_CACHE)
> error= binlog_end_trans(thd, trx_data, &qev, all);
> }
> - else if (all && !thd->transaction.all.modified_non_trans_table ||
> - !all && !thd->transaction.stmt.modified_non_trans_table)
> + else
> {
> /*
> - If we have modified only transactional tables, we can truncate
> - the transaction cache without writing anything to the binary
> - log.
> + We reach this point if either only transactional tables were modified or
> + the effect of a statement that did not get into the binlog needs to be
> + rolled back. In the latter case, if a statement changed non-transactional
> + tables or had the OPTION_KEEP_LOG associated, we write an incident event
> + to the binlog in order to stop slaves and notify users that some changes
> + on the master did not get into the binlog and slaves will be inconsistent.
> + On the other hand, if a statement is transactional, we just safely roll it
> + back.
> */
> + if ((thd->transaction.stmt.modified_non_trans_table ||
> + (thd->options & OPTION_KEEP_LOG)) &&
> + mysql_bin_log.check_write_error(thd))
> + trx_data->set_incident();
> error= binlog_end_trans(thd, trx_data, 0, all);
> }
> if (!all)
> @@ -1576,6 +1606,44 @@ static int binlog_rollback(handlerton *h
> DBUG_RETURN(error);
> }
>
> +void MYSQL_BIN_LOG::set_write_error(THD *thd)
> +{
> + DBUG_ENTER("MYSQL_BIN_LOG::set_write_error");
> +
> + write_error= 1;
> +
> + if (check_write_error(thd))
> + DBUG_VOID_RETURN;
> +
> + if (my_errno == EFBIG)
> + my_message(ER_TRANS_CACHE_FULL, ER(ER_TRANS_CACHE_FULL), MYF(MY_WME));
> + else
> + my_error(ER_ERROR_ON_WRITE, MYF(MY_WME), name, errno);
> +
> + DBUG_VOID_RETURN;
> +}
> +
> +bool MYSQL_BIN_LOG::check_write_error(THD *thd)
> +{
> + DBUG_ENTER("MYSQL_BIN_LOG::check_write_error");
> +
> + bool checked= FALSE;
> +
> + if (!thd->is_error())
> + DBUG_RETURN(checked);
> +
> + switch (thd->main_da.sql_errno())
> + {
> + case ER_TRANS_CACHE_FULL:
> + case ER_ERROR_ON_WRITE:
> + case ER_BINLOG_LOGGING_IMPOSSIBLE:
> + checked= TRUE;
> + break;
> + }
> +
> + DBUG_RETURN(checked);
> +}
> +
> /**
> @note
> How do we handle this (unlikely but legal) case:
> @@ -3851,6 +3919,7 @@ MYSQL_BIN_LOG::flush_and_set_pending_row
> if (pending->write(file))
> {
> pthread_mutex_unlock(&LOCK_log);
> + set_write_error(thd);
> DBUG_RETURN(1);
> }
>
> @@ -3925,7 +3994,8 @@ bool MYSQL_BIN_LOG::write(Log_event *eve
> */
> bool const end_stmt=
> thd->prelocked_mode && thd->lex->requires_prelocking();
> - thd->binlog_flush_pending_rows_event(end_stmt);
> + if (thd->binlog_flush_pending_rows_event(end_stmt))
> + DBUG_RETURN(error);
>
> pthread_mutex_lock(&LOCK_log);
>
> @@ -3976,8 +4046,7 @@ bool MYSQL_BIN_LOG::write(Log_event *eve
> DBUG_PRINT("info", ("Using trans_log: cache: %d, trans_log_pos: %lu",
> event_info->get_cache_stmt(),
> (ulong) trans_log_pos));
> - if (trans_log_pos == 0)
> - thd->binlog_start_trans_and_stmt();
> + thd->binlog_start_trans_and_stmt();
> file= trans_log;
> }
> /*
> @@ -4055,7 +4124,8 @@ bool MYSQL_BIN_LOG::write(Log_event *eve
> Write the SQL command
> */
>
> - if (event_info->write(file))
> + if (event_info->write(file) ||
> + DBUG_EVALUATE_IF("injecting_fault_writing", 1, 0))
> goto err;
>
> if (file == &log_file) // we are writing to the real log (disk)
> @@ -4069,13 +4139,7 @@ bool MYSQL_BIN_LOG::write(Log_event *eve
>
> err:
> if (error)
> - {
> - if (my_errno == EFBIG)
> - my_message(ER_TRANS_CACHE_FULL, ER(ER_TRANS_CACHE_FULL), MYF(0));
> - else
> - my_error(ER_ERROR_ON_WRITE, MYF(0), name, errno);
> - write_error=1;
> - }
> + set_write_error(thd);
> }
>
> if (event_info->flags & LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F)
> @@ -4327,6 +4391,29 @@ int MYSQL_BIN_LOG::write_cache(IO_CACHE
> return 0; // All OK
> }
>
> +bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock)
> +{
> + uint error= 0;
> + DBUG_ENTER("MYSQL_BIN_LOG::write_incident");
> + LEX_STRING const write_error_msg=
> + { C_STRING_WITH_LEN("error writing to the binary log") };
> + Incident incident= INCIDENT_LOST_EVENTS;
> + Incident_log_event ev(thd, incident, write_error_msg);
> + if (lock)
> + pthread_mutex_lock(&LOCK_log);
> + ev.write(&log_file);
> + if (lock)
> + {
> + if (!error && !(error= flush_and_sync()))
> + {
> + signal_update();
> + rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
> + }
> + pthread_mutex_unlock(&LOCK_log);
> + }
> + DBUG_RETURN(error);
> +}
> +
> /**
> Write a cached log entry to the binary log.
> - To support transaction over replication, we wrap the transaction
> @@ -4348,7 +4435,8 @@ int MYSQL_BIN_LOG::write_cache(IO_CACHE
> 'cache' needs to be reinitialized after this functions returns.
> */
>
> -bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event)
> +bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event,
> + bool incident)
> {
> DBUG_ENTER("MYSQL_BIN_LOG::write(THD *, IO_CACHE *, Log_event *)");
> VOID(pthread_mutex_lock(&LOCK_log));
> @@ -4408,6 +4496,10 @@ bool MYSQL_BIN_LOG::write(THD *thd, IO_C
>
> if (commit_event && commit_event->write(&log_file))
> goto err;
> +
> + if (incident && write_incident(thd, FALSE))
> + goto err;
> +
> if (flush_and_sync())
> goto err;
> DBUG_EXECUTE_IF("half_binlogged_transaction", abort(););
>
> === modified file 'sql/log.h'
> --- a/sql/log.h 2009-01-23 12:22:05 +0000
> +++ b/sql/log.h 2009-06-06 13:43:02 +0000
> @@ -356,9 +356,12 @@ public:
> void new_file();
>
> bool write(Log_event* event_info); // binary log write
> - bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event);
> + bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident);
> + bool write_incident(THD *thd, bool lock);
>
> int write_cache(IO_CACHE *cache, bool lock_log, bool flush_and_sync);
> + void set_write_error(THD *thd);
> + bool check_write_error(THD *thd);
>
> void start_union_events(THD *thd, query_id_t query_id_param);
> void stop_union_events(THD *thd);
>
> === modified file 'sql/sql_delete.cc'
> --- a/sql/sql_delete.cc 2009-05-15 13:25:29 +0000
> +++ b/sql/sql_delete.cc 2009-06-06 13:43:02 +0000
> @@ -404,7 +404,7 @@ cleanup:
> thd->query, thd->query_length,
> is_trans, FALSE, killed_status);
>
> - if (log_result && transactional_table)
> + if (log_result)
> {
> error=1;
> }
>
> === modified file 'sql/sql_insert.cc'
> --- a/sql/sql_insert.cc 2009-05-15 12:57:51 +0000
> +++ b/sql/sql_insert.cc 2009-06-06 13:43:02 +0000
> @@ -893,8 +893,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
> if (thd->binlog_query(THD::ROW_QUERY_TYPE,
> thd->query, thd->query_length,
> transactional_table, FALSE,
> - (error>0) ? thd->killed : THD::NOT_KILLED) &&
> - transactional_table)
> + (error>0) ? thd->killed : THD::NOT_KILLED))
> {
> error=1;
> }
>
> === modified file 'sql/sql_update.cc'
> --- a/sql/sql_update.cc 2009-05-15 13:03:22 +0000
> +++ b/sql/sql_update.cc 2009-06-06 13:43:02 +0000
> @@ -799,8 +799,7 @@ int mysql_update(THD *thd,
> thd->clear_error();
> if (thd->binlog_query(THD::ROW_QUERY_TYPE,
> thd->query, thd->query_length,
> - transactional_table, FALSE, killed_status) &&
> - transactional_table)
> + transactional_table, FALSE, killed_status))
> {
> error=1; // Rollback update
> }
> @@ -2079,8 +2078,7 @@ bool multi_update::send_eof()
> thd->clear_error();
> if (thd->binlog_query(THD::ROW_QUERY_TYPE,
> thd->query, thd->query_length,
> - transactional_tables, FALSE, killed_status) &&
> - trans_safe)
> + transactional_tables, FALSE, killed_status))
> {
> local_error= 1; // Rollback update
> }
>