1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-32947 Async conflicts with semi-sync in multi-source

`@@rpl_semi_sync_slave_enabled` can be changed without stopping
replication, because it takes effect when a connection is established.
But when it takes effect, it applies to *all* replication connections.
This means that starting an async connection will switch any ongoing
semi-sync connections to async as well, and vice versa, which will
cause those connections to fail to replicate in the wrong mode.

This fix resolves this oversight by changing the value application
to be specific to only the establishing connection (technically,
moving the singleton’s field `Repl_semi_sync_slave::m_slave_enabled`
to the instance variable `Master_info::semi_sync_enabled`).
This design builds toward MDEV-40941, which proposes migrating
`@@rpl_semi_sync_slave_enabled` to a proper per-connection config.
Note, `Master_info::semi_sync_reply_enabled` must remain separate,
because it only disables ACK replying in async slave fallback,
whereas the semi-sync slave setting also controls whether the IO
thread should expect additional semi-sync flags in event packets.

To maintain backward compatibility until MDEV-40941,
the status variable `Rpl_semi_sync_slave_status` is kept showing
the applied `@@rpl_semi_sync_slave_enabled` for the most recent
START SLAVE in any connection; that is, not yet made per-connection.

Reviewed-by: Brandon Nesterenko <brandon.nesterenko@mariadb.com>
This commit is contained in:
ParadoxV5
2026-10-11 14:59:35 -06:00
parent 18d3771000
commit 40cae5d6ed
7 changed files with 287 additions and 26 deletions

View File

@@ -0,0 +1,84 @@
# Test replicating from an additional connection of semi-sync mode `$mode2`
# does not interfere with the active connection of mode `$mode1`.
#
# Pre-Conditions:
# * On connection `slave`.
# * CHANGE MASTER `master1` and `master2` set up to
# replicate from the connection with the same name.
# * None of the replication threads are running.
# * User variables `$mode1` and `$mode2` contain
# corresponding `@@rpl_semi_sync_slave_enabled` values.
#
# Post-Conditions:
# * On connection `slave`.
# * None of the replication threads are running.
# * @@default_master_connection = 'master1',
# @@rpl_semi_sync_slave_enabled = $mode2
--let $status_var= Rpl_semi_sync_master_clients
--let $status_var_value= 1
# Start `master1`
SET @@SESSION.default_master_connection= 'master1';
--eval SET @@GLOBAL.rpl_semi_sync_slave_enabled= $mode1
--source include/start_slave.inc
# Start `master2`
SET @@SESSION.default_master_connection= 'master2';
--eval SET @@GLOBAL.rpl_semi_sync_slave_enabled= $mode2
--source include/start_slave.inc
# TODO MDEV-40941:
# `Rpl_semi_sync_slave_status` does not refer to `@@default_master_connection`,
# but rather the most recent START SLAVE (`$mode2` in this case).
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
SET @@SESSION.default_master_connection= 'master1';
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
# Verify `master2`
--connection master2
if ($mode2)
{
--source include/wait_for_status_var.inc
}
CREATE TABLE t2 (a INT);
DROP TABLE t2;
--save_master_pos
--connection slave
--sync_with_master 0, master2
# Verify `master1`
--connection master1
if ($mode1)
{
--source include/wait_for_status_var.inc
}
CREATE TABLE t1 (a INT);
--save_master_pos
--connection slave
--sync_with_master 0, master1
SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack';
# Stop `master2`
SET @@SESSION.default_master_connection= 'master2';
--source include/stop_slave.inc
# TODO MDEV-40941:
# `Rpl_semi_sync_slave_status` shows OFF after STOP SLAVE on any connection.
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
# Verify `master1`
--connection master1
DROP TABLE t1;
SHOW STATUS LIKE 'Rpl_semi_sync_master_clients';
--save_master_pos
--connection slave
--sync_with_master 0, master1
SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack';
# Stop `master1`
SET @@SESSION.default_master_connection= 'master1';
--source include/stop_slave.inc

View File

