Skip to content

Commit

Permalink
MDEV-32551: "Read semi-sync reply magic number error" warnings on master
Browse files Browse the repository at this point in the history
rpl_semi_sync_slave_enabled_consistent.test and the first part of
the commit message comes from Brandon Nesterenko.

A test to show how to induce the "Read semi-sync reply magic number
error" message on a primary. In short, if semi-sync is turned on
during the hand-shake process between a primary and replica, but
later a user negates the rpl_semi_sync_slave_enabled variable while
the replica's IO thread is running; if the io thread exits, the
replica can skip a necessary call to kill_connection() in
repl_semisync_slave.slave_stop() due to its reliance on a global
variable. Then, the replica will send a COM_QUIT packet to the
primary on an active semi-sync connection, causing the magic number
error.

The test in this patch exits the IO thread by forcing an error;
though note a call to STOP SLAVE could also do this, but it ends up
needing more synchronization. That is, the STOP SLAVE command also
tries to kill the VIO of the replica, which makes a race with the IO
thread to try and send the COM_QUIT before this happens (which would
need more debug_sync to get around). See THD::awake_no_mutex for
details as to the killing of the replica’s vio.

Notes:
- The MariaDB documentation does not make it clear that when one
  enables semi-sync replication it does not matter if one enables
  it first in the master or slave. Any order works.

Changes done:
- The rpl_semi_sync_slave_enabled variable is now a default value for
  when semisync is started. The variable does not anymore affect
  semisync if it is already running. This fixes the original reported
  bug.  Internally we now use repl_semisync_slave.get_slave_enabled()
  instead of rpl_semi_sync_slave_enabled. To check if semisync is
  active on should check the @@rpl_semi_sync_slave_status variable (as
  before).
- The semisync protocol conflicts in the way that the original
  MySQL/MariaDB client-server protocol was designed (client-server
  send and reply packets are strictly ordered and includes a packet
  number to allow one to check if a packet is lost). When using
  semi-sync the master and slave can send packets at 'any time', so
  packet numbering does not work. The 'solution' has been that each
  communication starts with packet number 1, but in some cases there
  is still a chance that the packet number check can fail.  Fixed by
  adding a flag (pkt_nr_can_be_reset) in the NET struct that one can
  use to signal that packet number checking should not be done. This
  is flag is set when semi-sync is used.
- Added Master_info::semi_sync_reply_enabled to allow one to configure
  some slaves with semisync and other other slaves without semisync.
  Removed global variable semi_sync_need_reply that would not work
  with multi-master.
- Repl_semi_sync_master::report_reply_packet() can now recognize
  the COM_QUIT packet from semisync slave and not give a
  "Read semi-sync reply magic number error" error for this case.
  The slave will be removed from the Ack listener.
- On Windows, don't stop semisync Ack listener just because one
  slave connection is using socket_id > FD_SETSIZE.
- Removed busy loop in Ack_receiver::run() by using
 "Self-pipe trick" to signal new slave and stop Ack_receiver.
- Changed some Repl_semi_sync_slave functions that always returns 0
  from int to void.
- Added Repl_semi_sync_slave::slave_reconnect().
- Removed dummy_function Repl_semi_sync_slave::reset_slave().
- Removed some duplicate semisync notes from the error log.
- Add test of "if (get_slave_enabled() && semi_sync_need_reply)"
  before calling Repl_semi_sync_slave::slave_reply().
  (Speeds up the code as we can skip all initializations).
- If epl_semisync_slave.slave_reply() fails, we disable semisync
  for that connection.
- We do not call semisync.switch_off() if there are no active slaves.
  Instead we check in Repl_semi_sync_master::commit_trx() if there are
  no active threads. This simplices the code.
- Changed assert() to DBUG_ASSERT() to ensure that the DBUG log is
  flushed in case of asserts.
- Removed the internal rpl_semi_sync_slave_status as it is not needed
  anymore. The @@rpl_semi_sync_slave_status status variable is now
  mapped to rpl_semi_sync_enabled.
- Removed rpl_semi_sync_slave_enabled  as it is not needed anymore.
  Repl_semi_sync_slave::get_slave_enabled() contains the active status.
- Added checking that we do not add a slave twice with
  Ack_receiver::add_slave(). This could happen with old code.
