From c81dffd460954b4e806ad261522d22246aa7144e Mon Sep 17 00:00:00 2001 From: Kristian Nielsen Date: Wed, 14 Oct 2026 11:58:26 +0200 Subject: [PATCH] MDEV-41214: PURGE BINARY LOGS does not delete non-active binlog files Make the binlog-in-engine code path ignore @@slave_connections_needed_for_purge for interactive PURGE BINARY LOGS. Signed-off-by: Kristian Nielsen --- .../t/binlog_purge_stale_refusal_cache.test | 40 +++++++--- .../binlog_flush_purge.result | 8 -- .../binlog_in_engine/binlog_flush_purge.test | 5 -- .../binlog_purge_stale_refusal_cache.result | 74 +++++++++++++++++++ .../binlog_purge_stale_refusal_cache.test | 4 + sql/sql_repl.cc | 31 ++------ 6 files changed, 113 insertions(+), 49 deletions(-) create mode 100644 mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.result create mode 100644 mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.test diff --git a/mysql-test/suite/binlog/t/binlog_purge_stale_refusal_cache.test b/mysql-test/suite/binlog/t/binlog_purge_stale_refusal_cache.test index 514b0d5bc12..1fe94b5a973 100644 --- a/mysql-test/suite/binlog/t/binlog_purge_stale_refusal_cache.test +++ b/mysql-test/suite/binlog/t/binlog_purge_stale_refusal_cache.test @@ -36,16 +36,22 @@ RESET MASTER; --echo # FLUSH BINARY LOGS; FLUSH BINARY LOGS; +--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) FLUSH BINARY LOGS; +if ($binlog_in_engine) { + --let $binlog_name= binlog-000004.ibb + --let $binlog_size= 262144 + --source include/wait_for_engine_binlog.inc +} --source include/show_binary_logs.inc -PURGE BINARY LOGS TO 'master-bin.000003'; +eval PURGE BINARY LOGS TO '$binlog_file'; SHOW WARNINGS; --source include/show_binary_logs.inc --let $first= query_get_value(SHOW BINARY LOGS, Log_name, 1) -if ($first != master-bin.000003) +if ($first != $binlog_file) { - --echo # Control failed: expected master-bin.000003 first, got $first + --echo # Control failed: expected $binlog_file first, got $first --die Interactive purge did not work even with no cached refusal } @@ -55,7 +61,12 @@ if ($first != master-bin.000003) --echo # FLUSH BINARY LOGS; FLUSH BINARY LOGS; +--let $binlog_file2= query_get_value(SHOW MASTER STATUS, File, 1) FLUSH BINARY LOGS; +if ($binlog_in_engine) { + --let $binlog_name= binlog-000007.ibb + --source include/wait_for_engine_binlog.inc +} --source include/show_binary_logs.inc --echo # Let the files age past binlog_expire_logs_seconds: purge_logs_before_date @@ -65,23 +76,30 @@ SET @@global.binlog_expire_logs_seconds= 1; --echo # Rotating attempts an automatic purge, which the guard refuses. That --echo # refusal is what populates the cache. FLUSH BINARY LOGS; +--let $binlog_file3= query_get_value(SHOW MASTER STATUS, File, 1) --let $first= query_get_value(SHOW BINARY LOGS, Log_name, 1) -if ($first != master-bin.000003) +if ($first != $binlog_file) { --echo # Setup failed: expected the automatic purge to delete nothing. --die The automatic purge was not refused, so nothing was cached } ---echo # The automatic refusal states the correct reason in the error log: ---let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err ---let SEARCH_PATTERN= Binary log .master-bin.000003. is not purged because less than .slave_connections_needed_for_purge. slaves have processed it ---source include/search_pattern_in_file.inc + if (!$binlog_in_engine) { + --echo # The automatic refusal states the correct reason in the error log: + --let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err + --let SEARCH_PATTERN= Binary log .$binlog_file. is not purged because less than .slave_connections_needed_for_purge. slaves have processed it + --source include/search_pattern_in_file.inc +} --echo # Now the interactive purge. It should behave exactly as in part 1. -PURGE BINARY LOGS TO 'master-bin.000006'; ---echo # On affected versions this reports master-bin.000003 as the current active ---echo # binlog, while master-bin.000008 is active: +eval PURGE BINARY LOGS TO '$binlog_file2'; +--echo # On affected versions this reports $binlog_file as the current active +--echo # binlog, while $binlog_file3 is active: SHOW WARNINGS; +if ($binlog_in_engine) { + --let $binlog_name= binlog-000008.ibb + --source include/wait_for_engine_binlog.inc +} --source include/show_binary_logs.inc --evalp SET @@global.binlog_expire_logs_seconds = $save_binlog_expire_logs_seconds diff --git a/mysql-test/suite/binlog_in_engine/binlog_flush_purge.result b/mysql-test/suite/binlog_in_engine/binlog_flush_purge.result index e5eabe79a87..bc54b84e6bf 100644 --- a/mysql-test/suite/binlog_in_engine/binlog_flush_purge.result +++ b/mysql-test/suite/binlog_in_engine/binlog_flush_purge.result @@ -36,14 +36,6 @@ INSERT INTO t1 VALUES (100); INSERT INTO t2 VALUES (100, 'xyzzy'); DROP TABLE t1, t2; CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(2048)) ENGINE=InnoDB; -SET @old_min_slaves= @@GLOBAL.slave_connections_needed_for_purge; -SET GLOBAL slave_connections_needed_for_purge= 1; -PURGE BINARY LOGS TO 'binlog-000001.ibb'; -ERROR HY000: A purgeable log is in use, will not purge -SHOW WARNINGS; -Level Code Message -Note 1375 Binary log 'binlog-000000.ibb' is not purged because less than 'slave_connections_needed_for_purge' slaves have processed it -Error 1378 A purgeable log is in use, will not purge SET GLOBAL slave_connections_needed_for_purge= 0; PURGE BINARY LOGS TO 'binlog-000001.ibb'; ERROR HY000: A purgeable log is in use, will not purge diff --git a/mysql-test/suite/binlog_in_engine/binlog_flush_purge.test b/mysql-test/suite/binlog_in_engine/binlog_flush_purge.test index 77a67bbae93..cd82a6d7b59 100644 --- a/mysql-test/suite/binlog_in_engine/binlog_flush_purge.test +++ b/mysql-test/suite/binlog_in_engine/binlog_flush_purge.test @@ -65,11 +65,6 @@ DROP TABLE t1, t2; # Test purge by size CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(2048)) ENGINE=InnoDB; -SET @old_min_slaves= @@GLOBAL.slave_connections_needed_for_purge; -SET GLOBAL slave_connections_needed_for_purge= 1; ---error ER_LOG_IN_USE -PURGE BINARY LOGS TO 'binlog-000001.ibb'; -SHOW WARNINGS; SET GLOBAL slave_connections_needed_for_purge= 0; --error ER_LOG_IN_USE PURGE BINARY LOGS TO 'binlog-000001.ibb'; diff --git a/mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.result b/mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.result new file mode 100644 index 00000000000..30798a3f995 --- /dev/null +++ b/mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.result @@ -0,0 +1,74 @@ +SET @@global.binlog_expire_logs_seconds= 0; +SET @@global.max_binlog_total_size= 0; +SET @@global.slave_connections_needed_for_purge= 1; +SELECT @@global.slave_connections_needed_for_purge; +@@global.slave_connections_needed_for_purge +1 +SELECT @@global.binlog_expire_logs_seconds, @@global.max_binlog_total_size; +@@global.binlog_expire_logs_seconds @@global.max_binlog_total_size +0 0 +# No replica has ever connected: +SELECT COUNT(*) AS dump_threads FROM information_schema.PROCESSLIST +WHERE COMMAND = 'Binlog Dump'; +dump_threads +0 +RESET MASTER; +# +# Part 1 (control). Nothing has attempted a purge yet, so no refusal is +# cached, and the interactive purge correctly ignores the slave's guard. +# +FLUSH BINARY LOGS; +FLUSH BINARY LOGS; +FLUSH BINARY LOGS; +show binary logs; +Log_name File_size +binlog-000000.ibb # +binlog-000001.ibb # +binlog-000002.ibb # +binlog-000003.ibb # +binlog-000004.ibb # +PURGE BINARY LOGS TO 'binlog-000002.ibb'; +SHOW WARNINGS; +Level Code Message +show binary logs; +Log_name File_size +binlog-000002.ibb # +binlog-000003.ibb # +binlog-000004.ibb # +# +# Part 2. Let one automatic purge be refused, then try PURGE that must +# be effective on the fixed server. +# +FLUSH BINARY LOGS; +FLUSH BINARY LOGS; +FLUSH BINARY LOGS; +show binary logs; +Log_name File_size +binlog-000002.ibb # +binlog-000003.ibb # +binlog-000004.ibb # +binlog-000005.ibb # +binlog-000006.ibb # +binlog-000007.ibb # +# Let the files age past binlog_expire_logs_seconds: purge_logs_before_date +# skips can_purge_log() for any file whose mtime is not older than that. +SET @@global.binlog_expire_logs_seconds= 1; +# Rotating attempts an automatic purge, which the guard refuses. That +# refusal is what populates the cache. +FLUSH BINARY LOGS; +# Now the interactive purge. It should behave exactly as in part 1. +PURGE BINARY LOGS TO 'binlog-000005.ibb'; +# On affected versions this reports binlog-000002.ibb as the current active +# binlog, while binlog-000007.ibb is active: +SHOW WARNINGS; +Level Code Message +show binary logs; +Log_name File_size +binlog-000005.ibb # +binlog-000006.ibb # +binlog-000007.ibb # +binlog-000008.ibb # +SET @@global.binlog_expire_logs_seconds = $save_binlog_expire_logs_seconds; +SET @@global.max_binlog_total_size= $save_max_binlog_total_size; +SET @@global.slave_connections_needed_for_purge= $save_slave_connections_needed_for_purge; +RESET MASTER; diff --git a/mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.test b/mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.test new file mode 100644 index 00000000000..696ea8c3c4f --- /dev/null +++ b/mysql-test/suite/binlog_in_engine/binlog_purge_stale_refusal_cache.test @@ -0,0 +1,4 @@ +--let $binlog_in_engine= 1 +--source suite/binlog/t/binlog_purge_stale_refusal_cache.test +# This is last so we override the --log-bin server option correctly. +--source include/have_innodb_binlog.inc diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 5c582b4357c..2ba639e5f79 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -838,22 +838,9 @@ bool purge_master_logs(THD* thd, const char* to_log) handler_binlog_purge_info purge_info; auto p= engine_binlog_in_use(); purge_info.limit_file_no= p.first; - uint num_dump_threads= p.second; - if (num_dump_threads < slave_connections_needed_for_purge) - { - /* - Prevent purging any file. - We need to do it this way, since we have to call into the engine to let - it check if there are any files to potentially purge. If there are, we - want to give an error that purge was not possible. But if there were no - files to purge in any case, we do not want to give any error. - */ - purge_info.limit_file_no= 0; - purge_info.nonpurge_reason= "less than " - "'slave_connections_needed_for_purge' slaves have processed it"; - } - else - purge_info.nonpurge_reason= nullptr; + uint num_dump_threads __attribute__((unused))= p.second; + /* Interactive purge ignores slave_connections_needed_for_purge */ + purge_info.nonpurge_reason= nullptr; purge_info.nonpurge_filename[0]= '\0'; purge_info.purge_by_date= false; purge_info.limit_date= (time_t)0; @@ -901,15 +888,9 @@ bool purge_master_logs_before_date(THD* thd, time_t purge_time) handler_binlog_purge_info purge_info; auto p= engine_binlog_in_use(); purge_info.limit_file_no= p.first; - uint num_dump_threads= p.second; - if (num_dump_threads < slave_connections_needed_for_purge) - { - purge_info.limit_file_no= 0; - purge_info.nonpurge_reason= "less than " - "'slave_connections_needed_for_purge' slaves have processed it"; - } - else - purge_info.nonpurge_reason= nullptr; + uint num_dump_threads __attribute__((unused))= p.second; + /* Interactive purge ignores slave_connections_needed_for_purge */ + purge_info.nonpurge_reason= nullptr; purge_info.nonpurge_filename[0]= '\0'; purge_info.purge_by_date= true; purge_info.limit_date= purge_time;