@@ -0,0 +1,109 @@
# Setup
connect master1, 127.0.0.1, root, , , $SERVER_MYPORT_1;
SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0;
SET @@GLOBAL.rpl_semi_sync_master_enabled= 1;
SET @@SESSION.gtid_domain_id= 1;
RESET MASTER;
connect master2, 127.0.0.1, root, , , $SERVER_MYPORT_2;
SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0;
SET @@GLOBAL.rpl_semi_sync_master_enabled= 1;
SET @@SESSION.gtid_domain_id= 2;
RESET MASTER;
connect slave, 127.0.0.1, root, , , $SERVER_MYPORT_3;
SET @@GLOBAL.gtid_slave_pos= '';
CHANGE MASTER 'master1' TO
master_host='127.0.0.1', master_port=SERVER_MYPORT_1, master_user='root';
CHANGE MASTER 'master2' TO
master_host='127.0.0.1', master_port=SERVER_MYPORT_2, master_user='root';
# Test semi-sync replication during async replication
SET @@SESSION.default_master_connection= 'master1';
SET @@GLOBAL.rpl_semi_sync_slave_enabled= 0;
include/start_slave.inc
SET @@SESSION.default_master_connection= 'master2';
SET @@GLOBAL.rpl_semi_sync_slave_enabled= 1;
include/start_slave.inc
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status ON
SET @@SESSION.default_master_connection= 'master1';
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status ON
connection master2;
CREATE TABLE t2 (a INT);
DROP TABLE t2;
connection slave;
connection master1;
CREATE TABLE t1 (a INT);
connection slave;
SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack';
Variable_name Value
Rpl_semi_sync_slave_send_ack 2
SET @@SESSION.default_master_connection= 'master2';
include/stop_slave.inc
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status OFF
connection master1;
DROP TABLE t1;
SHOW STATUS LIKE 'Rpl_semi_sync_master_clients';
Variable_name Value
Rpl_semi_sync_master_clients 0
connection slave;
SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack';
Variable_name Value
Rpl_semi_sync_slave_send_ack 2
SET @@SESSION.default_master_connection= 'master1';
include/stop_slave.inc
# Test the other order
SET @@SESSION.default_master_connection= 'master1';
SET @@GLOBAL.rpl_semi_sync_slave_enabled= 1;
include/start_slave.inc
SET @@SESSION.default_master_connection= 'master2';
SET @@GLOBAL.rpl_semi_sync_slave_enabled= 0;
include/start_slave.inc
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status OFF
SET @@SESSION.default_master_connection= 'master1';
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status OFF
connection master2;
CREATE TABLE t2 (a INT);
DROP TABLE t2;
connection slave;
connection master1;
CREATE TABLE t1 (a INT);
connection slave;
SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack';
Variable_name Value
Rpl_semi_sync_slave_send_ack 1
SET @@SESSION.default_master_connection= 'master2';
include/stop_slave.inc
SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status OFF
connection master1;
DROP TABLE t1;
SHOW STATUS LIKE 'Rpl_semi_sync_master_clients';
Variable_name Value
Rpl_semi_sync_master_clients 1
connection slave;
SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack';
Variable_name Value
Rpl_semi_sync_slave_send_ack 2
SET @@SESSION.default_master_connection= 'master1';
include/stop_slave.inc
# Cleanup
RESET SLAVE 'master1' ALL;
RESET SLAVE 'master2' ALL;
disconnect slave;
connection master1;
SET @@GLOBAL.rpl_semi_sync_master_enabled= 0;
SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 1;
disconnect master1;
connection master2;
SET @@GLOBAL.rpl_semi_sync_master_enabled= 0;
SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 1;
disconnect master2;

View File

@@ -0,0 +1,63 @@
# MDEV-32947 Async conflicts with semi-sync in multi-source
#
# With a async master and a semi-sync master,
# test replication does not mix async and semi-sync regardless of order
--source include/have_binlog_format_mixed.inc # format-agnostic
--let $orig_wait_no_slave= `SELECT @@GLOBAL.rpl_semi_sync_master_wait_no_slave`
--echo # Setup
# Enable semi-sync on both masters;
# the slave will disable semi-sync on one of its connections.
--connect (master1, 127.0.0.1, root, , , $SERVER_MYPORT_1)
SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0;
SET @@GLOBAL.rpl_semi_sync_master_enabled= 1;
SET @@SESSION.gtid_domain_id= 1;
RESET MASTER;
--connect (master2, 127.0.0.1, root, , , $SERVER_MYPORT_2)
SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0;
SET @@GLOBAL.rpl_semi_sync_master_enabled= 1;
SET @@SESSION.gtid_domain_id= 2;
RESET MASTER;
--connect (slave, 127.0.0.1, root, , , $SERVER_MYPORT_3)
--disable_warnings
SET @@GLOBAL.gtid_slave_pos= '';
--enable_warnings
--replace_result $SERVER_MYPORT_1 SERVER_MYPORT_1
eval CHANGE MASTER 'master1' TO
master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root';
--replace_result $SERVER_MYPORT_2 SERVER_MYPORT_2
eval CHANGE MASTER 'master2' TO
master_host='127.0.0.1', master_port=$SERVER_MYPORT_2, master_user='root';
--echo # Test semi-sync replication during async replication
--let $mode1= 0
--let $mode2= 1
--source mix_semisync_async.inc
--echo # Test the other order
--let $mode1= 1
--let $mode2= 0
--source mix_semisync_async.inc
--echo # Cleanup
RESET SLAVE 'master1' ALL;
RESET SLAVE 'master2' ALL;
--disconnect slave
--connection master1
SET @@GLOBAL.rpl_semi_sync_master_enabled= 0;
--eval SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= $orig_wait_no_slave
--disconnect master1
--connection master2
SET @@GLOBAL.rpl_semi_sync_master_enabled= 0;
--eval SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= $orig_wait_no_slave
--disconnect master2

