From dd4a8aedf194b927f2c5d961ecc7d3a2c59ed8b0 Mon Sep 17 00:00:00 2001 From: nada Date: Tue, 4 Aug 2026 20:46:29 +0200 Subject: [PATCH] MDEV-38829: Fix semisync assertion on failed direct binlog write MYSQL_BIN_LOG::write() guarded semisync registration (report_binlog_update) on the direct-write path with if (!error), but did not apply the same guard to the subsequent wait_after_sync() call. When the write failed before registering the transaction, wait_after_sync() still ran and looked for the transaction in the Active_tranx list, found nothing, and tripped the DBUG_ASSERT in commit_trx() that assumes a missing entry can only be caused by semisync having been switched off and on. --- .../r/mdev38829_semisync_write_fail.result | 18 ++++++++++ .../rpl/t/mdev38829_semisync_write_fail.test | 36 +++++++++++++++++++ sql/log.cc | 3 +- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/rpl/r/mdev38829_semisync_write_fail.result create mode 100644 mysql-test/suite/rpl/t/mdev38829_semisync_write_fail.test diff --git a/mysql-test/suite/rpl/r/mdev38829_semisync_write_fail.result b/mysql-test/suite/rpl/r/mdev38829_semisync_write_fail.result new file mode 100644 index 0000000000000..f1ffe58f266cb --- /dev/null +++ b/mysql-test/suite/rpl/r/mdev38829_semisync_write_fail.result @@ -0,0 +1,18 @@ +call mtr.add_suppression("Write to binary log failed*"); +SET GLOBAL rpl_semi_sync_master_enabled = 1; +SET GLOBAL rpl_semi_sync_master_wait_point = 'AFTER_SYNC'; +set @saved_dbug = @@global.debug_dbug; +SET GLOBAL debug_dbug='d,injecting_fault_writing'; +CREATE TABLE t1 (i INT); +CREATE TABLE t1 (i INT); +ERROR HY000: Error writing file 'master-bin' ((errno: #) +set @@global.debug_dbug = @saved_dbug; +SELECT 1; +1 +1 +SHOW STATUS LIKE 'Rpl_semi_sync_master_status'; +Variable_name Value +Rpl_semi_sync_master_status ON +SET GLOBAL rpl_semi_sync_master_enabled = 0; +SET GLOBAL rpl_semi_sync_master_wait_point = DEFAULT; +DROP TABLE IF EXISTS t1; diff --git a/mysql-test/suite/rpl/t/mdev38829_semisync_write_fail.test b/mysql-test/suite/rpl/t/mdev38829_semisync_write_fail.test new file mode 100644 index 0000000000000..65cd234012d9c --- /dev/null +++ b/mysql-test/suite/rpl/t/mdev38829_semisync_write_fail.test @@ -0,0 +1,36 @@ +# +# MDEV-38829: Assertion `rpl_semi_sync_master_off_times > +# thd->expected_semi_sync_offs' failed in Repl_semi_sync_master::commit_trx() +# +# A failed direct binlog write (write_gtid_event()/write_event() returning an +# error) left semisync registration skipped, but the semisync wait +# (wait_after_sync() -> commit_trx()) still ran unconditionally. commit_trx() +# then found no entry for the transaction and asserted that the only +# possible cause was a concurrent switch_off()/switch_on(), which is false +# here -- the entry was simply never registered. +# +# Verify that a failed direct binlog write no longer triggers a semisync +# wait for a transaction that was never registered: the query should just +# return a clean write error, and the server must remain fully responsive +# (in particular, no assertion failure / crash). +# +--source include/have_debug.inc +--source include/have_binlog_format_mixed_or_statement.inc + +call mtr.add_suppression("Write to binary log failed*"); + +SET GLOBAL rpl_semi_sync_master_enabled = 1; +SET GLOBAL rpl_semi_sync_master_wait_point = 'AFTER_SYNC'; + +--let $query= CREATE TABLE t1 (i INT) +--source include/binlog_inject_error.inc + +# The server must still be fully alive and responsive here -- this is the +# actual regression check: pre-fix, the statement above aborts the +# (debug-build) server via DBUG_ASSERT instead of returning cleanly. +SELECT 1; +SHOW STATUS LIKE 'Rpl_semi_sync_master_status'; + +SET GLOBAL rpl_semi_sync_master_enabled = 0; +SET GLOBAL rpl_semi_sync_master_wait_point = DEFAULT; +DROP TABLE IF EXISTS t1; diff --git a/sql/log.cc b/sql/log.cc index d70fe6b9170f7..72a8a9f91938a 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -7259,7 +7259,8 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) mysql_mutex_assert_owner(&LOCK_after_binlog_sync); mysql_mutex_assert_not_owner(&LOCK_commit_ordered); #ifdef HAVE_REPLICATION - if (repl_semisync_master.wait_after_sync(log_file_name, offset)) + if (likely(!error) && + repl_semisync_master.wait_after_sync(log_file_name, offset)) { error=1; /* error is already printed inside hook */