- Removed Repl_semi_sync_master::check_and_switch() as it is not
  needed anymore.
- Ensure that when we call Ack_receiver::remove_slave() that the slave
  is removed from the listener before function returns.
- Call listener.listen_on_sockets() outside of mutex for better
  performance and less contested mutex.
- Ensure that listening is ignoring newly added slaves when checking for
  responses.
- Fixed the master ack_receiver listener is not killed if there are no
  connected slaves (and thus stop semisync handling of future
  connections). This could happen if all slaves sockets where would be
  marked as unreliable.
- Added unlink() to base_ilist_iterator and remove() to
  I_List_iterator. This enables us to remove 'dead' slaves in
  Ack_recever::run().
- kill_zombie_dump_threads() now does killing of dump threads properly.
  - It can now kill several threads (should be impossible but could
    happen if IO slaves reconnects very fast).
  - We now wait until the dump thread is done before starting the
    dump.
- Added an error if kill_zombie_dump_threads() fails.
- Set thd->variables.server_id before calling
  kill_zombie_dump_threads(). This simplies the code.
- Added a lot of comments both in code and tests.
- Removed DBUG_EVALUATE_IF "failed_slave_start" as it is not used.

Test changes:
- rpl.rpl_session_var2 added which runs rpl.rpl_session_var test with
  semisync enabled.
- Some timings changed slight with startup of slave which caused
  rpl_binlog_dump_slave_gtid_state_info.text to fail as it checked the
  error log file before the slave had started properly. Fixed by
  adding wait_for_pattern_in_file.inc that allows waiting for the
  pattern to appear in the log file.
- Tests have been updated so that we first set
  rpl_semi_sync_master_enabled on the master and then set
  rpl_semi_sync_slave_enabled on the slaves (this is according to how
  the MariaDB documentation document how to setup semi-sync).
- Error text "Master server does not have semi-sync enabled" has been
  replaced with "Master server does not support semi-sync" for the
  case when the master supports semi-sync but semi-sync is not
  enabled.

Other things:
- Some trivial cleanups in Repl_semi_sync_master::update_sync_header().
- We should in 11.3 changed the default value for
  rpl-semi-sync-master-wait-no-slave from TRUE to FALSE as the TRUE
  does not make much sense as default. The main difference with using
  FALSE is that we do not wait for semisync Ack if there are no slave
  threads.  In the case of TRUE we wait once, which did not bring any
  notable benefits except slower startup of master configured for
  using semisync.

Co-author: Brandon Nesterenko <brandon.nesterenko@mariadb.com>

This solves the problem reported in MDEV-32960 where a new
slave may not be registered in time and the master disables
semi sync because of that.
  • Loading branch information