View File

@@ -376,6 +376,7 @@ class Master_info : public Slave_reporting_capability
it must be ignored similarly to the replicate-same-server-id rule.
*/
bool do_accept_own_server_id= false;
bool semi_sync_enabled= false;
/*
Set to 1 when semi_sync is enabled. Set to 0 if there is any transmit
problems to the slave, in which case any furter semi-sync reply is

View File

@@ -46,7 +46,8 @@ int rpl_semi_sync_enabled(THD *thd, SHOW_VAR *var, void *buff,
system_status_var *status_var,
enum_var_type scope)
{
local_semi_sync_enabled= repl_semisync_slave.get_slave_enabled();
//TODO MDEV-40941
local_semi_sync_enabled= repl_semisync_slave.m_slave_enabled;
var->type= SHOW_BOOL;
var->value= (char*) &local_semi_sync_enabled;
return 0;
@@ -55,14 +56,14 @@ int rpl_semi_sync_enabled(THD *thd, SHOW_VAR *var, void *buff,
int Repl_semi_sync_slave::slave_read_sync_header(const uchar *header,
unsigned long total_len,
int *semi_flags,
Master_info *mi,
const uchar **payload,
unsigned long *payload_len)
{
int read_res = 0;
DBUG_ENTER("Repl_semi_sync_slave::slave_read_sync_header");
if (get_slave_enabled())
if (get_slave_enabled(mi))
{
if (!DBUG_IF("semislave_corrupt_log")
&& header[0] == k_packet_magic_num)
@@ -76,9 +77,9 @@ int Repl_semi_sync_slave::slave_read_sync_header(const uchar *header,
semi_sync_need_reply));
if (semi_sync_need_reply)
*semi_flags |= SEMI_SYNC_NEED_ACK;
mi->semi_ack |= SEMI_SYNC_NEED_ACK;
if (is_delay_master())
*semi_flags |= SEMI_SYNC_SLAVE_DELAY_SYNC;
mi->semi_ack |= SEMI_SYNC_SLAVE_DELAY_SYNC;
}
else
{
@@ -111,7 +112,7 @@ void Repl_semi_sync_slave::slave_start(Master_info *mi)
*/
bool semi_sync= global_rpl_semi_sync_slave_enabled;
set_slave_enabled(semi_sync);
set_slave_enabled(mi, semi_sync);
mi->semi_sync_reply_enabled= 0;
sql_print_information("Slave I/O thread: Start %s replication to\
@@ -127,7 +128,7 @@ void Repl_semi_sync_slave::slave_start(Master_info *mi)
void Repl_semi_sync_slave::slave_stop(Master_info *mi)
{
if (get_slave_enabled())
if (get_slave_enabled(mi))
{
#ifdef ENABLED_DEBUG_SYNC
/*
@@ -144,7 +145,7 @@ void Repl_semi_sync_slave::slave_stop(Master_info *mi)
kill_connection(mi);
}
set_slave_enabled(0);
set_slave_enabled(mi, 0);
}
void Repl_semi_sync_slave::slave_reconnect(Master_info *mi)
@@ -153,7 +154,7 @@ void Repl_semi_sync_slave::slave_reconnect(Master_info *mi)
Start semi-sync either if it globally enabled or if was enabled
before the reconnect.
*/
if (global_rpl_semi_sync_slave_enabled || get_slave_enabled())
if (global_rpl_semi_sync_slave_enabled || get_slave_enabled(mi))
slave_start(mi);
}
@@ -215,7 +216,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi)
MYSQL_ROW row;
const char *query;
if (!get_slave_enabled())
if (!get_slave_enabled(mi))
return 0;
query= "SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled'";
@@ -223,7 +224,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi)
!(res= mysql_store_result(mysql)))
{
sql_print_error("Execution failed on master: %s, error :%s", query, mysql_error(mysql));
set_slave_enabled(0);
set_slave_enabled(mi, 0);
return 1;
}
@@ -234,7 +235,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi)
if (!row)
sql_print_warning("Master server does not support semi-sync, "
"fallback to asynchronous replication");
set_slave_enabled(0);
set_slave_enabled(mi, 0);
mysql_free_result(res);
return 0;
}
@@ -253,7 +254,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi)
if (mysql_real_query(mysql, query, (ulong)strlen(query)))
{
sql_print_error("%s on master failed", query);
set_slave_enabled(0);
set_slave_enabled(mi, 0);
return 1;
}
mi->semi_sync_reply_enabled= 1;
@@ -276,7 +277,7 @@ int Repl_semi_sync_slave::slave_reply(Master_info *mi)
int reply_res = 0;
size_t name_len = strlen(binlog_filename);
DBUG_ENTER("Repl_semi_sync_slave::slave_reply");
DBUG_ASSERT(get_slave_enabled() && mi->semi_sync_reply_enabled);
DBUG_ASSERT(get_slave_enabled(mi) && mi->semi_sync_reply_enabled);
/* Prepare the buffer of the reply. */
reply_buffer[REPLY_MAGIC_NUM_OFFSET] = k_packet_magic_num;

