From 3fa1017dc5249d02cdca290bf66605ff3890aa7a Mon Sep 17 00:00:00 2001 From: Mahmoud Khaled Date: Sun, 8 Feb 2026 22:24:14 +0200 Subject: [PATCH] MDEV-7394 Make slave_skip_errors dynamic Make slave_skip_errors dynamic so it can be changed while the slave is stopped. Attempts to change it while the slave is running are rejected with a clear error. --- .../suite/rpl/r/rpl_skip_error_dynamic.result | 29 +++++++++++ .../suite/rpl/t/rpl_skip_error_dynamic.test | 28 ++++++++++ sql/slave.cc | 52 +++++++++++++++++-- sql/slave.h | 2 + sql/sys_vars.cc | 6 ++- 5 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_skip_error_dynamic.result create mode 100644 mysql-test/suite/rpl/t/rpl_skip_error_dynamic.test diff --git a/mysql-test/suite/rpl/r/rpl_skip_error_dynamic.result b/mysql-test/suite/rpl/r/rpl_skip_error_dynamic.result new file mode 100644 index 0000000000000..d4ca86eabc3b4 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_skip_error_dynamic.result @@ -0,0 +1,29 @@ +# MDEV-7394 test dynamic slave_skip_error (writable when slaves stopped) +include/master-slave.inc +[connection master] +connection slave; +# should reduce error because slave is not stopped +SET GLOBAL slave_skip_errors = "1062"; +ERROR HY000: This operation cannot be performed as you have a running slave ''; run STOP SLAVE '' first +SELECT @@global.slave_skip_errors; +@@global.slave_skip_errors +OFF +STOP SLAVE; +# should work corerctly because slaves stopped +SET GLOBAL slave_skip_errors = "1062" +SELECT @@global.slave_skip_errors; +@@global.slave_skip_errors +OFF +SET GLOBAL slave_skip_errors = "1050"; +SELECT @@global.slave_skip_errors; +@@global.slave_skip_errors +1050 +SET GLOBAL slave_skip_errors = "OFF"; +SELECT @@global.slave_skip_errors; +@@global.slave_skip_errors +OFF +START SLAVE; +# should reduce error because slave is not stopped +SET GLOBAL slave_skip_errors = "1040"; +ERROR HY000: This operation cannot be performed as you have a running slave ''; run STOP SLAVE '' first +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_skip_error_dynamic.test b/mysql-test/suite/rpl/t/rpl_skip_error_dynamic.test new file mode 100644 index 0000000000000..5414b0380dda8 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_skip_error_dynamic.test @@ -0,0 +1,28 @@ +--echo # MDEV-7394 test dynamic slave_skip_error (writable when slaves stopped) + +--source include/master-slave.inc + +--connection slave + +--echo # should reduce error because slave is not stopped +--error ER_SLAVE_MUST_STOP +SET GLOBAL slave_skip_errors = "1062"; +SELECT @@global.slave_skip_errors; + +STOP SLAVE; +echo # should work corerctly because slaves stopped +SET GLOBAL slave_skip_errors = "1062"; +SELECT @@global.slave_skip_errors; + +SET GLOBAL slave_skip_errors = "1050"; +SELECT @@global.slave_skip_errors; + +SET GLOBAL slave_skip_errors = "OFF"; +SELECT @@global.slave_skip_errors; + +START SLAVE; +--echo # should reduce error because slave is not stopped +--error ER_SLAVE_MUST_STOP +SET GLOBAL slave_skip_errors = "1040"; + +--source include/rpl_end.inc diff --git a/sql/slave.cc b/sql/slave.cc index 65d674186af58..30bfc63722ad7 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -711,8 +711,8 @@ static void make_slave_skip_errors_printable(void) DBUG_ASSERT(sizeof(slave_skip_error_names) > MIN_ROOM); DBUG_ASSERT(MAX_SLAVE_ERROR <= 999999); // 6 digits - /* Make @@slave_skip_errors show the nice human-readable value. */ - opt_slave_skip_errors= slave_skip_error_names; + /* we should not touch opt_slave_skip_errors here. we just build the printable string only. */ + (void) opt_slave_skip_errors; if (!use_slave_mask || bitmap_is_clear_all(&slave_error_mask)) { @@ -774,8 +774,15 @@ bool init_slave_skip_errors(const char* arg) if (!arg || !*arg) // No errors defined goto end; - if (my_bitmap_init(&slave_error_mask,0,MAX_SLAVE_ERROR)) - DBUG_RETURN(1); + if (!slave_error_mask.bitmap) + { + if (my_bitmap_init(&slave_error_mask, 0, MAX_SLAVE_ERROR)) + DBUG_RETURN(1); + } + else + { + bitmap_clear_all(&slave_error_mask); + } use_slave_mask= 1; for (;my_isspace(system_charset_info,*arg);++arg) @@ -801,6 +808,43 @@ bool init_slave_skip_errors(const char* arg) DBUG_RETURN(0); } + +bool check_slave_skip_errors(sys_var *self, THD *thd, set_var *var) +{ + return give_error_if_slave_running(0); +} + + +bool update_slave_skip_errors(sys_var *self, THD *thd, enum_var_type type) +{ + bool res= false; + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_active_mi); + + if (active_mi->slave_running) + { + my_error(ER_SLAVE_MUST_STOP, MYF(0)); + res= true; + } + else + { + if (use_slave_mask) + my_bitmap_free(&slave_error_mask); + + use_slave_mask= 0; + + if (init_slave_skip_errors(opt_slave_skip_errors)) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "slave_skip_errors", opt_slave_skip_errors); + res= true; + } + } + + mysql_mutex_unlock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_global_system_variables); + return res; +} + /** Make printable version if slave_transaction_retry_errors This is never empty as at least ER_LOCK_DEADLOCK and ER_LOCK_WAIT_TIMEOUT diff --git a/sql/slave.h b/sql/slave.h index 02555210f5575..be1c2505a13d5 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -190,6 +190,8 @@ extern const char *relay_log_basename; int init_slave(); int init_recovery(Master_info* mi, const char** errmsg); bool init_slave_skip_errors(const char* arg); +bool check_slave_skip_errors(sys_var *self, THD *thd, set_var *var); +bool update_slave_skip_errors(sys_var *self, THD *thd, enum_var_type type); bool init_slave_transaction_retry_errors(const char* arg); int register_slave_on_master(MYSQL* mysql); int terminate_slave_threads(Master_info* mi, int thread_mask, diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 63f3f38bc36df..8d848571e1d55 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6243,8 +6243,10 @@ static Sys_var_charptr Sys_slave_skip_errors( "slave_skip_errors", "Tells the slave thread to continue " "replication when a query event returns an error from the " "provided list", - READ_ONLY GLOBAL_VAR(opt_slave_skip_errors), CMD_LINE(REQUIRED_ARG), - DEFAULT(0)); + GLOBAL_VAR(opt_slave_skip_errors), CMD_LINE(REQUIRED_ARG), + DEFAULT("OFF"), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(check_slave_skip_errors), + ON_UPDATE(update_slave_skip_errors)); static Sys_var_on_access_global