Michael Widenius authored and montywi committed Jan 23, 2024
1 parent ee7cc0a commit 7af50e4
Show file tree
Hide file tree
Showing 57 changed files with 1,168 additions and 357 deletions.
2 changes: 1 addition & 1 deletion include/mysql_com.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ typedef struct st_net {
char net_skip_rest_factor;
my_bool thread_specific_malloc;
unsigned char compress;
my_bool unused3; /* Please remove with the next incompatible ABI change. */
my_bool pkt_nr_can_be_reset;
/*
Pointer to query object in query cache, do not equal NULL (0) for
queries in cache that have not stored its results yet
Expand Down
40 changes: 33 additions & 7 deletions mysql-test/include/search_pattern_in_file.inc
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,15 @@
# Created: 2011-11-11 mleich
#

--error 0,1
perl;
use strict;
die "SEARCH_FILE not set" unless $ENV{SEARCH_FILE};
my @search_files= glob($ENV{SEARCH_FILE});
my $search_pattern= $ENV{SEARCH_PATTERN} or die "SEARCH_PATTERN not set";
my $search_range= $ENV{SEARCH_RANGE};
my $silent= $ENV{SEARCH_SILENT};
my $search_result= 0;
my $content;
foreach my $search_file (@search_files) {
open(FILE, '<', $search_file) || die("Can't open file $search_file: $!");
Expand Down Expand Up @@ -89,16 +92,39 @@ perl;
{
@matches=($content =~ /$search_pattern/gm);
}
my $res=@matches ? "FOUND " . scalar(@matches) : "NOT FOUND";
my $res;
if (@matches)
{
$res="FOUND " . scalar(@matches);
$search_result= 1;
}
else
{
$res= "NOT FOUND";
}
$ENV{SEARCH_FILE} =~ s{^.*?([^/\\]+)$}{$1};

if ($ENV{SEARCH_OUTPUT} eq "matches") {
foreach (@matches) {
print $_ . "\n";
}
} else {
print "$res /$search_pattern/ in $ENV{SEARCH_FILE}\n";
if (!$silent || $search_result)
{
if ($ENV{SEARCH_OUTPUT} eq "matches")
{
foreach (@matches)
{
print $_ . "\n";
}
}
else
{
print "$res /$search_pattern/ in $ENV{SEARCH_FILE}\n";
}
}
die "$ENV{SEARCH_ABORT}\n"
if $ENV{SEARCH_ABORT} && $res =~ /^$ENV{SEARCH_ABORT}/;
exit($search_result != 1);
EOF

let $SEARCH_RESULT= 1; # Found pattern
if ($errno)
{
let $SEARCH_RESULT= 0; # Did not find pattern
}
56 changes: 56 additions & 0 deletions mysql-test/include/wait_for_pattern_in_file.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# ==== Purpose ====
#
# Waits until pattern comes into log file or until a timeout is reached.
# This is a timeout wrapper for search_pattern_in_file.inc
#
#
# ==== Usage ====
#
# [--let $timeout= NUMBER in seconds]
# For other parameters, check search_pattern_in_file.inc

--let $wait_save_keep_include_silent=$keep_include_silent
--let $include_filename= wait_for_pattern_in_file.inc
--source include/begin_include_file.inc
--let $keep_include_silent= 1

let $_timeout= $timeout;
if (!$_timeout)
{
let $_timeout= 10;
if ($VALGRIND_TEST)
{
let $_timeout= 30;
}
}

let $_timeout_counter=`SELECT $_timeout * 10`;
let SEARCH_SILENT=1;

let $_continue= 1;
while ($_continue)
{
source include/search_pattern_in_file.inc;
if ($SEARCH_RESULT)
{
# Found match
let $_continue= 0;
}
if (!$SEARCH_RESULT)
{
dec $_timeout_counter;
if ($_timeout_counter == 1)
{
let $SEARCH_SILENT= 0;
}
if (!$_timeout_counter)
{
let $_continue= 0;
}
}
}

let SEARCH_SILENT=0;

--source include/end_include_file.inc
--let $keep_include_silent=$wait_save_keep_include_silent
2 changes: 1 addition & 1 deletion mysql-test/main/mysqld--help.result
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ The following specify which files/extra groups are read (specified before remain
The tracing level for semi-sync replication.
--rpl-semi-sync-master-wait-no-slave
Wait until timeout when no semi-synchronous replication
slave available (enabled by default).
slave is available.
(Defaults to on; use --skip-rpl-semi-sync-master-wait-no-slave to disable.)
--rpl-semi-sync-master-wait-point=name
Should transaction wait for semi-sync ack after having
Expand Down
13 changes: 10 additions & 3 deletions mysql-test/suite/binlog_encryption/rpl_semi_sync.result
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ call mtr.add_suppression("Read semi-sync reply");
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT.");
call mtr.add_suppression("mysqld: Got an error reading communication packets");
connection slave;
call mtr.add_suppression("Master server does not support semi-sync");
call mtr.add_suppression("Semi-sync slave .* reply");
call mtr.add_suppression("Slave SQL.*Request to stop slave SQL Thread received while applying a group that has non-transactional changes; waiting for completion of the group");
connection master;
Expand All @@ -26,7 +25,7 @@ set global rpl_semi_sync_slave_enabled= 0;
# Main test of semi-sync replication start here
#
connection master;
set global rpl_semi_sync_master_timeout= 60000;
set global rpl_semi_sync_master_timeout= 2000;
[ default state of semi-sync on master should be OFF ]
show variables like 'rpl_semi_sync_master_enabled';
Variable_name Value
Expand Down Expand Up @@ -161,11 +160,15 @@ connection slave;
# Test semi-sync master will switch OFF after one transaction
# timeout waiting for slave reply.
#
connection master;
show status like "Rpl_semi_sync_master_status";
Variable_name Value
Rpl_semi_sync_master_status ON
connection slave;
include/stop_slave.inc
connection master;
include/kill_binlog_dump_threads.inc
set global rpl_semi_sync_master_timeout= 5000;
set global rpl_semi_sync_master_timeout= 2000;
[ master status should be ON ]
show status like 'Rpl_semi_sync_master_no_tx';
Variable_name Value
Expand Down Expand Up @@ -315,6 +318,8 @@ include/kill_binlog_dump_threads.inc
connection slave;
include/start_slave.inc
connection master;
connection slave;
connection master;
create table t1 (a int) engine = ENGINE_TYPE;
insert into t1 values (1);
insert into t1 values (2), (3);
Expand Down Expand Up @@ -357,6 +362,8 @@ show status like 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status ON
connection master;
connection slave;
connection master;
[ master semi-sync should be ON ]
show status like 'Rpl_semi_sync_master_clients';
Variable_name Value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ CHANGE MASTER TO MASTER_USE_GTID=current_pos;
include/start_slave.inc
connection master;
"Test Case 1: Start binlog_dump to slave_server(#), pos(master-bin.000001, ###), using_gtid(1), gtid('')"
include/wait_for_pattern_in_file.inc
FOUND 1 /using_gtid\(1\), gtid\(\'\'\).*/ in mysqld.1.err
connection slave;
include/stop_slave.inc
CHANGE MASTER TO MASTER_USE_GTID=no;
include/start_slave.inc
connection master;
"Test Case 2: Start binlog_dump to slave_server(#), pos(master-bin.000001, ###), using_gtid(0), gtid('')"
include/wait_for_pattern_in_file.inc
FOUND 1 /using_gtid\(0\), gtid\(\'\'\).*/ in mysqld.1.err
CREATE TABLE t (f INT) ENGINE=INNODB;
INSERT INTO t VALUES(10);
Expand All @@ -25,6 +27,7 @@ CHANGE MASTER TO MASTER_USE_GTID=slave_pos;
include/start_slave.inc
connection master;
"Test Case 3: Start binlog_dump to slave_server(#), pos(master-bin.000001, ###), using_gtid(1), gtid('0-1-2')"
include/wait_for_pattern_in_file.inc
FOUND 1 /using_gtid\(1\), gtid\(\'0-1-2\'\).*/ in mysqld.1.err
SET @@SESSION.gtid_domain_id=10;
INSERT INTO t VALUES(20);
Expand All @@ -35,6 +38,7 @@ CHANGE MASTER TO MASTER_USE_GTID=slave_pos;
include/start_slave.inc
connection master;
"Test Case 4: Start binlog_dump to slave_server(#), pos(master-bin.000001, ###), using_gtid(1), gtid('0-1-2,10-1-1')"
include/wait_for_pattern_in_file.inc
FOUND 1 /using_gtid\(1\), gtid\(\'0-1-2,10-1-1\'\).*/ in mysqld.1.err
"===== Clean up ====="
connection slave;
Expand Down
4 changes: 4 additions & 0 deletions mysql-test/suite/rpl/r/rpl_circular_semi_sync.result
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
include/master-slave.inc
[connection master]
connection server_2;
call mtr.add_suppression("Timeout waiting for reply of binlog");
# Master server_1 and Slave server_2 initialization ...
connection server_2;
include/stop_slave.inc
Expand Down Expand Up @@ -40,6 +42,8 @@ set @@global.rpl_semi_sync_master_enabled = 1;
INSERT INTO t1(a) VALUES (2);
include/save_master_gtid.inc
connection server_1;
include/stop_slave.inc
include/start_slave.inc
#
# the successful sync is a required proof
#
Expand Down
3 changes: 3 additions & 0 deletions mysql-test/suite/rpl/r/rpl_delayed_slave.result
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ include/stop_slave.inc
# CHANGE MASTER TO MASTER_DELAY = 2*T
include/start_slave.inc
connection master;
INSERT INTO t1 VALUES ('Syncing slave', 5);
connection slave;
connection master;
INSERT INTO t1 VALUES (delay_on_slave(1), 6);
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it uses a system variable that may have a different value on the slave
Expand Down
13 changes: 10 additions & 3 deletions mysql-test/suite/rpl/r/rpl_semi_sync.result
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ call mtr.add_suppression("Read semi-sync reply");
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT.");
call mtr.add_suppression("mysqld: Got an error reading communication packets");
connection slave;
call mtr.add_suppression("Master server does not support semi-sync");
call mtr.add_suppression("Semi-sync slave .* reply");
call mtr.add_suppression("Slave SQL.*Request to stop slave SQL Thread received while applying a group that has non-transactional changes; waiting for completion of the group");
connection master;
Expand All @@ -26,7 +25,7 @@ set global rpl_semi_sync_slave_enabled= 0;
# Main test of semi-sync replication start here
#
connection master;
set global rpl_semi_sync_master_timeout= 60000;
set global rpl_semi_sync_master_timeout= 2000;
[ default state of semi-sync on master should be OFF ]
show variables like 'rpl_semi_sync_master_enabled';
Variable_name Value
Expand Down Expand Up @@ -161,11 +160,15 @@ connection slave;
# Test semi-sync master will switch OFF after one transaction
# timeout waiting for slave reply.
#
connection master;
show status like "Rpl_semi_sync_master_status";
Variable_name Value
Rpl_semi_sync_master_status ON
connection slave;
include/stop_slave.inc
connection master;
include/kill_binlog_dump_threads.inc
set global rpl_semi_sync_master_timeout= 5000;
set global rpl_semi_sync_master_timeout= 2000;
[ master status should be ON ]
show status like 'Rpl_semi_sync_master_no_tx';
Variable_name Value
Expand Down Expand Up @@ -315,6 +318,8 @@ include/kill_binlog_dump_threads.inc
connection slave;
include/start_slave.inc
connection master;
connection slave;
connection master;
create table t1 (a int) engine = ENGINE_TYPE;
insert into t1 values (1);
insert into t1 values (2), (3);
Expand Down Expand Up @@ -357,6 +362,8 @@ show status like 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status ON
connection master;
connection slave;
connection master;
[ master semi-sync should be ON ]
show status like 'Rpl_semi_sync_master_clients';
Variable_name Value
Expand Down
13 changes: 10 additions & 3 deletions mysql-test/suite/rpl/r/rpl_semi_sync_after_sync.result
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ call mtr.add_suppression("Read semi-sync reply");
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT.");
call mtr.add_suppression("mysqld: Got an error reading communication packets");
connection slave;
call mtr.add_suppression("Master server does not support semi-sync");
call mtr.add_suppression("Semi-sync slave .* reply");
call mtr.add_suppression("Slave SQL.*Request to stop slave SQL Thread received while applying a group that has non-transactional changes; waiting for completion of the group");
connection master;
Expand All @@ -27,7 +26,7 @@ set global rpl_semi_sync_slave_enabled= 0;
# Main test of semi-sync replication start here
#
connection master;
set global rpl_semi_sync_master_timeout= 60000;
set global rpl_semi_sync_master_timeout= 2000;
[ default state of semi-sync on master should be OFF ]
show variables like 'rpl_semi_sync_master_enabled';
Variable_name Value
Expand Down Expand Up @@ -162,11 +161,15 @@ connection slave;
# Test semi-sync master will switch OFF after one transaction
# timeout waiting for slave reply.
#
connection master;
show status like "Rpl_semi_sync_master_status";
Variable_name Value
Rpl_semi_sync_master_status ON
connection slave;
include/stop_slave.inc
connection master;
include/kill_binlog_dump_threads.inc
set global rpl_semi_sync_master_timeout= 5000;
set global rpl_semi_sync_master_timeout= 2000;
[ master status should be ON ]
show status like 'Rpl_semi_sync_master_no_tx';
Variable_name Value
Expand Down Expand Up @@ -316,6 +319,8 @@ include/kill_binlog_dump_threads.inc
connection slave;
include/start_slave.inc
connection master;
connection slave;
connection master;
create table t1 (a int) engine = ENGINE_TYPE;
insert into t1 values (1);
insert into t1 values (2), (3);
Expand Down Expand Up @@ -358,6 +363,8 @@ show status like 'Rpl_semi_sync_slave_status';
Variable_name Value
Rpl_semi_sync_slave_status ON
connection master;
connection slave;
connection master;
[ master semi-sync should be ON ]
show status like 'Rpl_semi_sync_master_clients';
Variable_name Value
Expand Down
Loading

0 comments on commit 7af50e4

Please sign in to comment.