View File

@@ -17,6 +17,7 @@
#ifndef SEMISYNC_SLAVE_H
#define SEMISYNC_SLAVE_H
#ifdef HAVE_REPLICATION
#include "semisync.h"
#include "my_global.h"
@@ -45,12 +46,12 @@ public:
*/
int init_object();
inline bool get_slave_enabled() {
return m_slave_enabled;
inline bool get_slave_enabled(Master_info *mi) {
return mi->semi_sync_enabled;
}
void set_slave_enabled(bool enabled) {
m_slave_enabled = enabled;
void set_slave_enabled(Master_info *mi, bool enabled) {
m_slave_enabled= mi->semi_sync_enabled= enabled;
}
inline bool is_delay_master(){
@@ -71,8 +72,8 @@ public:
* Input:
* header - (IN) packet header pointer
* total_len - (IN) total packet length: metadata + payload
* semi_flags - (IN) store flags: SEMI_SYNC_SLAVE_DELAY_SYNC and
SEMI_SYNC_NEED_ACK
* mi - (IN) store flags: SEMI_SYNC_SLAVE_DELAY_SYNC and
SEMI_SYNC_NEED_ACK, to Master_info::semi_ack
* payload - (IN) payload: the replication event
* payload_len - (IN) payload length
*
@@ -80,7 +81,7 @@ public:
* 0: success; non-zero: error
*/
int slave_read_sync_header(const uchar *header, unsigned long total_len,
int *semi_flags,
Master_info *mi,
const uchar **payload, unsigned long *payload_len);
/* A slave replies to the master indicating its replication process. It
@@ -94,10 +95,11 @@ public:
int request_transmit(Master_info *mi);
void kill_connection(Master_info *mi);
/// @deprecated For `Rpl_semi_sync_slave_status` compatibility only
bool m_slave_enabled;
private:
/* True when init_object has been called */
bool m_init_done;
bool m_slave_enabled; /* semi-sync is enabled on the slave */
bool m_delay_master;
unsigned int m_kill_conn_timeout;
};
@@ -115,4 +117,5 @@ extern unsigned long long rpl_semi_sync_slave_send_ack;
extern int rpl_semi_sync_enabled(THD *thd, SHOW_VAR *var, void *buff,
system_status_var *status_var,
enum_var_type scope);
#endif /* HAVE_REPLICATION */
#endif /* SEMISYNC_SLAVE_H */

View File

@@ -5293,7 +5293,7 @@ Stopping slave I/O thread due to out-of-memory error from master");
if (repl_semisync_slave.
slave_read_sync_header((const uchar*) mysql->net.read_pos + 1,
event_len,
&(mi->semi_ack), &event_buf, &event_len))
mi, &event_buf, &event_len))
{
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
@@ -5350,7 +5350,7 @@ Stopping slave I/O thread due to out-of-memory error from master");
goto err;
}
if (repl_semisync_slave.get_slave_enabled() &&
if (repl_semisync_slave.get_slave_enabled(mi) &&
mi->semi_sync_reply_enabled &&
(mi->semi_ack & SEMI_SYNC_NEED_ACK))
{
@@ -5394,7 +5394,7 @@ Stopping slave I/O thread due to out-of-memory error from master");
master info only when ack is needed. This may lead to at least one
group transaction delay but affords better performance improvement.
*/
(!repl_semisync_slave.get_slave_enabled() ||
(!repl_semisync_slave.get_slave_enabled(mi) ||
(!(mi->semi_ack & SEMI_SYNC_SLAVE_DELAY_SYNC) ||
(mi->semi_ack & (SEMI_SYNC_NEED_ACK)))) &&
(DBUG_IF("failed_flush_master_info") ||
@@ -7072,7 +7072,7 @@ dbug_gtid_accept:
*/
mi->do_accept_own_server_id=
(s_id == global_system_variables.server_id &&
repl_semisync_slave.get_slave_enabled() && opt_gtid_strict_mode &&
repl_semisync_slave.get_slave_enabled(mi) && opt_gtid_strict_mode &&
mi->using_gtid != Master_info::USE_GTID_NO &&
!mysql_bin_log.check_strict_gtid_sequence(event_gtid.domain_id,
event_gtid.server_id,