Commit 555c12a5 authored by Brandon Nesterenko's avatar Brandon Nesterenko

MDEV-21087/MDEV-21433: ER_SLAVE_INCIDENT arrives at slave without failure specifics

Problem:
=======

This patch addresses two issues:

 1. An incident event can be incorrectly reported for transactions
which are rolled back successfully. That is, an incident event
should only be generated for failed “non-transactional transactions”
(i.e., those which modify non-transactional tables) because they
cannot be rolled back.

 2. When the mariadb slave (error) stops at receiving the incident
event there's no description of what led to it. Neither in the event
nor in the master's error log.

Solution:
========

Before reporting an incident event for a transaction, first validate
that it is “non-transactional” (i.e. cannot be safely rolled back).
To determine if a transaction is non-transactional,
  lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)
is used because it is set previously in
THD::decide_logging_format().

Additionally, when an incident event is written, write an error
message to the server’s error log to indicate the underlying issue.

Reviewed by:
===========
Andrei Elkin <andrei.elkin@mariadb.com>
parent 46ff6608
...@@ -38,6 +38,8 @@ DROP TRIGGER IF EXISTS tr2; ...@@ -38,6 +38,8 @@ DROP TRIGGER IF EXISTS tr2;
DROP VIEW IF EXISTS v1, v2; DROP VIEW IF EXISTS v1, v2;
enable_warnings; enable_warnings;
call mtr.add_suppression("Write to binary log failed: Error writing file*");
--echo # --echo #
--echo # Test injecting binlog write error when executing queries --echo # Test injecting binlog write error when executing queries
--echo # --echo #
......
call mtr.add_suppression("Write to binary log failed: Multi-statement transaction required more than .max_binlog_cache_size.*");
SET AUTOCOMMIT=0; SET AUTOCOMMIT=0;
CREATE TABLE t1 (a INT NOT NULL AUTO_INCREMENT, b TEXT, PRIMARY KEY(a)) ENGINE=InnoDB; CREATE TABLE t1 (a INT NOT NULL AUTO_INCREMENT, b TEXT, PRIMARY KEY(a)) ENGINE=InnoDB;
SELECT COUNT(*) FROM t1; SELECT COUNT(*) FROM t1;
......
...@@ -9,6 +9,7 @@ DROP PROCEDURE IF EXISTS p2; ...@@ -9,6 +9,7 @@ DROP PROCEDURE IF EXISTS p2;
DROP TRIGGER IF EXISTS tr1; DROP TRIGGER IF EXISTS tr1;
DROP TRIGGER IF EXISTS tr2; DROP TRIGGER IF EXISTS tr2;
DROP VIEW IF EXISTS v1, v2; DROP VIEW IF EXISTS v1, v2;
call mtr.add_suppression("Write to binary log failed: Error writing file*");
# #
# Test injecting binlog write error when executing queries # Test injecting binlog write error when executing queries
# #
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
--source include/have_innodb.inc --source include/have_innodb.inc
--source include/have_log_bin.inc --source include/have_log_bin.inc
--source include/have_binlog_format_row.inc --source include/have_binlog_format_row.inc
call mtr.add_suppression("Write to binary log failed: Multi-statement transaction required more than .max_binlog_cache_size.*");
SET AUTOCOMMIT=0; SET AUTOCOMMIT=0;
# Create 1st table # Create 1st table
......
...@@ -9,6 +9,7 @@ DROP PROCEDURE IF EXISTS p2; ...@@ -9,6 +9,7 @@ DROP PROCEDURE IF EXISTS p2;
DROP TRIGGER IF EXISTS tr1; DROP TRIGGER IF EXISTS tr1;
DROP TRIGGER IF EXISTS tr2; DROP TRIGGER IF EXISTS tr2;
DROP VIEW IF EXISTS v1, v2; DROP VIEW IF EXISTS v1, v2;
call mtr.add_suppression("Write to binary log failed: Error writing file*");
# #
# Test injecting binlog write error when executing queries # Test injecting binlog write error when executing queries
# #
......
include/master-slave.inc include/master-slave.inc
[connection master] [connection master]
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT"); call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
call mtr.add_suppression("Write to binary log failed: Multi-row statements required more than .max_binlog_stmt_cache_size");
call mtr.add_suppression("Write to binary log failed: Multi-statement transaction required more than .max_binlog_cache_size");
SET GLOBAL max_binlog_cache_size = 4096; SET GLOBAL max_binlog_cache_size = 4096;
SET GLOBAL binlog_cache_size = 4096; SET GLOBAL binlog_cache_size = 4096;
SET GLOBAL max_binlog_stmt_cache_size = 4096; SET GLOBAL max_binlog_stmt_cache_size = 4096;
......
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
# #
######################################################################################## ########################################################################################
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT"); call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
call mtr.add_suppression("Write to binary log failed: Multi-row statements required more than .max_binlog_stmt_cache_size");
call mtr.add_suppression("Write to binary log failed: Multi-statement transaction required more than .max_binlog_cache_size");
let $old_max_binlog_cache_size= query_get_value(SHOW VARIABLES LIKE "max_binlog_cache_size", Value, 1); let $old_max_binlog_cache_size= query_get_value(SHOW VARIABLES LIKE "max_binlog_cache_size", Value, 1);
let $old_binlog_cache_size= query_get_value(SHOW VARIABLES LIKE "binlog_cache_size", Value, 1); let $old_binlog_cache_size= query_get_value(SHOW VARIABLES LIKE "binlog_cache_size", Value, 1);
......
include/master-slave.inc include/master-slave.inc
[connection master] [connection master]
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT"); call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
call mtr.add_suppression("Write to binary log failed: Multi-row statements required more than .max_binlog_stmt_cache_size");
call mtr.add_suppression("Write to binary log failed: Multi-statement transaction required more than .max_binlog_cache_size");
SET GLOBAL max_binlog_cache_size = 4096; SET GLOBAL max_binlog_cache_size = 4096;
SET GLOBAL binlog_cache_size = 4096; SET GLOBAL binlog_cache_size = 4096;
SET GLOBAL max_binlog_stmt_cache_size = 4096; SET GLOBAL max_binlog_stmt_cache_size = 4096;
......
include/master-slave.inc include/master-slave.inc
[connection master] [connection master]
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT"); call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
call mtr.add_suppression("Write to binary log failed: Multi-row statements required more than .max_binlog_stmt_cache_size");
call mtr.add_suppression("Write to binary log failed: Multi-statement transaction required more than .max_binlog_cache_size");
SET GLOBAL max_binlog_cache_size = 4096; SET GLOBAL max_binlog_cache_size = 4096;
SET GLOBAL binlog_cache_size = 4096; SET GLOBAL binlog_cache_size = 4096;
SET GLOBAL max_binlog_stmt_cache_size = 4096; SET GLOBAL max_binlog_stmt_cache_size = 4096;
......
This diff is collapsed.
...@@ -6270,7 +6270,7 @@ static int write_locked_table_maps(THD *thd) ...@@ -6270,7 +6270,7 @@ static int write_locked_table_maps(THD *thd)
continue; continue;
TABLE **const end_ptr= lock->table + lock->table_count; TABLE **const end_ptr= lock->table + lock->table_count;
for (TABLE **table_ptr= lock->table ; for (TABLE **table_ptr= lock->table ;
table_ptr != end_ptr ; table_ptr != end_ptr ;
++table_ptr) ++table_ptr)
{ {
...@@ -6294,8 +6294,10 @@ static int write_locked_table_maps(THD *thd) ...@@ -6294,8 +6294,10 @@ static int write_locked_table_maps(THD *thd)
*/ */
bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE || bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
table->file->has_transactions(); table->file->has_transactions();
int const error= thd->binlog_write_table_map(table, has_trans,
&with_annotate); int const error=
thd->binlog_write_table_map(table, has_trans, &with_annotate);
/* /*
If an error occurs, it is the responsibility of the caller to If an error occurs, it is the responsibility of the caller to
roll back the transaction. roll back the transaction.
......
...@@ -2081,7 +2081,8 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) ...@@ -2081,7 +2081,8 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
error= binlog_commit_flush_stmt_cache(thd, all, cache_mngr); error= binlog_commit_flush_stmt_cache(thd, all, cache_mngr);
} }
if (cache_mngr->trx_cache.empty())
if (!cache_mngr->trx_cache.has_incident() && cache_mngr->trx_cache.empty())
{ {
/* /*
we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid() we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
...@@ -5713,17 +5714,23 @@ binlog_start_consistent_snapshot(handlerton *hton, THD *thd) ...@@ -5713,17 +5714,23 @@ binlog_start_consistent_snapshot(handlerton *hton, THD *thd)
} }
/** /**
This function writes a table map to the binary log. This function writes a table map to the binary log.
Note that in order to keep the signature uniform with related methods, Note that in order to keep the signature uniform with related methods,
we use a redundant parameter to indicate whether a transactional table we use a redundant parameter to indicate whether a transactional table
was changed or not. was changed or not.
If with_annotate != NULL and If with_annotate != NULL and
*with_annotate = TRUE write also Annotate_rows before the table map. *with_annotate = TRUE write also Annotate_rows before the table map.
If an error occurs while writing events and rollback is not possible, e.g.
due to the statement modifying a non-transactional table, an incident event
is logged.
@param table a pointer to the table. @param table a pointer to the table.
@param is_transactional @c true indicates a transactional table, @param is_transactional @c true indicates a transactional table,
otherwise @c false a non-transactional. otherwise @c false a non-transactional.
@param with_annotate @c true to write an annotate event before writing
the table_map event, @c false otherwise.
@return @return
nonzero if an error pops up when writing the table map event. nonzero if an error pops up when writing the table map event.
*/ */
...@@ -5739,7 +5746,7 @@ int THD::binlog_write_table_map(TABLE *table, bool is_transactional, ...@@ -5739,7 +5746,7 @@ int THD::binlog_write_table_map(TABLE *table, bool is_transactional,
/* Ensure that all events in a GTID group are in the same cache */ /* Ensure that all events in a GTID group are in the same cache */
if (variables.option_bits & OPTION_GTID_BEGIN) if (variables.option_bits & OPTION_GTID_BEGIN)
is_transactional= 1; is_transactional= 1;
/* Pre-conditions */ /* Pre-conditions */
DBUG_ASSERT(is_current_stmt_binlog_format_row()); DBUG_ASSERT(is_current_stmt_binlog_format_row());
DBUG_ASSERT(WSREP_EMULATE_BINLOG(this) || mysql_bin_log.is_open()); DBUG_ASSERT(WSREP_EMULATE_BINLOG(this) || mysql_bin_log.is_open());
...@@ -5764,17 +5771,35 @@ int THD::binlog_write_table_map(TABLE *table, bool is_transactional, ...@@ -5764,17 +5771,35 @@ int THD::binlog_write_table_map(TABLE *table, bool is_transactional,
/* Annotate event should be written not more than once */ /* Annotate event should be written not more than once */
*with_annotate= 0; *with_annotate= 0;
if (unlikely((error= writer.write(&anno)))) if (unlikely((error= writer.write(&anno))))
{ goto write_err;
if (my_errno == EFBIG)
cache_data->set_incident();
DBUG_RETURN(error);
}
} }
if (unlikely((error= writer.write(&the_event)))) DBUG_EXECUTE_IF("table_map_write_error",
DBUG_RETURN(error); {
if (is_transactional)
{
my_errno= EFBIG;
error= 1;
goto write_err;
}
});
if ((error= writer.write(&the_event)))
goto write_err;
binlog_table_maps++; binlog_table_maps++;
DBUG_RETURN(0); DBUG_RETURN(0);
write_err:
mysql_bin_log.set_write_error(this, is_transactional);
/*
For non-transactional engine or multi statement transaction with mixed
engines, data is written to table but writing to binary log failed. In
these scenarios rollback is not possible. Hence report an incident.
*/
if (mysql_bin_log.check_write_error(this) && cache_data &&
lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) &&
table->current_lock == F_WRLCK)
cache_data->set_incident();
DBUG_RETURN(error);
} }
/** /**
...@@ -7202,7 +7227,9 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) ...@@ -7202,7 +7227,9 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd)
if (likely(is_open())) if (likely(is_open()))
{ {
prev_binlog_id= current_binlog_id; prev_binlog_id= current_binlog_id;
if (likely(!(error= write_incident_already_locked(thd))) && if (likely(
!(error= DBUG_EVALUATE_IF("incident_event_write_error", 1,
write_incident_already_locked(thd)))) &&
likely(!(error= flush_and_sync(0)))) likely(!(error= flush_and_sync(0))))
{ {
update_binlog_end_pos(); update_binlog_end_pos();
...@@ -7248,6 +7275,22 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) ...@@ -7248,6 +7275,22 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd)
mysql_mutex_unlock(&LOCK_log); mysql_mutex_unlock(&LOCK_log);
} }
/*
Upon writing incident event, check for thd->error() and print the
relevant error message in the error log.
*/
if (thd->is_error())
{
sql_print_error("Write to binary log failed: "
"%s. An incident event is written to binary log "
"and slave will be stopped.\n",
thd->get_stmt_da()->message());
}
if (error)
{
sql_print_error("Incident event write to the binary log file failed.");
}
DBUG_RETURN(error); DBUG_RETURN(error);
} }
......
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