用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

10070 lines
361 KiB

/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/**
@addtogroup Replication
@{
@file sql/rpl_slave.cc
@brief Code to run the io thread and the sql thread on the
replication slave.
*/
#include "sql/rpl_slave.h"
#include "my_config.h"
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/compression.h"
#include "include/mutex_lock.h"
#include "mysql/components/services/log_builtins.h"
#include "mysql/components/services/psi_memory_bits.h"
#include "mysql/components/services/psi_stage_bits.h"
#include "mysql/plugin.h"
#include "mysql/psi/mysql_cond.h"
#include "mysql/psi/mysql_mutex.h"
#include "mysql/psi/psi_base.h"
#include "mysql/status_var.h"
#include "sql/rpl_channel_service_interface.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <algorithm>
#include <atomic>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "errmsg.h" // CR_*
#include "lex_string.h"
#include "libbinlogevents/include/binlog_event.h"
#include "libbinlogevents/include/control_events.h"
#include "libbinlogevents/include/debug_vars.h"
#include "m_ctype.h"
#include "m_string.h"
#include "my_bitmap.h" // MY_BITMAP
#include "my_byteorder.h"
#include "my_command.h"
#include "my_compiler.h"
#include "my_dbug.h"
#include "my_dir.h"
#include "my_io.h"
#include "my_loglevel.h"
#include "my_macros.h"
#include "my_sys.h"
#include "my_systime.h"
#include "my_thread_local.h" // thread_local_key_t
#include "mysql.h" // MYSQL
#include "mysql/psi/mysql_file.h"
#include "mysql/psi/mysql_memory.h"
#include "mysql/psi/mysql_thread.h"
#include "mysql/service_mysql_alloc.h"
#include "mysql/thread_type.h"
#include "mysql_com.h"
#include "mysqld_error.h"
#include "pfs_thread_provider.h"
#include "prealloced_array.h"
#include "sql-common/net_ns.h"
#include "sql/auth/auth_acls.h"
#include "sql/auth/sql_security_ctx.h"
#include "sql/binlog.h"
#include "sql/binlog_reader.h"
#include "sql/clone_handler.h" // is_provisioning
#include "sql/current_thd.h"
#include "sql/debug_sync.h" // DEBUG_SYNC
#include "sql/derror.h" // ER_THD
#include "sql/dynamic_ids.h" // Server_ids
#include "sql/handler.h"
#include "sql/item.h"
#include "sql/log.h"
#include "sql/log_event.h" // Rotate_log_event
#include "sql/mdl.h"
#include "sql/mysqld.h" // ER
#include "sql/mysqld_thd_manager.h" // Global_THD_manager
#include "sql/protocol.h"
#include "sql/protocol_classic.h"
#include "sql/psi_memory_key.h"
#include "sql/query_options.h"
#include "sql/rpl_applier_reader.h"
#include "sql/rpl_filter.h"
#include "sql/rpl_group_replication.h"
#include "sql/rpl_gtid.h"
#include "sql/rpl_handler.h" // RUN_HOOK
#include "sql/rpl_info.h"
#include "sql/rpl_info_factory.h" // Rpl_info_factory
#include "sql/rpl_info_handler.h"
#include "sql/rpl_mi.h"
#include "sql/rpl_msr.h" // Multisource_info
#include "sql/rpl_mts_submode.h"
#include "sql/rpl_reporting.h"
#include "sql/rpl_rli.h" // Relay_log_info
#include "sql/rpl_rli_pdb.h" // Slave_worker
#include "sql/rpl_slave_commit_order_manager.h" // Commit_order_manager
#include "sql/rpl_slave_until_options.h"
#include "sql/rpl_trx_boundary_parser.h"
#include "sql/rpl_utility.h"
#include "sql/sql_backup_lock.h" // is_instance_backup_locked
#include "sql/sql_class.h" // THD
#include "sql/sql_const.h"
#include "sql/sql_error.h"
#include "sql/sql_lex.h"
#include "sql/sql_list.h"
#include "sql/sql_parse.h" // execute_init_command
#include "sql/sql_plugin.h" // opt_plugin_dir_ptr
#include "sql/system_variables.h"
#include "sql/table.h"
#include "sql/transaction.h" // trans_begin
#include "sql/transaction_info.h"
#include "sql_common.h" // end_server
#include "sql_string.h"
#include "typelib.h"
struct mysql_cond_t;
struct mysql_mutex_t;
using binary_log::checksum_crc32;
using binary_log::Log_event_header;
using std::max;
using std::min;
#define FLAGSTR(V, F) ((V) & (F) ? #F " " : "")
/*
a parameter of sql_slave_killed() to defer the killed status
*/
#define SLAVE_WAIT_GROUP_DONE 60
bool use_slave_mask = 0;
MY_BITMAP slave_error_mask;
char slave_skip_error_names[SHOW_VAR_FUNC_BUFF_SIZE];
char *slave_load_tmpdir = nullptr;
bool replicate_same_server_id;
ulonglong relay_log_space_limit = 0;
const char *relay_log_index = nullptr;
const char *relay_log_basename = nullptr;
/*
MTS load-ballancing parameter.
Max length of one MTS Worker queue. The value also determines the size
of Relay_log_info::gaq (see @c slave_start_workers()).
It can be set to any value in [1, ULONG_MAX - 1] range.
*/
const ulong mts_slave_worker_queue_len_max = 16384;
/*
Statistics go to the error log every # of seconds when
--log_error_verbosity > 2
*/
const long mts_online_stat_period = 60 * 2;
/*
MTS load-ballancing parameter.
Time unit in microsecs to sleep by MTS Coordinator to avoid extra thread
signalling in the case of Worker queues are close to be filled up.
*/
const ulong mts_coordinator_basic_nap = 5;
/*
MTS load-ballancing parameter.
Percent of Worker queue size at which Worker is considered to become
hungry.
C enqueues --+ . underrun level
V "
+----------+-+------------------+--------------+
| empty |.|::::::::::::::::::|xxxxxxxxxxxxxx| ---> Worker dequeues
+----------+-+------------------+--------------+
Like in the above diagram enqueuing to the x-d area would indicate
actual underrruning by Worker.
*/
const ulong mts_worker_underrun_level = 10;
/*
When slave thread exits, we need to remember the temporary tables so we
can re-use them on slave start.
TODO: move the vars below under Master_info
*/
int disconnect_slave_event_count = 0, abort_slave_event_count = 0;
static thread_local Master_info *RPL_MASTER_INFO = nullptr;
enum enum_slave_reconnect_actions {
SLAVE_RECON_ACT_REG = 0,
SLAVE_RECON_ACT_DUMP = 1,
SLAVE_RECON_ACT_EVENT = 2,
SLAVE_RECON_ACT_MAX
};
enum enum_slave_reconnect_messages {
SLAVE_RECON_MSG_WAIT = 0,
SLAVE_RECON_MSG_KILLED_WAITING = 1,
SLAVE_RECON_MSG_AFTER = 2,
SLAVE_RECON_MSG_FAILED = 3,
SLAVE_RECON_MSG_COMMAND = 4,
SLAVE_RECON_MSG_KILLED_AFTER = 5,
SLAVE_RECON_MSG_MAX
};
static const char *reconnect_messages[SLAVE_RECON_ACT_MAX][SLAVE_RECON_MSG_MAX] =
{{"Waiting to reconnect after a failed registration on master",
"Slave I/O thread killed while waiting to reconnect after a failed \
registration on master",
"Reconnecting after a failed registration on master",
"failed registering on master, reconnecting to try again, \
log '%s' at position %s",
"COM_REGISTER_SLAVE",
"Slave I/O thread killed during or after reconnect"},
{"Waiting to reconnect after a failed binlog dump request",
"Slave I/O thread killed while retrying master dump",
"Reconnecting after a failed binlog dump request",
"failed dump request, reconnecting to try again, log '%s' at position %s",
"COM_BINLOG_DUMP", "Slave I/O thread killed during or after reconnect"},
{"Waiting to reconnect after a failed master event read",
"Slave I/O thread killed while waiting to reconnect after a failed read",
"Reconnecting after a failed master event read",
"Slave I/O thread: Failed reading log event, reconnecting to retry, \
log '%s' at position %s",
"",
"Slave I/O thread killed during or after a reconnect done to recover from \
failed read"}};
enum enum_slave_apply_event_and_update_pos_retval {
SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK = 0,
SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPLY_ERROR = 1,
SLAVE_APPLY_EVENT_AND_UPDATE_POS_UPDATE_POS_ERROR = 2,
SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPEND_JOB_ERROR = 3,
SLAVE_APPLY_EVENT_AND_UPDATE_POS_MAX
};
static int process_io_rotate(Master_info *mi, Rotate_log_event *rev);
static bool wait_for_relay_log_space(Relay_log_info *rli);
static inline bool io_slave_killed(THD *thd, Master_info *mi);
static inline bool is_autocommit_off_and_infotables(THD *thd);
static int init_slave_thread(THD *thd, SLAVE_THD_TYPE thd_type);
static void print_slave_skip_errors(void);
static int safe_connect(THD *thd, MYSQL *mysql, Master_info *mi);
static int safe_reconnect(THD *thd, MYSQL *mysql, Master_info *mi,
bool suppress_warnings);
static int connect_to_master(THD *thd, MYSQL *mysql, Master_info *mi,
bool reconnect, bool suppress_warnings);
static int get_master_version_and_clock(MYSQL *mysql, Master_info *mi);
static int get_master_uuid(MYSQL *mysql, Master_info *mi);
int io_thread_init_commands(MYSQL *mysql, Master_info *mi);
static int terminate_slave_thread(THD *thd, mysql_mutex_t *term_lock,
mysql_cond_t *term_cond,
std::atomic<uint> *slave_running,
ulong *stop_wait_timeout, bool need_lock_term,
bool force = false);
static bool check_io_slave_killed(THD *thd, Master_info *mi, const char *info);
static int mts_event_coord_cmp(LOG_POS_COORD *id1, LOG_POS_COORD *id2);
static int check_slave_sql_config_conflict(const Relay_log_info *rli);
/*
Applier thread InnoDB priority.
When two transactions conflict inside InnoDB, the one with
greater priority wins.
@param thd Thread handler for slave
@param priority Thread priority
*/
static void set_thd_tx_priority(THD *thd, int priority) {
DBUG_TRACE;
DBUG_ASSERT(thd->system_thread == SYSTEM_THREAD_SLAVE_SQL ||
thd->system_thread == SYSTEM_THREAD_SLAVE_WORKER);
thd->thd_tx_priority = priority;
DBUG_EXECUTE_IF("dbug_set_high_prio_sql_thread",
{ thd->thd_tx_priority = 1; });
}
/*
Function to set the slave's max_allowed_packet based on the value
of slave_max_allowed_packet.
@in_param thd Thread handler for slave
@in_param mysql MySQL connection handle
*/
static void set_slave_max_allowed_packet(THD *thd, MYSQL *mysql) {
DBUG_TRACE;
// thd and mysql must be valid
DBUG_ASSERT(thd && mysql);
thd->variables.max_allowed_packet = slave_max_allowed_packet;
/*
Adding MAX_LOG_EVENT_HEADER_LEN to the max_packet_size on the I/O
thread and the mysql->option max_allowed_packet, since a
replication event can become this much larger than
the corresponding packet (query) sent from client to master.
*/
thd->get_protocol_classic()->set_max_packet_size(slave_max_allowed_packet +
MAX_LOG_EVENT_HEADER);
/*
Skipping the setting of mysql->net.max_packet size to slave
max_allowed_packet since this is done during mysql_real_connect.
*/
mysql->options.max_allowed_packet =
slave_max_allowed_packet + MAX_LOG_EVENT_HEADER;
}
/*
Find out which replications threads are running
SYNOPSIS
init_thread_mask()
mask Return value here
mi master_info for slave
inverse If set, returns which threads are not running
IMPLEMENTATION
Get a bit mask for which threads are running so that we can later restart
these threads.
RETURN
mask If inverse == 0, running threads
If inverse == 1, stopped threads
*/
void init_thread_mask(int *mask, Master_info *mi, bool inverse) {
bool set_io = mi->slave_running, set_sql = mi->rli->slave_running;
int tmp_mask = 0;
DBUG_TRACE;
if (set_io) tmp_mask |= SLAVE_IO;
if (set_sql) tmp_mask |= SLAVE_SQL;
if (inverse) tmp_mask ^= (SLAVE_IO | SLAVE_SQL);
*mask = tmp_mask;
}
/*
lock_slave_threads()
*/
void lock_slave_threads(Master_info *mi) {
DBUG_TRACE;
// protection against mixed locking order (see header)
mi->channel_assert_some_wrlock();
// TODO: see if we can do this without dual mutex
mysql_mutex_lock(&mi->run_lock);
mysql_mutex_lock(&mi->rli->run_lock);
}
/*
unlock_slave_threads()
*/
void unlock_slave_threads(Master_info *mi) {
DBUG_TRACE;
// TODO: see if we can do this without dual mutex
mysql_mutex_unlock(&mi->rli->run_lock);
mysql_mutex_unlock(&mi->run_lock);
}
#ifdef HAVE_PSI_INTERFACE
static PSI_memory_key key_memory_rli_mts_coor;
static PSI_thread_key key_thread_slave_io, key_thread_slave_sql,
key_thread_slave_worker;
static PSI_thread_info all_slave_threads[] = {
{&key_thread_slave_io, "slave_io", PSI_FLAG_SINGLETON, 0, PSI_DOCUMENT_ME},
{&key_thread_slave_sql, "slave_sql", PSI_FLAG_SINGLETON, 0,
PSI_DOCUMENT_ME},
{&key_thread_slave_worker, "slave_worker", PSI_FLAG_SINGLETON, 0,
PSI_DOCUMENT_ME}};
static PSI_memory_info all_slave_memory[] = {{&key_memory_rli_mts_coor,
"Relay_log_info::mts_coor", 0, 0,
PSI_DOCUMENT_ME}};
static void init_slave_psi_keys(void) {
const char *category = "sql";
int count;
count = static_cast<int>(array_elements(all_slave_threads));
mysql_thread_register(category, all_slave_threads, count);
count = static_cast<int>(array_elements(all_slave_memory));
mysql_memory_register(category, all_slave_memory, count);
}
#endif /* HAVE_PSI_INTERFACE */
/* Initialize slave structures */
int init_slave() {
DBUG_TRACE;
int error = 0;
int thread_mask = SLAVE_SQL | SLAVE_IO;
Master_info *mi = nullptr;
#ifdef HAVE_PSI_INTERFACE
init_slave_psi_keys();
#endif
/*
This is called when mysqld starts. Before client connections are
accepted. However bootstrap may conflict with us if it does START SLAVE.
So it's safer to take the lock.
*/
channel_map.wrlock();
RPL_MASTER_INFO = nullptr;
/*
Create slave info objects by reading repositories of individual
channels and add them into channel_map
*/
if ((error = Rpl_info_factory::create_slave_info_objects(
opt_mi_repository_id, opt_rli_repository_id, thread_mask,
&channel_map)))
LogErr(ERROR_LEVEL,
ER_RPL_SLAVE_FAILED_TO_CREATE_OR_RECOVER_INFO_REPOSITORIES);
#ifndef DBUG_OFF
/* @todo: Print it for all the channels */
{
Master_info *default_mi;
default_mi = channel_map.get_default_channel_mi();
if (default_mi && default_mi->rli) {
DBUG_PRINT("info",
("init group master %s %lu group relay %s %lu event %s %lu\n",
default_mi->rli->get_group_master_log_name(),
(ulong)default_mi->rli->get_group_master_log_pos(),
default_mi->rli->get_group_relay_log_name(),
(ulong)default_mi->rli->get_group_relay_log_pos(),
default_mi->rli->get_event_relay_log_name(),
(ulong)default_mi->rli->get_event_relay_log_pos()));
}
}
#endif
if (get_gtid_mode(GTID_MODE_LOCK_CHANNEL_MAP) == GTID_MODE_OFF) {
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
Master_info *mi = it->second;
if (mi != nullptr && mi->is_auto_position()) {
LogErr(WARNING_LEVEL,
ER_RPL_SLAVE_AUTO_POSITION_IS_1_AND_GTID_MODE_IS_OFF,
mi->get_channel(), mi->get_channel());
}
}
}
if (check_slave_sql_config_conflict(nullptr)) {
error = 1;
goto err;
}
/*
Loop through the channel_map and start slave threads for each channel.
*/
if (!opt_skip_slave_start) {
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
/* If server id is not set, start_slave_thread() will say it */
if (Master_info::is_configured(mi) && mi->rli->inited) {
/* same as in start_slave() cache the global var values into rli's
* members */
mi->rli->opt_slave_parallel_workers = opt_mts_slave_parallel_workers;
mi->rli->checkpoint_group = opt_mts_checkpoint_group;
if (mts_parallel_option == MTS_PARALLEL_TYPE_DB_NAME)
mi->rli->channel_mts_submode = MTS_PARALLEL_TYPE_DB_NAME;
else
mi->rli->channel_mts_submode = MTS_PARALLEL_TYPE_LOGICAL_CLOCK;
if (start_slave_threads(true /*need_lock_slave=true*/,
false /*wait_for_start=false*/, mi,
thread_mask)) {
LogErr(ERROR_LEVEL, ER_FAILED_TO_START_SLAVE_THREAD,
mi->get_channel());
}
} else {
LogErr(INFORMATION_LEVEL, ER_FAILED_TO_START_SLAVE_THREAD,
mi->get_channel());
}
}
}
err:
channel_map.unlock();
if (error) LogErr(INFORMATION_LEVEL, ER_SLAVE_NOT_STARTED_ON_SOME_CHANNELS);
return error;
}
/**
Function to start a slave for all channels.
Used in Multisource replication.
@param[in] thd THD object of the client.
@retval false success
@retval true error
@todo It is good to continue to start other channels
when a slave start failed for other channels.
@todo The problem with the below code is if the slave is already
stared, we would multple warnings called
"Slave was already running" for each channel.
A nice warning message would be to add
"Slave for channel '%s" was already running"
but error messages are in different languages and cannot be tampered
with so, we have to handle it case by case basis, whether
only default channel exists or not and properly continue with
starting other channels if one channel fails clearly giving
an error message by displaying failed channels.
*/
bool start_slave(THD *thd) {
DBUG_TRACE;
Master_info *mi;
bool channel_configured, error = false;
if (channel_map.get_num_instances() == 1) {
mi = channel_map.get_default_channel_mi();
DBUG_ASSERT(mi);
if (start_slave(thd, &thd->lex->slave_connection, &thd->lex->mi,
thd->lex->slave_thd_opt, mi, true))
return true;
} else {
/*
Users cannot start more than one channel's applier thread
if sql_slave_skip_counter > 0. It throws an error to the session.
*/
mysql_mutex_lock(&LOCK_sql_slave_skip_counter);
/* sql_slave_skip_counter > 0 && !(START SLAVE IO_THREAD) */
if (sql_slave_skip_counter > 0 && !(thd->lex->slave_thd_opt & SLAVE_IO)) {
my_error(ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER, MYF(0));
mysql_mutex_unlock(&LOCK_sql_slave_skip_counter);
return true;
}
mysql_mutex_unlock(&LOCK_sql_slave_skip_counter);
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
channel_configured =
mi && // Master_info exists
(mi->inited || mi->reset) // It is inited or was reset
&& mi->host[0]; // host is set
if (channel_configured) {
if (start_slave(thd, &thd->lex->slave_connection, &thd->lex->mi,
thd->lex->slave_thd_opt, mi, true)) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_CANT_START_SLAVE_FOR_CHANNEL,
mi->get_channel());
error = true;
}
}
}
}
if (!error) {
/* no error */
my_ok(thd);
}
return error;
}
/**
Function to stop a slave for all channels.
Used in Multisource replication.
@param[in] thd THD object of the client.
@return
@retval 0 success
@retval 1 error
@todo It is good to continue to stop other channels
when a slave start failed for other channels.
*/
int stop_slave(THD *thd) {
DBUG_TRACE;
bool push_temp_table_warning = true;
Master_info *mi = nullptr;
int error = 0;
if (channel_map.get_num_instances() == 1) {
mi = channel_map.get_default_channel_mi();
DBUG_ASSERT(!strcmp(mi->get_channel(), channel_map.get_default_channel()));
error = stop_slave(thd, mi, 1, false /*for_one_channel*/,
&push_temp_table_warning);
} else {
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
if (Master_info::is_configured(mi)) {
if (stop_slave(thd, mi, 1, false /*for_one_channel*/,
&push_temp_table_warning)) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_CANT_STOP_SLAVE_FOR_CHANNEL,
mi->get_channel());
error = 1;
}
}
}
}
if (!error) {
/* no error */
my_ok(thd);
}
return error;
}
/**
Entry point to the START SLAVE command. The function
decides to start replication threads on several channels
or a single given channel.
@param[in] thd the client thread carrying the command.
@return
@retval false ok
@retval true not ok.
*/
bool start_slave_cmd(THD *thd) {
DBUG_TRACE;
Master_info *mi;
LEX *lex = thd->lex;
bool res = true; /* default, an error */
DEBUG_SYNC(thd, "begin_start_slave");
channel_map.wrlock();
DEBUG_SYNC(thd, "after_locking_channel_map_in_start_slave");
if (!is_slave_configured()) {
my_error(ER_SLAVE_CONFIGURATION, MYF(0));
goto err;
}
if (!lex->mi.for_channel) {
/*
If slave_until options are provided when multiple channels exist
without explicitly providing FOR CHANNEL clause, error out.
*/
if (lex->mi.slave_until && channel_map.get_num_instances() > 1) {
my_error(ER_SLAVE_MULTIPLE_CHANNELS_CMD, MYF(0));
goto err;
}
res = start_slave(thd);
} else {
mi = channel_map.get_mi(lex->mi.channel);
/*
If the channel being used is a group replication channel we need to
disable this command here as, in some cases, group replication does not
support them.
For channel group_replication_applier we disable START SLAVE [IO_THREAD]
command.
For channel group_replication_recovery we disable START SLAVE command
and its two thread variants.
*/
if (mi &&
channel_map.is_group_replication_channel_name(mi->get_channel()) &&
((!thd->lex->slave_thd_opt || (thd->lex->slave_thd_opt & SLAVE_IO)) ||
(!(channel_map.is_group_replication_channel_name(mi->get_channel(),
true)) &&
(thd->lex->slave_thd_opt & SLAVE_SQL)))) {
const char *command = "START SLAVE FOR CHANNEL";
if (thd->lex->slave_thd_opt & SLAVE_IO)
command = "START SLAVE IO_THREAD FOR CHANNEL";
else if (thd->lex->slave_thd_opt & SLAVE_SQL)
command = "START SLAVE SQL_THREAD FOR CHANNEL";
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0), command,
mi->get_channel(), command);
goto err;
}
if (mi)
res = start_slave(thd, &thd->lex->slave_connection, &thd->lex->mi,
thd->lex->slave_thd_opt, mi, true);
else if (strcmp(channel_map.get_default_channel(), lex->mi.channel))
my_error(ER_SLAVE_CHANNEL_DOES_NOT_EXIST, MYF(0), lex->mi.channel);
if (!res) my_ok(thd);
}
err:
channel_map.unlock();
return res;
}
/**
Entry point for the STOP SLAVE command. This function stops replication
threads for all channels or a single channel based on the command
options supplied.
@param[in] thd the client thread.
@return
@retval false ok
@retval true not ok.
*/
bool stop_slave_cmd(THD *thd) {
DBUG_TRACE;
Master_info *mi;
bool push_temp_table_warning = true;
LEX *lex = thd->lex;
bool res = true; /*default, an error */
channel_map.rdlock();
if (!is_slave_configured()) {
my_error(ER_SLAVE_CONFIGURATION, MYF(0));
channel_map.unlock();
return true;
}
MDL_lock_guard backup_sentry{thd};
/* During provisioning we stop slave after acquiring backup lock. */
if (!Clone_handler::is_provisioning() &&
(!thd->lex->slave_thd_opt || (thd->lex->slave_thd_opt & SLAVE_SQL))) {
if (backup_sentry.lock(MDL_key::BACKUP_LOCK, MDL_INTENTION_EXCLUSIVE)) {
my_error(ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP, MYF(0));
channel_map.unlock();
return true;
}
}
if (!lex->mi.for_channel)
res = stop_slave(thd);
else {
mi = channel_map.get_mi(lex->mi.channel);
/*
If the channel being used is a group replication channel we need to
disable this command here as, in some cases, group replication does not
support them.
For channel group_replication_applier we disable STOP SLAVE [IO_THREAD]
command.
For channel group_replication_recovery we disable STOP SLAVE command
and its two thread variants.
*/
if (mi &&
channel_map.is_group_replication_channel_name(mi->get_channel()) &&
((!thd->lex->slave_thd_opt || (thd->lex->slave_thd_opt & SLAVE_IO)) ||
(!(channel_map.is_group_replication_channel_name(mi->get_channel(),
true)) &&
(thd->lex->slave_thd_opt & SLAVE_SQL)))) {
const char *command = "STOP SLAVE FOR CHANNEL";
if (thd->lex->slave_thd_opt & SLAVE_IO)
command = "STOP SLAVE IO_THREAD FOR CHANNEL";
else if (thd->lex->slave_thd_opt & SLAVE_SQL)
command = "STOP SLAVE SQL_THREAD FOR CHANNEL";
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0), command,
mi->get_channel(), command);
channel_map.unlock();
return true;
}
if (mi)
res = stop_slave(thd, mi, 1 /*net report */, true /*for_one_channel*/,
&push_temp_table_warning);
else if (strcmp(channel_map.get_default_channel(), lex->mi.channel))
my_error(ER_SLAVE_CHANNEL_DOES_NOT_EXIST, MYF(0), lex->mi.channel);
}
channel_map.unlock();
DBUG_EXECUTE_IF("stop_slave_dont_release_backup_lock", {
const char signal[] = "now SIGNAL slave_acquired_backup_lock";
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(signal)));
const char wait_for[] = "now WAIT_FOR tried_to_lock_instance_for_backup";
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(wait_for)));
});
return res;
}
/**
Parse the given relay log and identify the rotate event from the master.
Ignore the Format description event, Previous_gtid log event and ignorable
events within the relay log. When a rotate event is found check if it is a
rotate that is originated from the master or not based on the server_id. If
the rotate is from slave or if it is a fake rotate event ignore the event.
If any other events are encountered apart from the above events generate an
error. From the rotate event extract the master's binary log name and
position.
@param filename
Relay log name which needs to be parsed.
@param[out] master_log_file
Set the master_log_file to the log file name that is extracted from
rotate event. The master_log_file should contain string of len
FN_REFLEN.
@param[out] master_log_pos
Set the master_log_pos to the log position extracted from rotate
event.
@retval FOUND_ROTATE: When rotate event is found in the relay log
@retval NOT_FOUND_ROTATE: When rotate event is not found in the relay log
@retval ERROR: On error
*/
enum enum_read_rotate_from_relay_log_status {
FOUND_ROTATE,
NOT_FOUND_ROTATE,
ERROR
};
static enum_read_rotate_from_relay_log_status read_rotate_from_relay_log(
char *filename, char *master_log_file, my_off_t *master_log_pos) {
DBUG_TRACE;
Relaylog_file_reader relaylog_file_reader(opt_slave_sql_verify_checksum);
if (relaylog_file_reader.open(filename)) {
LogErr(ERROR_LEVEL, ER_RPL_RECOVERY_ERROR,
relaylog_file_reader.get_error_str());
return ERROR;
}
Log_event *ev = nullptr;
bool done = false;
enum_read_rotate_from_relay_log_status ret = NOT_FOUND_ROTATE;
while (!done && (ev = relaylog_file_reader.read_event_object()) != nullptr) {
DBUG_PRINT("info", ("Read event of type %s", ev->get_type_str()));
switch (ev->get_type_code()) {
case binary_log::FORMAT_DESCRIPTION_EVENT:
break;
case binary_log::ROTATE_EVENT:
/*
Check for rotate event from the master. Ignore the ROTATE event if it
is a fake rotate event with server_id=0.
*/
if (ev->server_id && ev->server_id != ::server_id) {
Rotate_log_event *rotate_ev = (Rotate_log_event *)ev;
DBUG_ASSERT(FN_REFLEN >= rotate_ev->ident_len + 1);
memcpy(master_log_file, rotate_ev->new_log_ident,
rotate_ev->ident_len + 1);
*master_log_pos = rotate_ev->pos;
ret = FOUND_ROTATE;
done = true;
}
break;
case binary_log::PREVIOUS_GTIDS_LOG_EVENT:
break;
case binary_log::IGNORABLE_LOG_EVENT:
break;
default:
LogErr(ERROR_LEVEL, ER_RPL_RECOVERY_NO_ROTATE_EVENT_FROM_MASTER);
ret = ERROR;
done = true;
break;
}
delete ev;
}
if (relaylog_file_reader.has_fatal_error()) {
LogErr(ERROR_LEVEL, ER_RPL_RECOVERY_ERROR_READ_RELAY_LOG, -1);
return ERROR;
}
return ret;
}
/**
Reads relay logs one by one starting from the first relay log. Looks for
the first rotate event from the master. If rotate is not found in the relay
log search continues to next relay log. If rotate event from master is
found then the extracted master_log_file and master_log_pos are used to set
rli->group_master_log_name and rli->group_master_log_pos. If an error has
occurred the error code is retuned back.
@param rli
Relay_log_info object to read relay log files and to set
group_master_log_name and group_master_log_pos.
@retval 0 On success
@retval 1 On failure
*/
static int find_first_relay_log_with_rotate_from_master(Relay_log_info *rli) {
DBUG_TRACE;
int error = 0;
LOG_INFO linfo;
bool got_rotate_from_master = false;
int pos;
char master_log_file[FN_REFLEN];
my_off_t master_log_pos = 0;
if (channel_map.is_group_replication_channel_name(rli->get_channel())) {
LogErr(INFORMATION_LEVEL,
ER_RPL_RECOVERY_SKIPPED_GROUP_REPLICATION_CHANNEL);
goto err;
}
for (pos = rli->relay_log.find_log_pos(&linfo, nullptr, true); !pos;
pos = rli->relay_log.find_next_log(&linfo, true)) {
switch (read_rotate_from_relay_log(linfo.log_file_name, master_log_file,
&master_log_pos)) {
case ERROR:
error = 1;
break;
case FOUND_ROTATE:
got_rotate_from_master = true;
break;
case NOT_FOUND_ROTATE:
break;
}
if (error || got_rotate_from_master) break;
}
if (pos == LOG_INFO_IO) {
error = 1;
LogErr(ERROR_LEVEL, ER_RPL_RECOVERY_IO_ERROR_READING_RELAY_LOG_INDEX);
goto err;
}
if (pos == LOG_INFO_EOF) {
error = 1;
LogErr(ERROR_LEVEL, ER_RPL_RECOVERY_NO_ROTATE_EVENT_FROM_MASTER);
goto err;
}
if (!error && got_rotate_from_master) {
rli->set_group_master_log_name(master_log_file);
rli->set_group_master_log_pos(master_log_pos);
}
err:
return error;
}
/*
Updates the master info based on the information stored in the
relay info and ignores relay logs previously retrieved by the IO
thread, which thus starts fetching again based on to the
master_log_pos and master_log_name. Eventually, the old
relay logs will be purged by the normal purge mechanism.
When GTID's are enabled the "Retrieved GTID" set should be cleared
so that partial read events are discarded and they are
fetched once again
@param mi pointer to Master_info instance
*/
static void recover_relay_log(Master_info *mi) {
Relay_log_info *rli = mi->rli;
// Set Receiver Thread's positions as per the recovered Applier Thread.
mi->set_master_log_pos(
max<ulonglong>(BIN_LOG_HEADER_SIZE, rli->get_group_master_log_pos()));
mi->set_master_log_name(rli->get_group_master_log_name());
LogErr(WARNING_LEVEL, ER_RPL_RECOVERY_FILE_MASTER_POS_INFO,
(ulong)mi->get_master_log_pos(), mi->get_master_log_name(),
mi->get_for_channel_str(), rli->get_group_relay_log_pos(),
rli->get_group_relay_log_name());
// Start with a fresh relay log.
rli->set_group_relay_log_name(rli->relay_log.get_log_fname());
rli->set_group_relay_log_pos(BIN_LOG_HEADER_SIZE);
/*
Clear the retrieved GTID set so that events that are written partially
will be fetched again.
*/
if (mi->get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_ON &&
!channel_map.is_group_replication_channel_name(rli->get_channel())) {
rli->get_sid_lock()->wrlock();
(const_cast<Gtid_set *>(rli->get_gtid_set()))->clear_set_and_sid_map();
rli->get_sid_lock()->unlock();
}
}
/*
Updates the master info based on the information stored in the
relay info and ignores relay logs previously retrieved by the IO
thread, which thus starts fetching again based on to the
master_log_pos and master_log_name. Eventually, the old
relay logs will be purged by the normal purge mechanism.
There can be a special case where rli->group_master_log_name and
rli->group_master_log_pos are not intialized, as the sql thread was never
started at all. In those cases all the existing relay logs are parsed
starting from the first one and the initial rotate event that was received
from the master is identified. From the rotate event master_log_name and
master_log_pos are extracted and they are set to rli->group_master_log_name
and rli->group_master_log_pos.
In the feature, we should improve this routine in order to avoid throwing
away logs that are safely stored in the disk. Note also that this recovery
routine relies on the correctness of the relay-log.info and only tolerates
coordinate problems in master.info.
In this function, there is no need for a mutex as the caller
(i.e. init_slave) already has one acquired.
Specifically, the following structures are updated:
1 - mi->master_log_pos <-- rli->group_master_log_pos
2 - mi->master_log_name <-- rli->group_master_log_name
3 - It moves the relay log to the new relay log file, by
rli->group_relay_log_pos <-- BIN_LOG_HEADER_SIZE;
rli->event_relay_log_pos <-- BIN_LOG_HEADER_SIZE;
rli->group_relay_log_name <-- rli->relay_log.get_log_fname();
rli->event_relay_log_name <-- rli->relay_log.get_log_fname();
If there is an error, it returns (1), otherwise returns (0).
*/
int init_recovery(Master_info *mi) {
DBUG_TRACE;
int error = 0;
Relay_log_info *rli = mi->rli;
char *group_master_log_name = nullptr;
/* Set the recovery_parallel_workers to 0 if Auto Position is enabled. */
bool is_gtid_with_autopos_on =
(((mi->get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_ON) &&
mi->is_auto_position())
? true
: false);
if (is_gtid_with_autopos_on) rli->recovery_parallel_workers = 0;
if (rli->recovery_parallel_workers) {
/*
This is not idempotent and a crash after this function and before
the recovery is actually done may lead the system to an inconsistent
state.
This may happen because the gap is not persitent stored anywhere
and eventually old relay log files will be removed and further
calculations on the gaps will be impossible.
We need to improve this. /Alfranio.
*/
error = mts_recovery_groups(rli);
if (rli->mts_recovery_group_cnt) return error;
}
group_master_log_name = const_cast<char *>(rli->get_group_master_log_name());
if (!error) {
if (!group_master_log_name[0]) {
if (rli->replicate_same_server_id) {
error = 1;
LogErr(ERROR_LEVEL,
ER_RPL_RECOVERY_REPLICATE_SAME_SERVER_ID_REQUIRES_POSITION);
return error;
}
error = find_first_relay_log_with_rotate_from_master(rli);
if (error) return error;
}
recover_relay_log(mi);
}
return error;
}
/*
Relay log recovery in the case of MTS, is handled by the following function.
Gaps in MTS execution are filled using implicit execution of
START SLAVE UNTIL SQL_AFTER_MTS_GAPS call. Once slave reaches a consistent
gapless state receiver thread's positions are initialized to applier thread's
positions and the old relay logs are discarded. This completes the recovery
process.
@param mi pointer to Master_info instance.
@retval 0 success
@retval 1 error
*/
static inline int fill_mts_gaps_and_recover(Master_info *mi) {
DBUG_TRACE;
Relay_log_info *rli = mi->rli;
int recovery_error = 0;
rli->is_relay_log_recovery = false;
Until_mts_gap *until_mg = new Until_mts_gap(rli);
rli->set_until_option(until_mg);
rli->until_condition = Relay_log_info::UNTIL_SQL_AFTER_MTS_GAPS;
until_mg->init();
rli->channel_mts_submode = (mts_parallel_option == MTS_PARALLEL_TYPE_DB_NAME)
? MTS_PARALLEL_TYPE_DB_NAME
: MTS_PARALLEL_TYPE_LOGICAL_CLOCK;
LogErr(INFORMATION_LEVEL, ER_RPL_MTS_RECOVERY_STARTING_COORDINATOR);
recovery_error = start_slave_thread(
#ifdef HAVE_PSI_THREAD_INTERFACE
key_thread_slave_sql,
#endif
handle_slave_sql, &rli->run_lock, &rli->run_lock, &rli->start_cond,
&rli->slave_running, &rli->slave_run_id, mi);
if (recovery_error) {
LogErr(WARNING_LEVEL, ER_RPL_MTS_RECOVERY_FAILED_TO_START_COORDINATOR);
goto err;
}
mysql_mutex_lock(&rli->run_lock);
mysql_cond_wait(&rli->stop_cond, &rli->run_lock);
mysql_mutex_unlock(&rli->run_lock);
if (rli->until_condition != Relay_log_info::UNTIL_DONE) {
LogErr(WARNING_LEVEL, ER_RPL_MTS_AUTOMATIC_RECOVERY_FAILED);
goto err;
}
rli->clear_until_option();
/*
We need a mutex while we are changing master info parameters to
keep other threads from reading bogus info
*/
mysql_mutex_lock(&mi->data_lock);
mysql_mutex_lock(&rli->data_lock);
recover_relay_log(mi);
if (mi->flush_info(true) || rli->flush_info(true)) {
recovery_error = 1;
mysql_mutex_unlock(&mi->data_lock);
mysql_mutex_unlock(&rli->data_lock);
goto err;
}
rli->inited = 1;
rli->error_on_rli_init_info = false;
mysql_mutex_unlock(&mi->data_lock);
mysql_mutex_unlock(&rli->data_lock);
LogErr(INFORMATION_LEVEL, ER_RPL_MTS_RECOVERY_SUCCESSFUL);
return recovery_error;
err:
/*
If recovery failed means we failed to initialize rli object in the case
of MTS. We should not allow the START SLAVE command to work as we do in
the case of STS. i.e if init_recovery call fails then we set inited=0.
*/
rli->end_info();
rli->inited = 0;
rli->error_on_rli_init_info = true;
rli->clear_until_option();
return recovery_error;
}
int load_mi_and_rli_from_repositories(Master_info *mi, bool ignore_if_no_info,
int thread_mask,
bool skip_received_gtid_set_recovery) {
DBUG_TRACE;
DBUG_ASSERT(mi != nullptr && mi->rli != nullptr);
int init_error = 0;
enum_return_check check_return = ERROR_CHECKING_REPOSITORY;
THD *thd = current_thd;
/*
We need a mutex while we are changing master info parameters to
keep other threads from reading bogus info
*/
mysql_mutex_lock(&mi->data_lock);
mysql_mutex_lock(&mi->rli->data_lock);
/*
When info tables are used and autocommit= 0 we force a new
transaction start to avoid table access deadlocks when START SLAVE
is executed after RESET SLAVE.
*/
if (is_autocommit_off_and_infotables(thd)) {
if (trans_begin(thd)) {
init_error = 1;
goto end;
}
}
/*
This takes care of the startup dependency between the master_info
and relay_info. It initializes the master info if the SLAVE_IO
thread is being started and the relay log info if either the
SLAVE_SQL thread is being started or was not initialized as it is
required by the SLAVE_IO thread.
*/
check_return = mi->check_info();
if (check_return == ERROR_CHECKING_REPOSITORY) {
init_error = 1;
goto end;
}
if (!(ignore_if_no_info && check_return == REPOSITORY_DOES_NOT_EXIST)) {
if ((thread_mask & SLAVE_IO) != 0 && mi->mi_init_info()) init_error = 1;
}
check_return = mi->rli->check_info();
if (check_return == ERROR_CHECKING_REPOSITORY) {
init_error = 1;
goto end;
}
if (!(ignore_if_no_info && check_return == REPOSITORY_DOES_NOT_EXIST)) {
if (((thread_mask & SLAVE_SQL) != 0 || !(mi->rli->inited)) &&
mi->rli->rli_init_info(skip_received_gtid_set_recovery))
init_error = 1;
else {
/*
During rli_init_info() above, the relay log is opened (if rli was not
initialized yet). The function below expects the relay log to be opened
to get its coordinates and store as the last flushed relay log
coordinates from I/O thread point of view.
*/
mi->update_flushed_relay_log_info();
}
}
DBUG_EXECUTE_IF("enable_mts_worker_failure_init",
{ DBUG_SET("+d,mts_worker_thread_init_fails"); });
end:
/*
When info tables are used and autocommit= 0 we force transaction
commit to avoid table access deadlocks when START SLAVE is executed
after RESET SLAVE.
*/
if (is_autocommit_off_and_infotables(thd))
if (trans_commit(thd)) init_error = 1;
mysql_mutex_unlock(&mi->rli->data_lock);
mysql_mutex_unlock(&mi->data_lock);
/*
Handling MTS Relay-log recovery after successful initialization of mi and
rli objects.
MTS Relay-log recovery is handled by SSUG command. In order to start the
slave applier thread rli needs to be inited and mi->rli->data_lock should
be in released state. Hence we do the MTS recovery at this point of time
where both conditions are satisfied.
*/
if (!init_error && mi->rli->is_relay_log_recovery &&
mi->rli->mts_recovery_group_cnt)
init_error = fill_mts_gaps_and_recover(mi);
return init_error;
}
void end_info(Master_info *mi) {
DBUG_TRACE;
DBUG_ASSERT(mi != nullptr && mi->rli != nullptr);
/*
The previous implementation was not acquiring locks. We do the same here.
However, this is quite strange.
*/
mi->end_info();
mi->rli->end_info();
}
int remove_info(Master_info *mi) {
int error = 1;
DBUG_TRACE;
DBUG_ASSERT(mi != nullptr && mi->rli != nullptr);
/*
The previous implementation was not acquiring locks.
We do the same here. However, this is quite strange.
*/
/*
Reset errors (the idea is that we forget about the
old master).
*/
mi->clear_error();
mi->rli->clear_error();
if (mi->rli->workers_array_initialized) {
for (size_t i = 0; i < mi->rli->get_worker_count(); i++) {
mi->rli->get_worker(i)->clear_error();
}
}
mi->rli->clear_sql_delay();
mi->end_info();
mi->rli->end_info();
if (mi->remove_info() || Rpl_info_factory::reset_workers(mi->rli) ||
mi->rli->remove_info())
goto err;
error = 0;
err:
return error;
}
int flush_master_info(Master_info *mi, bool force, bool need_lock,
bool do_flush_relay_log) {
DBUG_TRACE;
DBUG_ASSERT(mi != nullptr && mi->rli != nullptr);
DBUG_EXECUTE_IF("fail_to_flush_master_info", { return 1; });
/*
With the appropriate recovery process, we will not need to flush
the content of the current log.
For now, we flush the relay log BEFORE the master.info file, because
if we crash, we will get a duplicate event in the relay log at restart.
If we change the order, there might be missing events.
If we don't do this and the slave server dies when the relay log has
some parts (its last kilobytes) in memory only, with, say, from master's
position 100 to 150 in memory only (not on disk), and with position 150
in master.info, there will be missing information. When the slave restarts,
the I/O thread will fetch binlogs from 150, so in the relay log we will
have "[0, 100] U [150, infinity[" and nobody will notice it, so the SQL
thread will jump from 100 to 150, and replication will silently break.
*/
mysql_mutex_t *log_lock = mi->rli->relay_log.get_log_lock();
mysql_mutex_t *data_lock = &mi->data_lock;
if (need_lock) {
mysql_mutex_lock(log_lock);
mysql_mutex_lock(data_lock);
} else {
mysql_mutex_assert_owner(log_lock);
mysql_mutex_assert_owner(&mi->data_lock);
}
int err = 0;
/*
We can skip flushing the relay log when this function is called from
queue_event(), as after_write_to_relay_log() will already flush it.
*/
if (do_flush_relay_log) err |= mi->rli->flush_current_log();
err |= mi->flush_info(force);
if (need_lock) {
mysql_mutex_unlock(data_lock);
mysql_mutex_unlock(log_lock);
}
return err;
}
/**
Convert slave skip errors bitmap into a printable string.
*/
static void print_slave_skip_errors(void) {
/*
To be safe, we want 10 characters of room in the buffer for a number
plus terminators. Also, we need some space for constant strings.
10 characters must be sufficient for a number plus {',' | '...'}
plus a NUL terminator. That is a max 6 digit number.
*/
const size_t MIN_ROOM = 10;
DBUG_TRACE;
DBUG_ASSERT(sizeof(slave_skip_error_names) > MIN_ROOM);
DBUG_ASSERT(MAX_SLAVE_ERROR <= 999999); // 6 digits
if (!use_slave_mask || bitmap_is_clear_all(&slave_error_mask)) {
/* purecov: begin tested */
memcpy(slave_skip_error_names, STRING_WITH_LEN("OFF"));
/* purecov: end */
} else if (bitmap_is_set_all(&slave_error_mask)) {
/* purecov: begin tested */
memcpy(slave_skip_error_names, STRING_WITH_LEN("ALL"));
/* purecov: end */
} else {
char *buff = slave_skip_error_names;
char *bend = buff + sizeof(slave_skip_error_names);
int errnum;
for (errnum = 0; errnum < MAX_SLAVE_ERROR; errnum++) {
if (bitmap_is_set(&slave_error_mask, errnum)) {
if (buff + MIN_ROOM >= bend) break; /* purecov: tested */
buff = int10_to_str(errnum, buff, 10);
*buff++ = ',';
}
}
if (buff != slave_skip_error_names) buff--; // Remove last ','
/*
The range for client side error is [2000-2999]
so if the errnum doesn't lie in that and if less
than MAX_SLAVE_ERROR[10000] we enter the if loop.
*/
if (errnum < MAX_SLAVE_ERROR &&
(errnum < CR_MIN_ERROR || errnum > CR_MAX_ERROR)) {
/* Couldn't show all errors */
buff = my_stpcpy(buff, "..."); /* purecov: tested */
}
*buff = 0;
}
DBUG_PRINT("init", ("error_names: '%s'", slave_skip_error_names));
}
/**
Change arg to the string with the nice, human-readable skip error values.
@param slave_skip_errors_ptr
The pointer to be changed
*/
void set_slave_skip_errors(char **slave_skip_errors_ptr) {
DBUG_TRACE;
print_slave_skip_errors();
*slave_skip_errors_ptr = slave_skip_error_names;
}
/**
Init function to set up array for errors that should be skipped for slave
*/
static void init_slave_skip_errors() {
DBUG_TRACE;
DBUG_ASSERT(!use_slave_mask); // not already initialized
if (bitmap_init(&slave_error_mask, 0, MAX_SLAVE_ERROR, 0)) {
fprintf(stderr, "Badly out of memory, please check your system status\n");
exit(MYSQLD_ABORT_EXIT);
}
use_slave_mask = 1;
}
static void add_slave_skip_errors(const uint *errors, uint n_errors) {
DBUG_TRACE;
DBUG_ASSERT(errors);
DBUG_ASSERT(use_slave_mask);
for (uint i = 0; i < n_errors; i++) {
const uint err_code = errors[i];
/*
The range for client side error is [2000-2999]
so if the err_code doesn't lie in that and if less
than MAX_SLAVE_ERROR[14000] we enter the if loop.
*/
if (err_code < MAX_SLAVE_ERROR &&
(err_code < CR_MIN_ERROR || err_code > CR_MAX_ERROR))
bitmap_set_bit(&slave_error_mask, err_code);
}
}
/*
Add errors that should be skipped for slave
SYNOPSIS
add_slave_skip_errors()
arg List of errors numbers to be added to skip, separated with ','
NOTES
Called from get_options() in mysqld.cc on start-up
*/
void add_slave_skip_errors(const char *arg) {
const char *p = nullptr;
/*
ALL is only valid when nothing else is provided.
*/
const uchar SKIP_ALL[] = "all";
size_t SIZE_SKIP_ALL = strlen((const char *)SKIP_ALL) + 1;
/*
IGNORE_DDL_ERRORS can be combined with other parameters
but must be the first one provided.
*/
const uchar SKIP_DDL_ERRORS[] = "ddl_exist_errors";
size_t SIZE_SKIP_DDL_ERRORS = strlen((const char *)SKIP_DDL_ERRORS);
DBUG_TRACE;
// initialize mask if not done yet
if (!use_slave_mask) init_slave_skip_errors();
for (; my_isspace(system_charset_info, *arg); ++arg) /* empty */
;
if (!my_strnncoll(system_charset_info, pointer_cast<const uchar *>(arg),
SIZE_SKIP_ALL, SKIP_ALL, SIZE_SKIP_ALL)) {
bitmap_set_all(&slave_error_mask);
return;
}
if (!my_strnncoll(system_charset_info, pointer_cast<const uchar *>(arg),
SIZE_SKIP_DDL_ERRORS, SKIP_DDL_ERRORS,
SIZE_SKIP_DDL_ERRORS)) {
// DDL errors to be skipped for relaxed 'exist' handling
const uint ddl_errors[] = {
// error codes with create/add <schema object>
ER_DB_CREATE_EXISTS, ER_TABLE_EXISTS_ERROR, ER_DUP_KEYNAME,
ER_MULTIPLE_PRI_KEY,
// error codes with change/rename <schema object>
ER_BAD_FIELD_ERROR, ER_NO_SUCH_TABLE, ER_DUP_FIELDNAME,
// error codes with drop <schema object>
ER_DB_DROP_EXISTS, ER_BAD_TABLE_ERROR, ER_CANT_DROP_FIELD_OR_KEY};
add_slave_skip_errors(ddl_errors,
sizeof(ddl_errors) / sizeof(ddl_errors[0]));
/*
After processing the SKIP_DDL_ERRORS, the pointer is
increased to the position after the comma.
*/
if (strlen(arg) > SIZE_SKIP_DDL_ERRORS + 1) arg += SIZE_SKIP_DDL_ERRORS + 1;
}
for (p = arg; *p;) {
long err_code;
if (!(p = str2int(p, 10, 0, LONG_MAX, &err_code))) break;
if (err_code < MAX_SLAVE_ERROR)
bitmap_set_bit(&slave_error_mask, (uint)err_code);
while (!my_isdigit(system_charset_info, *p) && *p) p++;
}
}
static void set_thd_in_use_temporary_tables(Relay_log_info *rli) {
TABLE *table;
for (table = rli->save_temporary_tables; table; table = table->next) {
table->in_use = rli->info_thd;
if (table->file != nullptr) {
/*
Since we are stealing opened temporary tables from one thread to
another, we need to let the performance schema know that, for aggregates
per thread to work properly.
*/
table->file->unbind_psi();
table->file->rebind_psi();
}
}
}
int terminate_slave_threads(Master_info *mi, int thread_mask,
ulong stop_wait_timeout, bool need_lock_term) {
DBUG_TRACE;
if (!mi->inited) return 0; /* successfully do nothing */
int error, force_all = (thread_mask & SLAVE_FORCE_ALL);
mysql_mutex_t *sql_lock = &mi->rli->run_lock, *io_lock = &mi->run_lock;
mysql_mutex_t *log_lock = mi->rli->relay_log.get_log_lock();
/*
Set it to a variable, so the value is shared by both stop methods.
This guarantees that the user defined value for the timeout value is for
the time the 2 threads take to shutdown, and not the time of each thread
stop operation.
*/
ulong total_stop_wait_timeout = stop_wait_timeout;
if (thread_mask & (SLAVE_SQL | SLAVE_FORCE_ALL)) {
DBUG_PRINT("info", ("Terminating SQL thread"));
mi->rli->abort_slave = 1;
if ((error = terminate_slave_thread(
mi->rli->info_thd, sql_lock, &mi->rli->stop_cond,
&mi->rli->slave_running, &total_stop_wait_timeout,
need_lock_term)) &&
!force_all) {
if (error == 1) {
return ER_STOP_SLAVE_SQL_THREAD_TIMEOUT;
}
return error;
}
DBUG_PRINT("info", ("Flushing relay-log info file."));
if (current_thd)
THD_STAGE_INFO(current_thd, stage_flushing_relay_log_info_file);
/*
Flushes the relay log info regardles of the sync_relay_log_info option.
*/
if (mi->rli->flush_info(true)) {
return ER_ERROR_DURING_FLUSH_LOGS;
}
}
if (thread_mask & (SLAVE_IO | SLAVE_FORCE_ALL)) {
DBUG_PRINT("info", ("Terminating IO thread"));
mi->abort_slave = 1;
DBUG_EXECUTE_IF("pause_after_queue_event", {
const char act[] = "now SIGNAL reached_stopping_io_thread";
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
/*
If the I/O thread is running and waiting for disk space,
the signal above will not make it to stop.
*/
bool io_waiting_disk_space =
mi->slave_running && mi->info_thd->is_waiting_for_disk_space();
/*
If we are shutting down the server and the I/O thread is waiting for
disk space, tell the terminate_slave_thread to forcefully kill the I/O
thread by sending a KILL_CONNECTION signal that will be listened by
my_write function.
*/
bool force_io_stop =
io_waiting_disk_space && (thread_mask & SLAVE_FORCE_ALL);
// If not shutting down, let the user to decide to abort I/O thread or wait
if (io_waiting_disk_space && !force_io_stop) {
LogErr(WARNING_LEVEL, ER_STOP_SLAVE_IO_THREAD_DISK_SPACE,
mi->get_channel());
DBUG_EXECUTE_IF("simulate_io_thd_wait_for_disk_space", {
const char act[] = "now SIGNAL reached_stopping_io_thread";
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
}
if ((error = terminate_slave_thread(
mi->info_thd, io_lock, &mi->stop_cond, &mi->slave_running,
&total_stop_wait_timeout, need_lock_term, force_io_stop)) &&
!force_all) {
if (error == 1) {
return ER_STOP_SLAVE_IO_THREAD_TIMEOUT;
}
return error;
}
#ifndef DBUG_OFF
if (force_io_stop) {
if (DBUG_EVALUATE_IF("simulate_io_thd_wait_for_disk_space", 1, 0)) {
DBUG_SET("-d,simulate_io_thd_wait_for_disk_space");
}
}
#endif
mysql_mutex_lock(log_lock);
DBUG_PRINT("info", ("Flushing relay log and master info repository."));
if (current_thd)
THD_STAGE_INFO(current_thd,
stage_flushing_relay_log_and_master_info_repository);
/*
Flushes the master info regardles of the sync_master_info option.
*/
mysql_mutex_lock(&mi->data_lock);
if (mi->flush_info(true)) {
mysql_mutex_unlock(&mi->data_lock);
mysql_mutex_unlock(log_lock);
return ER_ERROR_DURING_FLUSH_LOGS;
}
mysql_mutex_unlock(&mi->data_lock);
/*
Flushes the relay log regardles of the sync_relay_log option.
*/
if (mi->rli->relay_log.is_open() &&
mi->rli->relay_log.flush_and_sync(true)) {
mysql_mutex_unlock(log_lock);
return ER_ERROR_DURING_FLUSH_LOGS;
}
mysql_mutex_unlock(log_lock);
}
return 0;
}
/**
Wait for a slave thread to terminate.
This function is called after requesting the thread to terminate
(by setting @c abort_slave member of @c Relay_log_info or @c
Master_info structure to 1). Termination of the thread is
controlled with the the predicate <code>*slave_running</code>.
Function will acquire @c term_lock before waiting on the condition
unless @c need_lock_term is false in which case the mutex should be
owned by the caller of this function and will remain acquired after
return from the function.
@param thd
Current session.
@param term_lock
Associated lock to use when waiting for @c term_cond
@param term_cond
Condition that is signalled when the thread has terminated
@param slave_running
Pointer to predicate to check for slave thread termination
@param stop_wait_timeout
A pointer to a variable that denotes the time the thread has
to stop before we time out and throw an error.
@param need_lock_term
If @c false the lock will not be acquired before waiting on
the condition. In this case, it is assumed that the calling
function acquires the lock before calling this function.
@param force
Force the slave thread to stop by sending a KILL_CONNECTION
signal to it. This is used to forcefully stop the I/O thread
when it is waiting for disk space and the server is shutting
down.
@retval 0 All OK, 1 on "STOP SLAVE" command timeout,
ER_SLAVE_CHANNEL_NOT_RUNNING otherwise.
@note If the executing thread has to acquire term_lock
(need_lock_term is true, the negative running status does not
represent any issue therefore no error is reported.
*/
static int terminate_slave_thread(THD *thd, mysql_mutex_t *term_lock,
mysql_cond_t *term_cond,
std::atomic<uint> *slave_running,
ulong *stop_wait_timeout, bool need_lock_term,
bool force) {
DBUG_TRACE;
if (need_lock_term) {
mysql_mutex_lock(term_lock);
} else {
mysql_mutex_assert_owner(term_lock);
}
if (!*slave_running) {
if (need_lock_term) {
/*
if run_lock (term_lock) is acquired locally then either
slave_running status is fine
*/
mysql_mutex_unlock(term_lock);
return 0;
} else {
return ER_SLAVE_CHANNEL_NOT_RUNNING;
}
}
DBUG_ASSERT(thd != 0);
THD_CHECK_SENTRY(thd);
/*
Is is critical to test if the slave is running. Otherwise, we might
be referening freed memory trying to kick it
*/
while (*slave_running) // Should always be true
{
DBUG_PRINT("loop", ("killing slave thread"));
mysql_mutex_lock(&thd->LOCK_thd_data);
/*
Error codes from pthread_kill are:
EINVAL: invalid signal number (can't happen)
ESRCH: thread already killed (can happen, should be ignored)
*/
#ifndef _WIN32
int err MY_ATTRIBUTE((unused)) = pthread_kill(thd->real_id, SIGUSR1);
DBUG_ASSERT(err != EINVAL);
#endif
if (force)
thd->awake(THD::KILL_CONNECTION);
else
thd->awake(THD::NOT_KILLED);
mysql_mutex_unlock(&thd->LOCK_thd_data);
/*
There is a small chance that slave thread might miss the first
alarm. To protect againts it, resend the signal until it reacts
*/
struct timespec abstime;
set_timespec(&abstime, 2);
#ifndef DBUG_OFF
int error =
#endif
mysql_cond_timedwait(term_cond, term_lock, &abstime);
if ((*stop_wait_timeout) >= 2)
(*stop_wait_timeout) = (*stop_wait_timeout) - 2;
else if (*slave_running) {
if (need_lock_term) mysql_mutex_unlock(term_lock);
return 1;
}
DBUG_ASSERT(error == ETIMEDOUT || error == 0);
}
DBUG_ASSERT(*slave_running == 0);
if (need_lock_term) mysql_mutex_unlock(term_lock);
return 0;
}
bool start_slave_thread(
#ifdef HAVE_PSI_THREAD_INTERFACE
PSI_thread_key thread_key,
#endif
my_start_routine h_func, mysql_mutex_t *start_lock,
mysql_mutex_t *cond_lock, mysql_cond_t *start_cond,
std::atomic<uint> *slave_running, std::atomic<ulong> *slave_run_id,
Master_info *mi) {
bool is_error = false;
my_thread_handle th;
ulong start_id;
DBUG_TRACE;
if (start_lock) mysql_mutex_lock(start_lock);
if (!server_id) {
if (start_cond) mysql_cond_broadcast(start_cond);
LogErr(ERROR_LEVEL, ER_RPL_SERVER_ID_MISSING, mi->get_for_channel_str());
my_error(ER_BAD_SLAVE, MYF(0));
goto err;
}
if (*slave_running) {
if (start_cond) mysql_cond_broadcast(start_cond);
my_error(ER_SLAVE_CHANNEL_MUST_STOP, MYF(0), mi->get_channel());
goto err;
}
start_id = *slave_run_id;
DBUG_PRINT("info", ("Creating new slave thread"));
if (mysql_thread_create(thread_key, &th, &connection_attrib, h_func,
(void *)mi)) {
LogErr(ERROR_LEVEL, ER_RPL_CANT_CREATE_SLAVE_THREAD,
mi->get_for_channel_str());
my_error(ER_SLAVE_THREAD, MYF(0));
goto err;
}
if (start_cond && cond_lock) // caller has cond_lock
{
THD *thd = current_thd;
while (start_id == *slave_run_id && thd != nullptr) {
DBUG_PRINT("sleep", ("Waiting for slave thread to start"));
PSI_stage_info saved_stage = {0, "", 0, ""};
thd->ENTER_COND(start_cond, cond_lock,
&stage_waiting_for_slave_thread_to_start, &saved_stage);
/*
It is not sufficient to test this at loop bottom. We must test
it after registering the mutex in enter_cond(). If the kill
happens after testing of thd->killed and before the mutex is
registered, we could otherwise go waiting though thd->killed is
set.
*/
if (!thd->killed) mysql_cond_wait(start_cond, cond_lock);
mysql_mutex_unlock(cond_lock);
thd->EXIT_COND(&saved_stage);
mysql_mutex_lock(cond_lock); // re-acquire it
if (thd->killed) {
my_error(thd->killed, MYF(0));
goto err;
}
}
}
goto end;
err:
is_error = true;
end:
if (start_lock) mysql_mutex_unlock(start_lock);
return is_error;
}
/*
start_slave_threads()
NOTES
SLAVE_FORCE_ALL is not implemented here on purpose since it does not make
sense to do that for starting a slave--we always care if it actually
started the threads that were not previously running
*/
bool start_slave_threads(bool need_lock_slave, bool wait_for_start,
Master_info *mi, int thread_mask) {
mysql_mutex_t *lock_io = nullptr, *lock_sql = nullptr,
*lock_cond_io = nullptr, *lock_cond_sql = nullptr;
mysql_cond_t *cond_io = nullptr, *cond_sql = nullptr;
bool is_error = false;
DBUG_TRACE;
DBUG_EXECUTE_IF("uninitialized_master-info_structure", mi->inited = false;);
if (!mi->inited || !mi->rli->inited) {
int error = (!mi->inited ? ER_SLAVE_MI_INIT_REPOSITORY
: ER_SLAVE_RLI_INIT_REPOSITORY);
Rpl_info *info = (!mi->inited ? mi : static_cast<Rpl_info *>(mi->rli));
const char *prefix = current_thd ? ER_THD_NONCONST(current_thd, error)
: ER_DEFAULT_NONCONST(error);
info->report(ERROR_LEVEL,
(!mi->inited ? ER_SERVER_SLAVE_MI_INIT_REPOSITORY
: ER_SERVER_SLAVE_RLI_INIT_REPOSITORY),
prefix, nullptr);
my_error(error, MYF(0));
return true;
}
if (mi->is_auto_position() && (thread_mask & SLAVE_IO) &&
mi->get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_OFF) {
my_error(ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF, MYF(0),
mi->get_for_channel_str());
return true;
}
if (need_lock_slave) {
lock_io = &mi->run_lock;
lock_sql = &mi->rli->run_lock;
}
if (wait_for_start) {
cond_io = &mi->start_cond;
cond_sql = &mi->rli->start_cond;
lock_cond_io = &mi->run_lock;
lock_cond_sql = &mi->rli->run_lock;
}
if (thread_mask & SLAVE_IO)
is_error = start_slave_thread(
#ifdef HAVE_PSI_THREAD_INTERFACE
key_thread_slave_io,
#endif
handle_slave_io, lock_io, lock_cond_io, cond_io, &mi->slave_running,
&mi->slave_run_id, mi);
if (!is_error && (thread_mask & SLAVE_SQL)) {
/*
MTS-recovery gaps gathering is placed onto common execution path
for either START-SLAVE and --skip-start-slave= 0
*/
if (mi->rli->recovery_parallel_workers != 0) {
if (mts_recovery_groups(mi->rli)) {
is_error = true;
my_error(ER_MTS_RECOVERY_FAILURE, MYF(0));
}
}
if (!is_error)
is_error = start_slave_thread(
#ifdef HAVE_PSI_THREAD_INTERFACE
key_thread_slave_sql,
#endif
handle_slave_sql, lock_sql, lock_cond_sql, cond_sql,
&mi->rli->slave_running, &mi->rli->slave_run_id, mi);
if (is_error)
terminate_slave_threads(mi, thread_mask & SLAVE_IO,
rpl_stop_slave_timeout, need_lock_slave);
}
return is_error;
}
/*
Release slave threads at time of executing shutdown.
SYNOPSIS
end_slave()
*/
void end_slave() {
DBUG_TRACE;
Master_info *mi = nullptr;
/*
This is called when the server terminates, in close_connections().
It terminates slave threads. However, some CHANGE MASTER etc may still be
running presently. If a START SLAVE was in progress, the mutex lock below
will make us wait until slave threads have started, and START SLAVE
returns, then we terminate them here.
*/
channel_map.wrlock();
/* traverse through the map and terminate the threads */
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
if (mi)
terminate_slave_threads(mi, SLAVE_FORCE_ALL, rpl_stop_slave_timeout);
}
channel_map.unlock();
}
/**
Free all resources used by slave threads at time of executing shutdown.
The routine must be called after all possible users of channel_map
have left.
*/
void delete_slave_info_objects() {
DBUG_TRACE;
Master_info *mi = nullptr;
channel_map.wrlock();
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
if (mi) {
mi->channel_wrlock();
end_info(mi);
if (mi->rli) delete mi->rli;
delete mi;
it->second = 0;
}
}
// Clean other types of channel
for (mi_map::iterator it = channel_map.begin(GROUP_REPLICATION_CHANNEL);
it != channel_map.end(GROUP_REPLICATION_CHANNEL); it++) {
mi = it->second;
if (mi) {
mi->channel_wrlock();
end_info(mi);
if (mi->rli) delete mi->rli;
delete mi;
it->second = 0;
}
}
channel_map.unlock();
}
/**
Check if multi-statement transaction mode and master and slave info
repositories are set to table.
@param thd THD object
@retval true Success
@retval false Failure
*/
static bool is_autocommit_off_and_infotables(THD *thd) {
DBUG_TRACE;
return (thd && thd->in_multi_stmt_transaction_mode() &&
(opt_mi_repository_id == INFO_REPOSITORY_TABLE ||
opt_rli_repository_id == INFO_REPOSITORY_TABLE))
? true
: false;
}
static bool io_slave_killed(THD *thd, Master_info *mi) {
DBUG_TRACE;
DBUG_ASSERT(mi->info_thd == thd);
DBUG_ASSERT(mi->slave_running); // tracking buffer overrun
return mi->abort_slave || connection_events_loop_aborted() || thd->killed;
}
/**
The function analyzes a possible killed status and makes
a decision whether to accept it or not.
Normally upon accepting the sql thread goes to shutdown.
In the event of deferring decision @c rli->last_event_start_time waiting
timer is set to force the killed status be accepted upon its expiration.
Notice Multi-Threaded-Slave behaves similarly in that when it's being
stopped and the current group of assigned events has not yet scheduled
completely, Coordinator defers to accept to leave its read-distribute
state. The above timeout ensures waiting won't last endlessly, and in
such case an error is reported.
@param thd pointer to a THD instance
@param rli pointer to Relay_log_info instance
@return true the killed status is recognized, false a possible killed
status is deferred.
*/
bool sql_slave_killed(THD *thd, Relay_log_info *rli) {
bool is_parallel_warn = false;
DBUG_TRACE;
DBUG_ASSERT(rli->info_thd == thd);
DBUG_ASSERT(rli->slave_running == 1);
if (rli->sql_thread_kill_accepted) return true;
DBUG_EXECUTE_IF("stop_when_mts_in_group", rli->abort_slave = 1;
DBUG_SET("-d,stop_when_mts_in_group");
DBUG_SET("-d,simulate_stop_when_mts_in_group");
return false;);
if (connection_events_loop_aborted() || thd->killed || rli->abort_slave) {
rli->sql_thread_kill_accepted = true;
is_parallel_warn =
(rli->is_parallel_exec() && (rli->is_mts_in_group() || thd->killed));
/*
Slave can execute stop being in one of two MTS or Single-Threaded mode.
The modes define different criteria to accept the stop.
In particular that relates to the concept of groupping.
Killed Coordinator thread expects the worst so it warns on
possible consistency issue.
*/
if (is_parallel_warn || (!rli->is_parallel_exec() &&
thd->get_transaction()->cannot_safely_rollback(
Transaction_ctx::SESSION) &&
rli->is_in_group())) {
char msg_stopped[] =
"... Slave SQL Thread stopped with incomplete event group "
"having non-transactional changes. "
"If the group consists solely of row-based events, you can try "
"to restart the slave with --slave-exec-mode=IDEMPOTENT, which "
"ignores duplicate key, key not found, and similar errors (see "
"documentation for details).";
char msg_stopped_mts[] =
"... The slave coordinator and worker threads are stopped, possibly "
"leaving data in inconsistent state. A restart should "
"restore consistency automatically, although using non-transactional "
"storage for data or info tables or DDL queries could lead to "
"problems. "
"In such cases you have to examine your data (see documentation for "
"details).";
if (rli->abort_slave) {
DBUG_PRINT("info",
("Request to stop slave SQL Thread received while "
"applying an MTS group or a group that "
"has non-transactional "
"changes; waiting for completion of the group ... "));
/*
Slave sql thread shutdown in face of unfinished group modified
Non-trans table is handled via a timer. The slave may eventually
give out to complete the current group and in that case there
might be issues at consequent slave restart, see the error message.
WL#2975 offers a robust solution requiring to store the last exectuted
event's coordinates along with the group's coordianates
instead of waiting with @c last_event_start_time the timer.
*/
if (rli->last_event_start_time == 0)
rli->last_event_start_time = my_time(0);
rli->sql_thread_kill_accepted =
difftime(my_time(0), rli->last_event_start_time) <=
SLAVE_WAIT_GROUP_DONE
? false
: true;
DBUG_EXECUTE_IF("stop_slave_middle_group",
DBUG_EXECUTE_IF("incomplete_group_in_relay_log",
rli->sql_thread_kill_accepted =
true;);); // time is over
if (!rli->sql_thread_kill_accepted && !rli->reported_unsafe_warning) {
rli->report(
WARNING_LEVEL, 0,
!is_parallel_warn
? "Request to stop slave SQL Thread received while "
"applying a group that has non-transactional "
"changes; waiting for completion of the group ... "
: "Coordinator thread of multi-threaded slave is being "
"stopped in the middle of assigning a group of events; "
"deferring to exit until the group completion ... ");
rli->reported_unsafe_warning = true;
}
}
if (rli->sql_thread_kill_accepted) {
rli->last_event_start_time = 0;
if (rli->mts_group_status == Relay_log_info::MTS_IN_GROUP) {
rli->mts_group_status = Relay_log_info::MTS_KILLED_GROUP;
}
if (is_parallel_warn)
rli->report(!rli->is_error()
? ERROR_LEVEL
: WARNING_LEVEL, // an error was reported by Worker
ER_MTS_INCONSISTENT_DATA,
ER_THD(thd, ER_MTS_INCONSISTENT_DATA), msg_stopped_mts);
else
rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR), msg_stopped);
}
}
}
return rli->sql_thread_kill_accepted;
}
bool net_request_file(NET *net, const char *fname) {
DBUG_TRACE;
return net_write_command(net, 251, pointer_cast<const uchar *>(fname),
strlen(fname), pointer_cast<const uchar *>(""), 0);
}
/*
From other comments and tests in code, it looks like
sometimes Query_log_event and Load_log_event can have db == 0
(see rewrite_db() above for example)
(cases where this happens are unclear; it may be when the master is 3.23).
*/
const char *print_slave_db_safe(const char *db) {
DBUG_TRACE;
return (db ? db : "");
}
/*
Check if the error is caused by network.
@param[in] errorno Number of the error.
RETURNS:
true network error
false not network error
*/
static bool is_network_error(uint errorno) {
return errorno == CR_CONNECTION_ERROR || errorno == CR_CONN_HOST_ERROR ||
errorno == CR_SERVER_GONE_ERROR || errorno == CR_SERVER_LOST ||
errorno == ER_CON_COUNT_ERROR || errorno == ER_SERVER_SHUTDOWN ||
errorno == ER_NET_READ_INTERRUPTED ||
errorno == ER_NET_WRITE_INTERRUPTED;
}
/**
Execute an initialization query for the IO thread.
If there is an error, then this function calls mysql_free_result;
otherwise the MYSQL object holds the result after this call. If
there is an error other than allowed_error, then this function
prints a message and returns -1.
@param mysql MYSQL object.
@param query Query string.
@param allowed_error Allowed error code, or 0 if no errors are allowed.
@param[out] master_res If this is not NULL and there is no error, then
mysql_store_result() will be called and the result stored in this pointer.
@param[out] master_row If this is not NULL and there is no error, then
mysql_fetch_row() will be called and the result stored in this pointer.
@retval COMMAND_STATUS_OK No error.
@retval COMMAND_STATUS_ALLOWED_ERROR There was an error and the
error code was 'allowed_error'.
@retval COMMAND_STATUS_ERROR There was an error and the error code
was not 'allowed_error'.
*/
enum enum_command_status {
COMMAND_STATUS_OK,
COMMAND_STATUS_ERROR,
COMMAND_STATUS_ALLOWED_ERROR
};
static enum_command_status io_thread_init_command(
Master_info *mi, const char *query, int allowed_error,
MYSQL_RES **master_res = nullptr, MYSQL_ROW *master_row = nullptr) {
DBUG_TRACE;
DBUG_PRINT("info", ("IO thread initialization command: '%s'", query));
MYSQL *mysql = mi->mysql;
int ret = mysql_real_query(mysql, query, static_cast<ulong>(strlen(query)));
if (io_slave_killed(mi->info_thd, mi)) {
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_IO_THREAD_WAS_KILLED,
mi->get_for_channel_str(), query);
mysql_free_result(mysql_store_result(mysql));
return COMMAND_STATUS_ERROR;
}
if (ret != 0) {
int err = mysql_errno(mysql);
mysql_free_result(mysql_store_result(mysql));
if (!err || err != allowed_error) {
mi->report(is_network_error(err) ? WARNING_LEVEL : ERROR_LEVEL, err,
"The slave IO thread stops because the initialization query "
"'%s' failed with error '%s'.",
query, mysql_error(mysql));
return COMMAND_STATUS_ERROR;
}
return COMMAND_STATUS_ALLOWED_ERROR;
}
if (master_res != nullptr) {
if ((*master_res = mysql_store_result(mysql)) == nullptr) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"The slave IO thread stops because the initialization query "
"'%s' did not return any result.",
query);
return COMMAND_STATUS_ERROR;
}
if (master_row != nullptr) {
if ((*master_row = mysql_fetch_row(*master_res)) == nullptr) {
mysql_free_result(*master_res);
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"The slave IO thread stops because the initialization query "
"'%s' did not return any row.",
query);
return COMMAND_STATUS_ERROR;
}
}
} else
DBUG_ASSERT(master_row == nullptr);
return COMMAND_STATUS_OK;
}
/**
Set user variables after connecting to the master.
@param mysql MYSQL to request uuid from master.
@param mi Master_info to set master_uuid
@return 0: Success, 1: Fatal error, 2: Transient network error.
*/
int io_thread_init_commands(MYSQL *mysql, Master_info *mi) {
char query[256];
int ret = 0;
DBUG_EXECUTE_IF("fake_5_5_version_slave", return ret;);
sprintf(query, "SET @slave_uuid= '%s'", server_uuid);
if (mysql_real_query(mysql, query, static_cast<ulong>(strlen(query))) &&
!check_io_slave_killed(mi->info_thd, mi, nullptr))
goto err;
mysql_free_result(mysql_store_result(mysql));
return ret;
err:
if (mysql_errno(mysql) && is_network_error(mysql_errno(mysql))) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"The initialization command '%s' failed with the following"
" error: '%s'.",
query, mysql_error(mysql));
ret = 2;
} else {
char errmsg[512];
const char *errmsg_fmt =
"The slave I/O thread stops because a fatal error is encountered "
"when it tries to send query to master(query: %s).";
sprintf(errmsg, errmsg_fmt, query);
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(current_thd, ER_SLAVE_FATAL_ERROR), errmsg);
ret = 1;
}
mysql_free_result(mysql_store_result(mysql));
return ret;
}
/**
Get master's uuid on connecting.
@param mysql MYSQL to request uuid from master.
@param mi Master_info to set master_uuid
@return 0: Success, 1: Fatal error, 2: Transient network error.
*/
static int get_master_uuid(MYSQL *mysql, Master_info *mi) {
const char *errmsg;
MYSQL_RES *master_res = nullptr;
MYSQL_ROW master_row = nullptr;
int ret = 0;
char query_buf[] = "SELECT @@GLOBAL.SERVER_UUID";
DBUG_EXECUTE_IF("dbug.return_null_MASTER_UUID", {
mi->master_uuid[0] = 0;
return 0;
};);
DBUG_EXECUTE_IF("dbug.before_get_MASTER_UUID", {
const char act[] = "now wait_for signal.get_master_uuid";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
DBUG_EXECUTE_IF("dbug.simulate_busy_io", {
const char act[] = "now signal Reached wait_for signal.got_stop_slave";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
#ifndef DBUG_OFF
DBUG_EXECUTE_IF("dbug.simulate_no_such_var_server_uuid", {
query_buf[strlen(query_buf) - 1] = '_'; // currupt the last char
});
#endif
if (!mysql_real_query(mysql, STRING_WITH_LEN(query_buf)) &&
(master_res = mysql_store_result(mysql)) &&
(master_row = mysql_fetch_row(master_res))) {
if (!strcmp(::server_uuid, master_row[0]) &&
!mi->rli->replicate_same_server_id) {
errmsg =
"The slave I/O thread stops because master and slave have equal "
"MySQL server UUIDs; these UUIDs must be different for "
"replication to work.";
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(current_thd, ER_SLAVE_FATAL_ERROR), errmsg);
// Fatal error
ret = 1;
} else {
if (mi->master_uuid[0] != 0 && strcmp(mi->master_uuid, master_row[0]))
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_MASTER_UUID_HAS_CHANGED,
mi->master_uuid);
strncpy(mi->master_uuid, master_row[0], UUID_LENGTH);
mi->master_uuid[UUID_LENGTH] = 0;
}
} else if (mysql_errno(mysql) != ER_UNKNOWN_SYSTEM_VARIABLE) {
if (is_network_error(mysql_errno(mysql))) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"Get master SERVER_UUID failed with error: %s",
mysql_error(mysql));
ret = 2;
} else {
/* Fatal error */
errmsg =
"The slave I/O thread stops because a fatal error is encountered "
"when it tries to get the value of SERVER_UUID variable from master.";
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(current_thd, ER_SLAVE_FATAL_ERROR), errmsg);
ret = 1;
}
} else {
mi->master_uuid[0] = 0;
mi->report(
WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE,
"Unknown system variable 'SERVER_UUID' on master. "
"A probable cause is that the variable is not supported on the "
"master (version: %s), even though it is on the slave (version: %s)",
mysql->server_version, server_version);
}
if (master_res) mysql_free_result(master_res);
return ret;
}
/**
Determine, case-sensitively, if short_string is equal to
long_string, or a true prefix of long_string, or not a prefix.
@retval 0 short_string is not a prefix of long_string.
@retval 1 short_string is a true prefix of long_string (not equal).
@retval 2 short_string is equal to long_string.
*/
static int is_str_prefix_case(const char *short_string,
const char *long_string) {
int i;
for (i = 0; short_string[i]; i++)
if (my_toupper(system_charset_info, short_string[i]) !=
my_toupper(system_charset_info, long_string[i]))
return 0;
return long_string[i] ? 1 : 2;
}
/*
Note that we rely on the master's version (3.23, 4.0.14 etc) instead of
relying on the binlog's version. This is not perfect: imagine an upgrade
of the master without waiting that all slaves are in sync with the master;
then a slave could be fooled about the binlog's format. This is what happens
when people upgrade a 3.23 master to 4.0 without doing RESET MASTER: 4.0
slaves are fooled. So we do this only to distinguish between 3.23 and more
recent masters (it's too late to change things for 3.23).
RETURNS
0 ok
1 error
2 transient network problem, the caller should try to reconnect
*/
static int get_master_version_and_clock(MYSQL *mysql, Master_info *mi) {
char err_buff[MAX_SLAVE_ERRMSG];
const char *errmsg = nullptr;
int err_code = 0;
int version_number = 0;
version_number = atoi(mysql->server_version);
MYSQL_RES *master_res = nullptr;
MYSQL_ROW master_row;
DBUG_TRACE;
DBUG_EXECUTE_IF("unrecognized_master_version", { version_number = 1; };);
if (!my_isdigit(&my_charset_bin, *mysql->server_version) ||
version_number < 5) {
errmsg = "Master reported unrecognized MySQL version";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, ER_THD_NONCONST(current_thd, err_code), errmsg);
goto err;
}
mysql_mutex_lock(mi->rli->relay_log.get_log_lock());
mysql_mutex_lock(&mi->data_lock);
mi->set_mi_description_event(new Format_description_log_event());
/* as we are here, we tried to allocate the event */
if (mi->get_mi_description_event() == nullptr) {
mysql_mutex_unlock(&mi->data_lock);
mysql_mutex_unlock(mi->rli->relay_log.get_log_lock());
errmsg = "default Format_description_log_event";
err_code = ER_SLAVE_CREATE_EVENT_FAILURE;
sprintf(err_buff, ER_THD_NONCONST(current_thd, err_code), errmsg);
goto err;
}
/*
FD_q's (A) is set initially from RL's (A): FD_q.(A) := RL.(A).
It's necessary to adjust FD_q.(A) at this point because in the following
course FD_q is going to be dumped to RL.
Generally FD_q is derived from a received FD_m (roughly FD_q := FD_m)
in queue_event and the master's (A) is installed.
At one step with the assignment the Relay-Log's checksum alg is set to
a new value: RL.(A) := FD_q.(A). If the slave service is stopped
the last time assigned RL.(A) will be passed over to the restarting
service (to the current execution point).
RL.A is a "codec" to verify checksum in queue_event() almost all the time
the first fake Rotate event.
Starting from this point IO thread will executes the following checksum
warmup sequence of actions:
FD_q.A := RL.A,
A_m^0 := master.@@global.binlog_checksum,
{queue_event(R_f): verifies(R_f, A_m^0)},
{queue_event(FD_m): verifies(FD_m, FD_m.A), dump(FD_q), rotate(RL),
FD_q := FD_m, RL.A := FD_q.A)}
See legends definition on MYSQL_BIN_LOG::relay_log_checksum_alg
docs lines (binlog.h).
In above A_m^0 - the value of master's
@@binlog_checksum determined in the upcoming handshake (stored in
mi->checksum_alg_before_fd).
After the warm-up sequence IO gets to "normal" checksum verification mode
to use RL.A in
{queue_event(E_m): verifies(E_m, RL.A)}
until it has received a new FD_m.
*/
mi->get_mi_description_event()->common_footer->checksum_alg =
mi->rli->relay_log.relay_log_checksum_alg;
DBUG_ASSERT(mi->get_mi_description_event()->common_footer->checksum_alg !=
binary_log::BINLOG_CHECKSUM_ALG_UNDEF);
DBUG_ASSERT(mi->rli->relay_log.relay_log_checksum_alg !=
binary_log::BINLOG_CHECKSUM_ALG_UNDEF);
mysql_mutex_unlock(&mi->data_lock);
mysql_mutex_unlock(mi->rli->relay_log.get_log_lock());
/*
Compare the master and slave's clock. Do not die if master's clock is
unavailable (very old master not supporting UNIX_TIMESTAMP()?).
*/
DBUG_EXECUTE_IF("dbug.before_get_UNIX_TIMESTAMP", {
const char act[] =
"now "
"wait_for signal.get_unix_timestamp";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
master_res = nullptr;
if (!mysql_real_query(mysql, STRING_WITH_LEN("SELECT UNIX_TIMESTAMP()")) &&
(master_res = mysql_store_result(mysql)) &&
(master_row = mysql_fetch_row(master_res))) {
mysql_mutex_lock(&mi->data_lock);
mi->clock_diff_with_master =
(long)(time((time_t *)0) - strtoul(master_row[0], 0, 10));
DBUG_EXECUTE_IF("dbug.mts.force_clock_diff_eq_0",
mi->clock_diff_with_master = 0;);
mysql_mutex_unlock(&mi->data_lock);
} else if (check_io_slave_killed(mi->info_thd, mi, nullptr))
goto slave_killed_err;
else if (is_network_error(mysql_errno(mysql))) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"Get master clock failed with error: %s", mysql_error(mysql));
goto network_err;
} else {
mysql_mutex_lock(&mi->data_lock);
mi->clock_diff_with_master = 0; /* The "most sensible" value */
mysql_mutex_unlock(&mi->data_lock);
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_SECONDS_BEHIND_MASTER_DUBIOUS,
mysql_error(mysql), mysql_errno(mysql));
}
if (master_res) {
mysql_free_result(master_res);
master_res = nullptr;
}
/*
Check that the master's server id and ours are different. Because if they
are equal (which can result from a simple copy of master's datadir to slave,
thus copying some my.cnf), replication will work but all events will be
skipped.
Do not die if SELECT @@SERVER_ID fails on master (very old master?).
Note: we could have put a @@SERVER_ID in the previous SELECT
UNIX_TIMESTAMP() instead, but this would not have worked on 3.23 masters.
*/
DBUG_EXECUTE_IF("dbug.before_get_SERVER_ID", {
const char act[] =
"now "
"wait_for signal.get_server_id";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
master_res = nullptr;
master_row = nullptr;
DBUG_EXECUTE_IF("get_master_server_id.ER_NET_READ_INTERRUPTED", {
DBUG_SET("+d,inject_ER_NET_READ_INTERRUPTED");
DBUG_SET(
"-d,get_master_server_id."
"ER_NET_READ_INTERRUPTED");
});
if (!mysql_real_query(mysql, STRING_WITH_LEN("SELECT @@GLOBAL.SERVER_ID")) &&
(master_res = mysql_store_result(mysql)) &&
(master_row = mysql_fetch_row(master_res))) {
if ((::server_id == (mi->master_id = strtoul(master_row[0], 0, 10))) &&
!mi->rli->replicate_same_server_id) {
errmsg =
"The slave I/O thread stops because master and slave have equal \
MySQL server ids; these ids must be different for replication to work (or \
the --replicate-same-server-id option must be used on slave but this does \
not always make sense; please check the manual before using it).";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, ER_THD(current_thd, ER_SLAVE_FATAL_ERROR), errmsg);
goto err;
}
} else if (mysql_errno(mysql) != ER_UNKNOWN_SYSTEM_VARIABLE) {
if (check_io_slave_killed(mi->info_thd, mi, nullptr))
goto slave_killed_err;
else if (is_network_error(mysql_errno(mysql))) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"Get master SERVER_ID failed with error: %s",
mysql_error(mysql));
goto network_err;
}
/* Fatal error */
errmsg =
"The slave I/O thread stops because a fatal error is encountered \
when it try to get the value of SERVER_ID variable from master.";
err_code = mysql_errno(mysql);
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
goto err;
} else {
mi->report(WARNING_LEVEL, ER_SERVER_UNKNOWN_SYSTEM_VARIABLE,
"Unknown system variable 'SERVER_ID' on master, \
maybe it is a *VERY OLD MASTER*.");
}
if (master_res) {
mysql_free_result(master_res);
master_res = nullptr;
}
if (mi->master_id == 0 && mi->ignore_server_ids->dynamic_ids.size() > 0) {
errmsg =
"Slave configured with server id filtering could not detect the master "
"server id.";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, ER_THD(current_thd, ER_SLAVE_FATAL_ERROR), errmsg);
goto err;
}
if (mi->heartbeat_period != 0.0) {
char llbuf[22];
const char query_format[] = "SET @master_heartbeat_period= %s";
char query[sizeof(query_format) - 2 + sizeof(llbuf)];
/*
the period is an ulonglong of nano-secs.
*/
llstr((ulonglong)(mi->heartbeat_period * 1000000000UL), llbuf);
sprintf(query, query_format, llbuf);
if (mysql_real_query(mysql, query, static_cast<ulong>(strlen(query)))) {
if (check_io_slave_killed(mi->info_thd, mi, nullptr))
goto slave_killed_err;
if (is_network_error(mysql_errno(mysql))) {
mi->report(
WARNING_LEVEL, mysql_errno(mysql),
"SET @master_heartbeat_period to master failed with error: %s",
mysql_error(mysql));
mysql_free_result(mysql_store_result(mysql));
goto network_err;
} else {
/* Fatal error */
errmsg =
"The slave I/O thread stops because a fatal error is encountered "
" when it tries to SET @master_heartbeat_period on master.";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
mysql_free_result(mysql_store_result(mysql));
goto err;
}
}
mysql_free_result(mysql_store_result(mysql));
}
/*
Querying if master is capable to checksum and notifying it about own
CRC-awareness. The master's side instant value of @@global.binlog_checksum
is stored in the dump thread's uservar area as well as cached locally
to become known in consensus by master and slave.
*/
if (DBUG_EVALUATE_IF("simulate_slave_unaware_checksum", 0, 1)) {
int rc;
const char query[] =
"SET @master_binlog_checksum= @@global.binlog_checksum";
master_res = nullptr;
// initially undefined
mi->checksum_alg_before_fd = binary_log::BINLOG_CHECKSUM_ALG_UNDEF;
/*
@c checksum_alg_before_fd is queried from master in this block.
If master is old checksum-unaware the value stays undefined.
Once the first FD will be received its alg descriptor will replace
the being queried one.
*/
rc = mysql_real_query(mysql, query, static_cast<ulong>(strlen(query)));
if (rc != 0) {
mi->checksum_alg_before_fd = binary_log::BINLOG_CHECKSUM_ALG_OFF;
if (check_io_slave_killed(mi->info_thd, mi, nullptr))
goto slave_killed_err;
if (mysql_errno(mysql) == ER_UNKNOWN_SYSTEM_VARIABLE) {
// this is tolerable as OM -> NS is supported
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"Notifying master by %s failed with "
"error: %s",
query, mysql_error(mysql));
} else {
if (is_network_error(mysql_errno(mysql))) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"Notifying master by %s failed with "
"error: %s",
query, mysql_error(mysql));
mysql_free_result(mysql_store_result(mysql));
goto network_err;
} else {
errmsg =
"The slave I/O thread stops because a fatal error is encountered "
"when it tried to SET @master_binlog_checksum on master.";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
mysql_free_result(mysql_store_result(mysql));
goto err;
}
}
} else {
mysql_free_result(mysql_store_result(mysql));
if (!mysql_real_query(
mysql, STRING_WITH_LEN("SELECT @master_binlog_checksum")) &&
(master_res = mysql_store_result(mysql)) &&
(master_row = mysql_fetch_row(master_res)) &&
(master_row[0] != nullptr)) {
mi->checksum_alg_before_fd = static_cast<enum_binlog_checksum_alg>(
find_type(master_row[0], &binlog_checksum_typelib, 1) - 1);
DBUG_EXECUTE_IF("undefined_algorithm_on_slave",
mi->checksum_alg_before_fd =
binary_log::BINLOG_CHECKSUM_ALG_UNDEF;);
if (mi->checksum_alg_before_fd ==
binary_log::BINLOG_CHECKSUM_ALG_UNDEF) {
errmsg =
"The slave I/O thread was stopped because a fatal error is "
"encountered "
"The checksum algorithm used by master is unknown to slave.";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
mysql_free_result(mysql_store_result(mysql));
goto err;
}
// valid outcome is either of
DBUG_ASSERT(mi->checksum_alg_before_fd ==
binary_log::BINLOG_CHECKSUM_ALG_OFF ||
mi->checksum_alg_before_fd ==
binary_log::BINLOG_CHECKSUM_ALG_CRC32);
} else if (check_io_slave_killed(mi->info_thd, mi, nullptr))
goto slave_killed_err;
else if (is_network_error(mysql_errno(mysql))) {
mi->report(WARNING_LEVEL, mysql_errno(mysql),
"Get master BINLOG_CHECKSUM failed with error: %s",
mysql_error(mysql));
goto network_err;
} else {
errmsg =
"The slave I/O thread stops because a fatal error is encountered "
"when it tried to SELECT @master_binlog_checksum.";
err_code = ER_SLAVE_FATAL_ERROR;
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
mysql_free_result(mysql_store_result(mysql));
goto err;
}
}
if (master_res) {
mysql_free_result(master_res);
master_res = nullptr;
}
} else
mi->checksum_alg_before_fd = binary_log::BINLOG_CHECKSUM_ALG_OFF;
if (DBUG_EVALUATE_IF("simulate_slave_unaware_gtid", 0, 1)) {
enum_gtid_mode master_gtid_mode = GTID_MODE_OFF;
enum_gtid_mode slave_gtid_mode =
mi->get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE);
switch (io_thread_init_command(mi, "SELECT @@GLOBAL.GTID_MODE",
ER_UNKNOWN_SYSTEM_VARIABLE, &master_res,
&master_row)) {
case COMMAND_STATUS_ERROR:
return 2;
case COMMAND_STATUS_ALLOWED_ERROR:
// master is old and does not have @@GLOBAL.GTID_MODE
master_gtid_mode = GTID_MODE_OFF;
break;
case COMMAND_STATUS_OK: {
bool error = false;
const char *master_gtid_mode_string = master_row[0];
DBUG_EXECUTE_IF("simulate_master_has_gtid_mode_on_something",
{ master_gtid_mode_string = "on_something"; });
DBUG_EXECUTE_IF("simulate_master_has_gtid_mode_off_something",
{ master_gtid_mode_string = "off_something"; });
DBUG_EXECUTE_IF("simulate_master_has_unknown_gtid_mode",
{ master_gtid_mode_string = "Krakel Spektakel"; });
master_gtid_mode = get_gtid_mode(master_gtid_mode_string, &error);
if (error) {
// For potential future compatibility, allow unknown
// GTID_MODEs that begin with ON/OFF (treating them as ON/OFF
// respectively).
enum_gtid_mode mode = GTID_MODE_OFF;
for (int i = 0; i < 2; i++) {
switch (is_str_prefix_case(get_gtid_mode_string(mode),
master_gtid_mode_string)) {
case 0: // is not a prefix; continue loop
break;
case 1: // is a true prefix, i.e. not equal
mi->report(WARNING_LEVEL, ER_UNKNOWN_ERROR,
"The master uses an unknown GTID_MODE '%s'. "
"Treating it as '%s'.",
master_gtid_mode_string, get_gtid_mode_string(mode));
// fall through
case 2: // is equal
error = false;
master_gtid_mode = mode;
break;
}
mode = GTID_MODE_ON;
}
}
if (error) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
"The slave IO thread stops because the master has "
"an unknown @@GLOBAL.GTID_MODE '%s'.",
master_gtid_mode_string);
mysql_free_result(master_res);
return 1;
}
mysql_free_result(master_res);
break;
}
}
if ((slave_gtid_mode == GTID_MODE_OFF &&
master_gtid_mode >= GTID_MODE_ON_PERMISSIVE) ||
(slave_gtid_mode == GTID_MODE_ON &&
master_gtid_mode <= GTID_MODE_OFF_PERMISSIVE)) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
"The replication receiver thread cannot start because "
"the master has GTID_MODE = %.192s and this server has "
"GTID_MODE = %.192s.",
get_gtid_mode_string(master_gtid_mode),
get_gtid_mode_string(slave_gtid_mode));
return 1;
}
if (mi->is_auto_position() && master_gtid_mode != GTID_MODE_ON) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
"The replication receiver thread cannot start in "
"AUTO_POSITION mode: the master has GTID_MODE = %.192s "
"instead of ON.",
get_gtid_mode_string(master_gtid_mode));
return 1;
}
}
err:
if (errmsg) {
if (master_res) mysql_free_result(master_res);
DBUG_ASSERT(err_code != 0);
mi->report(ERROR_LEVEL, err_code, "%s", err_buff);
return 1;
}
return 0;
network_err:
if (master_res) mysql_free_result(master_res);
return 2;
slave_killed_err:
if (master_res) mysql_free_result(master_res);
return 2;
}
static bool wait_for_relay_log_space(Relay_log_info *rli) {
bool slave_killed = 0;
Master_info *mi = rli->mi;
PSI_stage_info old_stage;
THD *thd = mi->info_thd;
DBUG_TRACE;
mysql_mutex_lock(&rli->log_space_lock);
thd->ENTER_COND(&rli->log_space_cond, &rli->log_space_lock,
&stage_waiting_for_relay_log_space, &old_stage);
while (rli->log_space_limit < rli->log_space_total &&
!(slave_killed = io_slave_killed(thd, mi)) &&
!rli->ignore_log_space_limit)
mysql_cond_wait(&rli->log_space_cond, &rli->log_space_lock);
/*
Makes the IO thread read only one event at a time
until the SQL thread is able to purge the relay
logs, freeing some space.
Therefore, once the SQL thread processes this next
event, it goes to sleep (no more events in the queue),
sets ignore_log_space_limit=true and wakes the IO thread.
However, this event may have been enough already for
the SQL thread to purge some log files, freeing
rli->log_space_total .
This guarantees that the SQL and IO thread move
forward only one event at a time (to avoid deadlocks),
when the relay space limit is reached. It also
guarantees that when the SQL thread is prepared to
rotate (to be able to purge some logs), the IO thread
will know about it and will rotate.
NOTE: The ignore_log_space_limit is only set when the SQL
thread sleeps waiting for events.
*/
if (rli->ignore_log_space_limit) {
#ifndef DBUG_OFF
{
char llbuf1[22], llbuf2[22];
DBUG_PRINT("info", ("log_space_limit=%s "
"log_space_total=%s "
"ignore_log_space_limit=%d "
"sql_force_rotate_relay=%d",
llstr(rli->log_space_limit, llbuf1),
llstr(rli->log_space_total, llbuf2),
(int)rli->ignore_log_space_limit,
(int)rli->sql_force_rotate_relay));
}
#endif
if (rli->sql_force_rotate_relay) {
rotate_relay_log(mi, true, true, false);
rli->sql_force_rotate_relay = false;
}
rli->ignore_log_space_limit = false;
}
mysql_mutex_unlock(&rli->log_space_lock);
thd->EXIT_COND(&old_stage);
return slave_killed;
}
/*
Builds a Rotate and writes it to relay log.
The caller must hold mi->data_lock.
@param thd pointer to I/O Thread's Thd.
@param mi point to I/O Thread metadata class.
@param force_flush_mi_info when true, do not respect sync period and flush
information.
when false, flush will only happen if it is time to
flush.
@return 0 if everything went fine, 1 otherwise.
*/
static int write_rotate_to_master_pos_into_relay_log(THD *thd, Master_info *mi,
bool force_flush_mi_info) {
Relay_log_info *rli = mi->rli;
int error = 0;
DBUG_TRACE;
DBUG_ASSERT(thd == mi->info_thd);
mysql_mutex_assert_owner(rli->relay_log.get_log_lock());
DBUG_PRINT("info", ("writing a Rotate event to the relay log"));
Rotate_log_event *ev = new Rotate_log_event(mi->get_master_log_name(), 0,
mi->get_master_log_pos(),
Rotate_log_event::DUP_NAME);
DBUG_EXECUTE_IF("fail_generating_rotate_event_on_write_rotate_to_master_pos",
{
if (likely((bool)ev)) {
delete ev;
ev = nullptr;
}
});
if (likely((bool)ev)) {
if (mi->get_mi_description_event() != nullptr)
ev->common_footer->checksum_alg =
mi->get_mi_description_event()->common_footer->checksum_alg;
ev->server_id = 0; // don't be ignored by slave SQL thread
if (unlikely(rli->relay_log.write_event(ev, mi) != 0))
mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE,
ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
"failed to write a Rotate event"
" to the relay log, SHOW SLAVE STATUS may be"
" inaccurate");
mysql_mutex_lock(&mi->data_lock);
if (flush_master_info(mi, force_flush_mi_info, false, false)) {
error = 1;
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_CANT_FLUSH_MASTER_INFO_FILE);
}
mysql_mutex_unlock(&mi->data_lock);
delete ev;
} else {
error = 1;
mi->report(ERROR_LEVEL, ER_SLAVE_CREATE_EVENT_FAILURE,
ER_THD(thd, ER_SLAVE_CREATE_EVENT_FAILURE),
"Rotate_event (out of memory?),"
" SHOW SLAVE STATUS may be inaccurate");
}
return error;
}
/*
Builds a Rotate from the ignored events' info and writes it to relay log.
@param thd pointer to I/O Thread's Thd.
@param mi point to I/O Thread metadata class.
@return 0 if everything went fine, 1 otherwise.
*/
static int write_ignored_events_info_to_relay_log(THD *thd, Master_info *mi) {
Relay_log_info *rli = mi->rli;
mysql_mutex_t *end_pos_lock = rli->relay_log.get_binlog_end_pos_lock();
int error = 0;
DBUG_TRACE;
DBUG_ASSERT(thd == mi->info_thd);
mysql_mutex_lock(rli->relay_log.get_log_lock());
mysql_mutex_lock(end_pos_lock);
if (rli->ign_master_log_name_end[0]) {
DBUG_PRINT("info", ("writing a Rotate event to track down ignored events"));
/*
If the ignored events' info still hold, they should have same info as
the mi->get_master_log_[name|pos].
*/
DBUG_ASSERT(
strcmp(rli->ign_master_log_name_end, mi->get_master_log_name()) == 0);
DBUG_ASSERT(rli->ign_master_log_pos_end == mi->get_master_log_pos());
/* Avoid the applier to get the ignored event' info by rli->ign* */
rli->ign_master_log_name_end[0] = 0;
/* can unlock before writing as the relay log will soon have our Rotate */
mysql_mutex_unlock(end_pos_lock);
/* Generate the rotate based on mi position */
error = write_rotate_to_master_pos_into_relay_log(
thd, mi, false /* force_flush_mi_info */);
} else
mysql_mutex_unlock(end_pos_lock);
mysql_mutex_unlock(rli->relay_log.get_log_lock());
return error;
}
static int register_slave_on_master(MYSQL *mysql, Master_info *mi,
bool *suppress_warnings) {
uchar buf[1024], *pos = buf;
size_t report_host_len = 0, report_user_len = 0, report_password_len = 0;
DBUG_TRACE;
*suppress_warnings = false;
if (report_host) report_host_len = strlen(report_host);
if (report_host_len > HOSTNAME_LENGTH) {
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_REPORT_HOST_TOO_LONG, report_host_len,
HOSTNAME_LENGTH, mi->get_for_channel_str());
return 0;
}
if (report_user) report_user_len = strlen(report_user);
if (report_user_len > USERNAME_LENGTH) {
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_REPORT_USER_TOO_LONG, report_user_len,
USERNAME_LENGTH, mi->get_for_channel_str());
return 0;
}
if (report_password) report_password_len = strlen(report_password);
if (report_password_len > MAX_PASSWORD_LENGTH) {
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_REPORT_PASSWORD_TOO_LONG,
report_password_len, MAX_PASSWORD_LENGTH, mi->get_for_channel_str());
return 0;
}
int4store(pos, server_id);
pos += 4;
pos = net_store_data(pos, (uchar *)report_host, report_host_len);
pos = net_store_data(pos, (uchar *)report_user, report_user_len);
pos = net_store_data(pos, (uchar *)report_password, report_password_len);
int2store(pos, (uint16)report_port);
pos += 2;
/*
Fake rpl_recovery_rank, which was removed in BUG#13963,
so that this server can register itself on old servers,
see BUG#49259.
*/
int4store(pos, /* rpl_recovery_rank */ 0);
pos += 4;
/* The master will fill in master_id */
int4store(pos, 0);
pos += 4;
if (simple_command(mysql, COM_REGISTER_SLAVE, buf, (size_t)(pos - buf), 0)) {
if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED) {
*suppress_warnings = true; // Suppress reconnect warning
} else if (!check_io_slave_killed(mi->info_thd, mi, nullptr)) {
char buf[256];
snprintf(buf, sizeof(buf), "%s (Errno: %d)", mysql_error(mysql),
mysql_errno(mysql));
mi->report(ERROR_LEVEL, ER_SLAVE_MASTER_COM_FAILURE,
ER_THD(current_thd, ER_SLAVE_MASTER_COM_FAILURE),
"COM_REGISTER_SLAVE", buf);
}
return 1;
}
DBUG_EXECUTE_IF("simulate_register_slave_killed", {
mi->abort_slave = 1;
return 1;
};);
return 0;
}
/**
Function that fills the metadata required for SHOW SLAVE STATUS.
This function shall be used in two cases:
1) SHOW SLAVE STATUS FOR ALL CHANNELS
2) SHOW SLAVE STATUS for a channel
@param[in,out] field_list field_list to fill the metadata
@param[in] io_gtid_set_size the size to be allocated to store
the retrieved gtid set
@param[in] sql_gtid_set_size the size to be allocated to store
the executed gtid set
@todo return a bool after adding catching the exceptions to the
push_back() methods for field_list.
*/
static void show_slave_status_metadata(List<Item> &field_list,
int io_gtid_set_size,
int sql_gtid_set_size) {
field_list.push_back(new Item_empty_string("Slave_IO_State", 14));
field_list.push_back(
new Item_empty_string("Master_Host", HOSTNAME_LENGTH + 1));
field_list.push_back(
new Item_empty_string("Master_User", USERNAME_LENGTH + 1));
field_list.push_back(new Item_return_int("Master_Port", 7, MYSQL_TYPE_LONG));
field_list.push_back(
new Item_return_int("Connect_Retry", 10, MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Master_Log_File", FN_REFLEN));
field_list.push_back(
new Item_return_int("Read_Master_Log_Pos", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(new Item_empty_string("Relay_Log_File", FN_REFLEN));
field_list.push_back(
new Item_return_int("Relay_Log_Pos", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(
new Item_empty_string("Relay_Master_Log_File", FN_REFLEN));
field_list.push_back(new Item_empty_string("Slave_IO_Running", 3));
field_list.push_back(new Item_empty_string("Slave_SQL_Running", 3));
field_list.push_back(new Item_empty_string("Replicate_Do_DB", 20));
field_list.push_back(new Item_empty_string("Replicate_Ignore_DB", 20));
field_list.push_back(new Item_empty_string("Replicate_Do_Table", 20));
field_list.push_back(new Item_empty_string("Replicate_Ignore_Table", 23));
field_list.push_back(new Item_empty_string("Replicate_Wild_Do_Table", 24));
field_list.push_back(
new Item_empty_string("Replicate_Wild_Ignore_Table", 28));
field_list.push_back(new Item_return_int("Last_Errno", 4, MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Last_Error", 20));
field_list.push_back(
new Item_return_int("Skip_Counter", 10, MYSQL_TYPE_LONG));
field_list.push_back(
new Item_return_int("Exec_Master_Log_Pos", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(
new Item_return_int("Relay_Log_Space", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(new Item_empty_string("Until_Condition", 6));
field_list.push_back(new Item_empty_string("Until_Log_File", FN_REFLEN));
field_list.push_back(
new Item_return_int("Until_Log_Pos", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(new Item_empty_string("Master_SSL_Allowed", 7));
field_list.push_back(new Item_empty_string("Master_SSL_CA_File", FN_REFLEN));
field_list.push_back(new Item_empty_string("Master_SSL_CA_Path", FN_REFLEN));
field_list.push_back(new Item_empty_string("Master_SSL_Cert", FN_REFLEN));
field_list.push_back(new Item_empty_string("Master_SSL_Cipher", FN_REFLEN));
field_list.push_back(new Item_empty_string("Master_SSL_Key", FN_REFLEN));
field_list.push_back(
new Item_return_int("Seconds_Behind_Master", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(
new Item_empty_string("Master_SSL_Verify_Server_Cert", 3));
field_list.push_back(
new Item_return_int("Last_IO_Errno", 4, MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Last_IO_Error", 20));
field_list.push_back(
new Item_return_int("Last_SQL_Errno", 4, MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Last_SQL_Error", 20));
field_list.push_back(
new Item_empty_string("Replicate_Ignore_Server_Ids", FN_REFLEN));
field_list.push_back(
new Item_return_int("Master_Server_Id", sizeof(ulong), MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Master_UUID", UUID_LENGTH));
field_list.push_back(
new Item_empty_string("Master_Info_File", 2 * FN_REFLEN));
field_list.push_back(new Item_return_int("SQL_Delay", 10, MYSQL_TYPE_LONG));
field_list.push_back(
new Item_return_int("SQL_Remaining_Delay", 8, MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Slave_SQL_Running_State", 20));
field_list.push_back(
new Item_return_int("Master_Retry_Count", 10, MYSQL_TYPE_LONGLONG));
field_list.push_back(
new Item_empty_string("Master_Bind", HOSTNAME_LENGTH + 1));
field_list.push_back(new Item_empty_string("Last_IO_Error_Timestamp", 20));
field_list.push_back(new Item_empty_string("Last_SQL_Error_Timestamp", 20));
field_list.push_back(new Item_empty_string("Master_SSL_Crl", FN_REFLEN));
field_list.push_back(new Item_empty_string("Master_SSL_Crlpath", FN_REFLEN));
field_list.push_back(
new Item_empty_string("Retrieved_Gtid_Set", io_gtid_set_size));
field_list.push_back(
new Item_empty_string("Executed_Gtid_Set", sql_gtid_set_size));
field_list.push_back(
new Item_return_int("Auto_Position", sizeof(ulong), MYSQL_TYPE_LONG));
field_list.push_back(new Item_empty_string("Replicate_Rewrite_DB", 24));
field_list.push_back(
new Item_empty_string("Channel_Name", CHANNEL_NAME_LENGTH));
field_list.push_back(new Item_empty_string("Master_TLS_Version", FN_REFLEN));
field_list.push_back(
new Item_empty_string("Master_public_key_path", FN_REFLEN));
field_list.push_back(new Item_return_int("Get_master_public_key",
sizeof(ulong), MYSQL_TYPE_LONG));
field_list.push_back(
new Item_empty_string("Network_Namespace", NAME_LEN + 1));
}
/**
Send the data to the client of a Master_info during show_slave_status()
This function has to be called after calling show_slave_status_metadata().
Just before sending the data, thd->get_protocol() is prepared to (re)send;
@param[in] thd client thread
@param[in] mi the master info. In the case of multisource
replication, this master info corresponds to a
channel.
@param[in] io_gtid_set_buffer buffer related to Retrieved GTID set
for each channel.
@param[in] sql_gtid_set_buffer buffer related to Executed GTID set
for each channel.
@return
@retval 0 success
@retval 1 Error
*/
static bool show_slave_status_send_data(THD *thd, Master_info *mi,
char *io_gtid_set_buffer,
char *sql_gtid_set_buffer) {
DBUG_TRACE;
Protocol *protocol = thd->get_protocol();
char *slave_sql_running_state = nullptr;
Rpl_filter *rpl_filter = mi->rli->rpl_filter;
DBUG_PRINT("info", ("host is set: '%s'", mi->host));
protocol->start_row();
/*
slave_running can be accessed without run_lock but not other
non-volatile members like mi->info_thd or rli->info_thd, for
them either info_thd_lock or run_lock hold is required.
*/
mysql_mutex_lock(&mi->info_thd_lock);
protocol->store(mi->info_thd ? mi->info_thd->get_proc_info() : "",
&my_charset_bin);
mysql_mutex_unlock(&mi->info_thd_lock);
mysql_mutex_lock(&mi->rli->info_thd_lock);
slave_sql_running_state = const_cast<char *>(
mi->rli->info_thd ? mi->rli->info_thd->get_proc_info() : "");
mysql_mutex_unlock(&mi->rli->info_thd_lock);
mysql_mutex_lock(&mi->data_lock);
mysql_mutex_lock(&mi->rli->data_lock);
mysql_mutex_lock(&mi->err_lock);
mysql_mutex_lock(&mi->rli->err_lock);
DEBUG_SYNC(thd, "wait_after_lock_active_mi_and_rli_data_lock_is_acquired");
protocol->store(mi->host, &my_charset_bin);
protocol->store(mi->get_user(), &my_charset_bin);
protocol->store((uint32)mi->port);
protocol->store((uint32)mi->connect_retry);
protocol->store(mi->get_master_log_name(), &my_charset_bin);
protocol->store((ulonglong)mi->get_master_log_pos());
protocol->store(mi->rli->get_group_relay_log_name() +
dirname_length(mi->rli->get_group_relay_log_name()),
&my_charset_bin);
protocol->store((ulonglong)mi->rli->get_group_relay_log_pos());
protocol->store(mi->rli->get_group_master_log_name(), &my_charset_bin);
protocol->store(
mi->slave_running == MYSQL_SLAVE_RUN_CONNECT
? "Yes"
: (mi->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT ? "Connecting"
: "No"),
&my_charset_bin);
protocol->store(mi->rli->slave_running ? "Yes" : "No", &my_charset_bin);
/*
Acquire the read lock, because the filter may be modified by
CHANGE REPLICATION FILTER when slave is not running.
*/
rpl_filter->rdlock();
store(protocol, rpl_filter->get_do_db());
store(protocol, rpl_filter->get_ignore_db());
char buf[256];
String tmp(buf, sizeof(buf), &my_charset_bin);
rpl_filter->get_do_table(&tmp);
protocol->store(&tmp);
rpl_filter->get_ignore_table(&tmp);
protocol->store(&tmp);
rpl_filter->get_wild_do_table(&tmp);
protocol->store(&tmp);
rpl_filter->get_wild_ignore_table(&tmp);
protocol->store(&tmp);
protocol->store(mi->rli->last_error().number);
protocol->store(mi->rli->last_error().message, &my_charset_bin);
protocol->store((uint32)mi->rli->slave_skip_counter);
protocol->store((ulonglong)mi->rli->get_group_master_log_pos());
protocol->store((ulonglong)mi->rli->log_space_total);
const char *until_type = "";
switch (mi->rli->until_condition) {
case Relay_log_info::UNTIL_NONE:
until_type = "None";
break;
case Relay_log_info::UNTIL_MASTER_POS:
until_type = "Master";
break;
case Relay_log_info::UNTIL_RELAY_POS:
until_type = "Relay";
break;
case Relay_log_info::UNTIL_SQL_BEFORE_GTIDS:
until_type = "SQL_BEFORE_GTIDS";
break;
case Relay_log_info::UNTIL_SQL_AFTER_GTIDS:
until_type = "SQL_AFTER_GTIDS";
break;
case Relay_log_info::UNTIL_SQL_VIEW_ID:
until_type = "SQL_VIEW_ID";
break;
case Relay_log_info::UNTIL_SQL_AFTER_MTS_GAPS:
until_type = "SQL_AFTER_MTS_GAPS";
break;
case Relay_log_info::UNTIL_DONE:
until_type = "DONE";
break;
default:
DBUG_ASSERT(0);
}
protocol->store(until_type, &my_charset_bin);
protocol->store(mi->rli->get_until_log_name(), &my_charset_bin);
protocol->store((ulonglong)mi->rli->get_until_log_pos());
#ifdef HAVE_OPENSSL
protocol->store(mi->ssl ? "Yes" : "No", &my_charset_bin);
#else
protocol->store(mi->ssl ? "Ignored" : "No", &my_charset_bin);
#endif
protocol->store(mi->ssl_ca, &my_charset_bin);
protocol->store(mi->ssl_capath, &my_charset_bin);
protocol->store(mi->ssl_cert, &my_charset_bin);
protocol->store(mi->ssl_cipher, &my_charset_bin);
protocol->store(mi->ssl_key, &my_charset_bin);
/*
The pseudo code to compute Seconds_Behind_Master:
if (SQL thread is running)
{
if (SQL thread processed all the available relay log)
{
if (IO thread is running)
print 0;
else
print NULL;
}
else
compute Seconds_Behind_Master;
}
else
print NULL;
*/
if (mi->rli->slave_running) {
/*
Check if SQL thread is at the end of relay log
Checking should be done using two conditions
condition1: compare the log positions and
condition2: compare the file names (to handle rotation case)
*/
if ((mi->get_master_log_pos() == mi->rli->get_group_master_log_pos()) &&
(!strcmp(mi->get_master_log_name(),
mi->rli->get_group_master_log_name()))) {
if (mi->slave_running == MYSQL_SLAVE_RUN_CONNECT)
protocol->store(0LL);
else
protocol->store_null();
} else {
long time_diff = ((long)(time(0) - mi->rli->last_master_timestamp) -
mi->clock_diff_with_master);
/*
Apparently on some systems time_diff can be <0. Here are possible
reasons related to MySQL:
- the master is itself a slave of another master whose time is ahead.
- somebody used an explicit SET TIMESTAMP on the master.
Possible reason related to granularity-to-second of time functions
(nothing to do with MySQL), which can explain a value of -1:
assume the master's and slave's time are perfectly synchronized, and
that at slave's connection time, when the master's timestamp is read,
it is at the very end of second 1, and (a very short time later) when
the slave's timestamp is read it is at the very beginning of second
2. Then the recorded value for master is 1 and the recorded value for
slave is 2. At SHOW SLAVE STATUS time, assume that the difference
between timestamp of slave and rli->last_master_timestamp is 0
(i.e. they are in the same second), then we get 0-(2-1)=-1 as a result.
This confuses users, so we don't go below 0: hence the max().
last_master_timestamp == 0 (an "impossible" timestamp 1970) is a
special marker to say "consider we have caught up".
*/
protocol->store(
(longlong)(mi->rli->last_master_timestamp ? max(0L, time_diff) : 0));
}
} else {
protocol->store_null();
}
protocol->store(mi->ssl_verify_server_cert ? "Yes" : "No", &my_charset_bin);
// Last_IO_Errno
protocol->store(mi->last_error().number);
// Last_IO_Error
protocol->store(mi->last_error().message, &my_charset_bin);
// Last_SQL_Errno
protocol->store(mi->rli->last_error().number);
// Last_SQL_Error
protocol->store(mi->rli->last_error().message, &my_charset_bin);
// Replicate_Ignore_Server_Ids
{
char buff[FN_REFLEN];
ulong i, cur_len;
for (i = 0, buff[0] = 0, cur_len = 0;
i < mi->ignore_server_ids->dynamic_ids.size(); i++) {
ulong s_id, slen;
char sbuff[FN_REFLEN];
s_id = mi->ignore_server_ids->dynamic_ids[i];
slen = sprintf(sbuff, (i == 0 ? "%lu" : ", %lu"), s_id);
if (cur_len + slen + 4 > FN_REFLEN) {
/*
break the loop whenever remained space could not fit
ellipses on the next cycle
*/
sprintf(buff + cur_len, "...");
break;
}
cur_len += sprintf(buff + cur_len, "%s", sbuff);
}
protocol->store(buff, &my_charset_bin);
}
// Master_Server_id
protocol->store((uint32)mi->master_id);
protocol->store(mi->master_uuid, &my_charset_bin);
// Master_Info_File
protocol->store(mi->get_description_info(), &my_charset_bin);
// SQL_Delay
protocol->store((uint32)mi->rli->get_sql_delay());
// SQL_Remaining_Delay
if (slave_sql_running_state == stage_sql_thd_waiting_until_delay.m_name) {
time_t t = my_time(0), sql_delay_end = mi->rli->get_sql_delay_end();
protocol->store((uint32)(t < sql_delay_end ? sql_delay_end - t : 0));
} else
protocol->store_null();
// Slave_SQL_Running_State
protocol->store(slave_sql_running_state, &my_charset_bin);
// Master_Retry_Count
protocol->store((ulonglong)mi->retry_count);
// Master_Bind
protocol->store(mi->bind_addr, &my_charset_bin);
// Last_IO_Error_Timestamp
protocol->store(mi->last_error().timestamp, &my_charset_bin);
// Last_SQL_Error_Timestamp
protocol->store(mi->rli->last_error().timestamp, &my_charset_bin);
// Master_Ssl_Crl
protocol->store(mi->ssl_crl, &my_charset_bin);
// Master_Ssl_Crlpath
protocol->store(mi->ssl_crlpath, &my_charset_bin);
// Retrieved_Gtid_Set
protocol->store(io_gtid_set_buffer, &my_charset_bin);
// Executed_Gtid_Set
protocol->store(sql_gtid_set_buffer, &my_charset_bin);
// Auto_Position
protocol->store(mi->is_auto_position() ? 1 : 0);
// Replicate_Rewrite_DB
rpl_filter->get_rewrite_db(&tmp);
protocol->store(&tmp);
// channel_name
protocol->store(mi->get_channel(), &my_charset_bin);
// Master_TLS_Version
protocol->store(mi->tls_version, &my_charset_bin);
// Master_public_key_path
protocol->store(mi->public_key_path, &my_charset_bin);
// Get_master_public_key
protocol->store(mi->get_public_key ? 1 : 0);
protocol->store(mi->network_namespace_str(), &my_charset_bin);
rpl_filter->unlock();
mysql_mutex_unlock(&mi->rli->err_lock);
mysql_mutex_unlock(&mi->err_lock);
mysql_mutex_unlock(&mi->rli->data_lock);
mysql_mutex_unlock(&mi->data_lock);
return false;
}
/**
Method to the show the replication status in all channels.
@param[in] thd the client thread
@return
@retval 0 success
@retval 1 Error
*/
bool show_slave_status(THD *thd) {
List<Item> field_list;
Protocol *protocol = thd->get_protocol();
int sql_gtid_set_size = 0, io_gtid_set_size = 0;
Master_info *mi = nullptr;
char *sql_gtid_set_buffer = nullptr;
char **io_gtid_set_buffer_array;
/*
We need the maximum size of the retrieved gtid set (i.e io_gtid_set_size).
This size is needed to reserve the place in show_slave_status_metadata().
So, we travel all the mi's and find out the maximum size of io_gtid_set_size
and pass it through show_slave_status_metadata()
*/
int max_io_gtid_set_size = io_gtid_set_size;
uint idx;
uint num_io_gtid_sets;
bool ret = true;
DBUG_TRACE;
channel_map.assert_some_lock();
num_io_gtid_sets = channel_map.get_num_instances();
io_gtid_set_buffer_array =
(char **)my_malloc(key_memory_show_slave_status_io_gtid_set,
num_io_gtid_sets * sizeof(char *), MYF(MY_WME));
if (io_gtid_set_buffer_array == nullptr) return true;
global_sid_lock->wrlock();
const Gtid_set *sql_gtid_set = gtid_state->get_executed_gtids();
sql_gtid_set_size = sql_gtid_set->to_string(&sql_gtid_set_buffer);
global_sid_lock->unlock();
idx = 0;
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
/*
The following statement is needed because, when mi->host[0]=0
we don't alloc memory for retried_gtid_set. However, we try
to free it at the end, causing a crash. To be on safeside,
we initialize it to NULL, so that my_free() takes care of it.
*/
io_gtid_set_buffer_array[idx] = nullptr;
if (Master_info::is_configured(mi)) {
const Gtid_set *io_gtid_set = mi->rli->get_gtid_set();
mi->rli->get_sid_lock()->wrlock();
/*
@todo: a single memory allocation improves speed,
instead of doing it for each loop
*/
if ((io_gtid_set_size =
io_gtid_set->to_string(&io_gtid_set_buffer_array[idx])) < 0) {
my_eof(thd);
my_free(sql_gtid_set_buffer);
for (uint i = 0; i < idx - 1; i++) {
my_free(io_gtid_set_buffer_array[i]);
}
my_free(io_gtid_set_buffer_array);
mi->rli->get_sid_lock()->unlock();
return true;
} else
max_io_gtid_set_size = max_io_gtid_set_size > io_gtid_set_size
? max_io_gtid_set_size
: io_gtid_set_size;
mi->rli->get_sid_lock()->unlock();
}
idx++;
}
show_slave_status_metadata(field_list, max_io_gtid_set_size,
sql_gtid_set_size);
if (thd->send_result_metadata(&field_list,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) {
goto err;
}
/* Run through each mi */
idx = 0;
for (mi_map::iterator it = channel_map.begin(); it != channel_map.end();
it++) {
mi = it->second;
if (Master_info::is_configured(mi)) {
if (show_slave_status_send_data(thd, mi, io_gtid_set_buffer_array[idx],
sql_gtid_set_buffer))
goto err;
if (protocol->end_row()) goto err;
}
idx++;
}
ret = false;
err:
my_eof(thd);
for (uint i = 0; i < num_io_gtid_sets; i++) {
my_free(io_gtid_set_buffer_array[i]);
}
my_free(io_gtid_set_buffer_array);
my_free(sql_gtid_set_buffer);
return ret;
}
/**
Execute a SHOW SLAVE STATUS statement.
@param thd Pointer to THD object for the client thread executing the
statement.
@param mi Pointer to Master_info object for the IO thread.
@retval false success
@retval true failure
Currently, show slave status works for a channel too, in multisource
replication. But using performance schema tables is better.
*/
bool show_slave_status(THD *thd, Master_info *mi) {
List<Item> field_list;
Protocol *protocol = thd->get_protocol();
char *sql_gtid_set_buffer = nullptr, *io_gtid_set_buffer = nullptr;
int sql_gtid_set_size = 0, io_gtid_set_size = 0;
DBUG_TRACE;
if (mi != nullptr) {
global_sid_lock->wrlock();
const Gtid_set *sql_gtid_set = gtid_state->get_executed_gtids();
sql_gtid_set_size = sql_gtid_set->to_string(&sql_gtid_set_buffer);
global_sid_lock->unlock();
mi->rli->get_sid_lock()->wrlock();
const Gtid_set *io_gtid_set = mi->rli->get_gtid_set();
io_gtid_set_size = io_gtid_set->to_string(&io_gtid_set_buffer);
mi->rli->get_sid_lock()->unlock();
if (sql_gtid_set_size < 0 || io_gtid_set_size < 0) {
my_eof(thd);
my_free(sql_gtid_set_buffer);
my_free(io_gtid_set_buffer);
return true;
}
}
/* Fill the metadata required for show slave status. */
show_slave_status_metadata(field_list, io_gtid_set_size, sql_gtid_set_size);
if (thd->send_result_metadata(&field_list,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) {
my_free(sql_gtid_set_buffer);
my_free(io_gtid_set_buffer);
return true;
}
if (Master_info::is_configured(mi)) {
if (show_slave_status_send_data(thd, mi, io_gtid_set_buffer,
sql_gtid_set_buffer))
return true;
if (protocol->end_row()) {
my_free(sql_gtid_set_buffer);
my_free(io_gtid_set_buffer);
return true;
}
}
my_eof(thd);
my_free(sql_gtid_set_buffer);
my_free(io_gtid_set_buffer);
return false;
}
/**
Entry point for SHOW SLAVE STATUS command. Function displayes
the slave status for all channels or for a single channel
based on the FOR CHANNEL clause.
@param[in] thd the client thread.
@return
@retval false ok
@retval true not ok
*/
bool show_slave_status_cmd(THD *thd) {
Master_info *mi = nullptr;
LEX *lex = thd->lex;
bool res;
DBUG_TRACE;
channel_map.rdlock();
if (!lex->mi.for_channel)
res = show_slave_status(thd);
else {
mi = channel_map.get_mi(lex->mi.channel);
/*
When mi is NULL, that means the channel doesn't exist, SSS
will throw an error.
*/
if (mi == nullptr) {
my_error(ER_SLAVE_CHANNEL_DOES_NOT_EXIST, MYF(0), lex->mi.channel);
channel_map.unlock();
return true;
}
/*
If the channel being used is a group replication applier channel we
need to disable the SHOW SLAVE STATUS commannd as its output is not
compatible with this command.
*/
if (channel_map.is_group_replication_channel_name(mi->get_channel(),
true)) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"SHOW SLAVE STATUS", mi->get_channel());
channel_map.unlock();
return true;
}
res = show_slave_status(thd, mi);
}
channel_map.unlock();
return res;
}
void set_slave_thread_options(THD *thd) {
DBUG_TRACE;
/*
It's nonsense to constrain the slave threads with max_join_size; if a
query succeeded on master, we HAVE to execute it. So set
OPTION_BIG_SELECTS. Setting max_join_size to HA_POS_ERROR is not enough
(and it's not needed if we have OPTION_BIG_SELECTS) because an INSERT
SELECT examining more than 4 billion rows would still fail (yes, because
when max_join_size is 4G, OPTION_BIG_SELECTS is automatically set, but
only for client threads.
*/
ulonglong options = thd->variables.option_bits | OPTION_BIG_SELECTS;
if (opt_log_slave_updates)
options |= OPTION_BIN_LOG;
else
options &= ~OPTION_BIN_LOG;
thd->variables.option_bits = options;
thd->variables.completion_type = 0;
/*
Set autocommit= 1 when info tables are used and autocommit == 0 to
avoid trigger asserts on mysql_execute_command(THD *thd) caused by
info tables updates which do not commit, like Rotate, Stop and
skipped events handling.
*/
if ((thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) &&
(opt_mi_repository_id == INFO_REPOSITORY_TABLE ||
opt_rli_repository_id == INFO_REPOSITORY_TABLE)) {
thd->variables.option_bits |= OPTION_AUTOCOMMIT;
thd->variables.option_bits &= ~OPTION_NOT_AUTOCOMMIT;
thd->server_status |= SERVER_STATUS_AUTOCOMMIT;
}
/*
Set thread InnoDB high priority.
*/
DBUG_EXECUTE_IF("dbug_set_high_prio_sql_thread", {
if (thd->system_thread == SYSTEM_THREAD_SLAVE_SQL ||
thd->system_thread == SYSTEM_THREAD_SLAVE_WORKER)
thd->thd_tx_priority = 1;
});
}
void set_slave_thread_default_charset(THD *thd, Relay_log_info const *rli) {
DBUG_TRACE;
thd->variables.character_set_client =
global_system_variables.character_set_client;
thd->variables.collation_connection =
global_system_variables.collation_connection;
thd->variables.collation_server = global_system_variables.collation_server;
thd->update_charset();
/*
We use a const cast here since the conceptual (and externally
visible) behavior of the function is to set the default charset of
the thread. That the cache has to be invalidated is a secondary
effect.
*/
const_cast<Relay_log_info *>(rli)->cached_charset_invalidate();
}
/*
init_slave_thread()
*/
static int init_slave_thread(THD *thd, SLAVE_THD_TYPE thd_type) {
DBUG_TRACE;
#if !defined(DBUG_OFF)
int simulate_error = 0;
#endif
thd->system_thread = (thd_type == SLAVE_THD_WORKER)
? SYSTEM_THREAD_SLAVE_WORKER
: (thd_type == SLAVE_THD_SQL)
? SYSTEM_THREAD_SLAVE_SQL
: SYSTEM_THREAD_SLAVE_IO;
thd->get_protocol_classic()->init_net(0);
thd->slave_thread = 1;
thd->enable_slow_log = opt_log_slow_slave_statements;
set_slave_thread_options(thd);
/*
Replication threads are:
- background threads in the server, not user sessions,
- yet still assigned a PROCESSLIST_ID,
for historical reasons (displayed in SHOW PROCESSLIST).
*/
thd->set_new_thread_id();
#ifdef HAVE_PSI_THREAD_INTERFACE
/*
Populate the PROCESSLIST_ID in the instrumentation.
*/
struct PSI_thread *psi = PSI_THREAD_CALL(get_thread)();
PSI_THREAD_CALL(set_thread_id)(psi, thd->thread_id());
#endif /* HAVE_PSI_THREAD_INTERFACE */
DBUG_EXECUTE_IF("simulate_io_slave_error_on_init",
simulate_error |= (1 << SLAVE_THD_IO););
DBUG_EXECUTE_IF("simulate_sql_slave_error_on_init",
simulate_error |= (1 << SLAVE_THD_SQL););
thd->store_globals();
#if !defined(DBUG_OFF)
if (simulate_error & (1 << thd_type)) {
return -1;
}
#endif
if (thd_type == SLAVE_THD_SQL) {
THD_STAGE_INFO(thd, stage_waiting_for_the_next_event_in_relay_log);
thd->set_command(
COM_QUERY); // the SQL thread does not use the server protocol
} else {
THD_STAGE_INFO(thd, stage_waiting_for_master_update);
}
thd->set_time();
/* Do not use user-supplied timeout value for system threads. */
thd->variables.lock_wait_timeout = LONG_TIMEOUT;
return 0;
}
/**
Sleep for a given amount of time or until killed.
@param thd Thread context of the current thread.
@param seconds The number of seconds to sleep.
@param func Function object to check if the thread has been killed.
@param info The Rpl_info object associated with this sleep.
@retval True if the thread has been killed, false otherwise.
*/
template <typename killed_func, typename rpl_info>
static inline bool slave_sleep(THD *thd, time_t seconds, killed_func func,
rpl_info info) {
bool ret;
struct timespec abstime;
mysql_mutex_t *lock = &info->sleep_lock;
mysql_cond_t *cond = &info->sleep_cond;
/* Absolute system time at which the sleep time expires. */
set_timespec(&abstime, seconds);
mysql_mutex_lock(lock);
thd->ENTER_COND(cond, lock, nullptr, nullptr);
while (!(ret = func(thd, info))) {
int error = mysql_cond_timedwait(cond, lock, &abstime);
if (is_timeout(error)) break;
}
mysql_mutex_unlock(lock);
thd->EXIT_COND(nullptr);
return ret;
}
/**
Callback function for mysql_binlog_open().
Sets gtid data in the command packet.
@param rpl Replication stream information.
@param packet_gtid_set Pointer to command packet where gtid
data should be stored.
*/
static void fix_gtid_set(MYSQL_RPL *rpl, uchar *packet_gtid_set) {
Gtid_set *gtid_set = (Gtid_set *)rpl->gtid_set_arg;
gtid_set->encode(packet_gtid_set);
}
static int request_dump(THD *thd, MYSQL *mysql, MYSQL_RPL *rpl, Master_info *mi,
bool *suppress_warnings) {
DBUG_TRACE;
enum_server_command command =
mi->is_auto_position() ? COM_BINLOG_DUMP_GTID : COM_BINLOG_DUMP;
/*
Note: binlog_flags is always 0. However, in versions up to 5.6
RC, the master would check the lowest bit and do something
unexpected if it was set; in early versions of 5.6 it would also
use the two next bits. Therefore, for backward compatibility,
if we ever start to use the flags, we should leave the three
lowest bits unused.
*/
uint binlog_flags = 0;
*suppress_warnings = false;
if (RUN_HOOK(binlog_relay_io, before_request_transmit,
(thd, mi, binlog_flags)))
return 1;
rpl->server_id = server_id;
rpl->flags = binlog_flags;
Sid_map sid_map(nullptr); /* No lock needed */
/*
Note: should be declared at the same level as the mysql_binlog_open() call,
as the latter might call fix_gtid_set() which in turns calls
gtid_executed->encode().
*/
Gtid_set gtid_executed(&sid_map);
if (command == COM_BINLOG_DUMP_GTID) {
// get set of GTIDs
mi->rli->get_sid_lock()->wrlock();
if (gtid_executed.add_gtid_set(mi->rli->get_gtid_set()) !=
RETURN_STATUS_OK) {
mi->rli->get_sid_lock()->unlock();
return 1;
}
mi->rli->get_sid_lock()->unlock();
global_sid_lock->wrlock();
gtid_state->dbug_print();
if (gtid_executed.add_gtid_set(gtid_state->get_executed_gtids()) !=
RETURN_STATUS_OK) {
global_sid_lock->unlock();
return 1;
}
global_sid_lock->unlock();
rpl->file_name = nullptr; /* No need to set rpl.file_name_length */
rpl->start_position = 4;
rpl->flags |= MYSQL_RPL_GTID;
rpl->gtid_set_encoded_size = gtid_executed.get_encoded_length();
rpl->fix_gtid_set = fix_gtid_set;
rpl->gtid_set_arg = (void *)&gtid_executed;
} else {
rpl->file_name_length = 0;
rpl->file_name = mi->get_master_log_name();
rpl->start_position = DBUG_EVALUATE_IF("request_master_log_pos_3", 3,
mi->get_master_log_pos());
}
if (mysql_binlog_open(mysql, rpl)) {
/*
Something went wrong, so we will just reconnect and retry later
in the future, we should do a better error analysis, but for
now we just fill up the error log :-)
*/
if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED)
*suppress_warnings = true; // Suppress reconnect warning
else
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_ERROR_RETRYING,
command_name[command].str, mysql_errno(mysql), mysql_error(mysql),
mi->connect_retry);
return 1;
}
return 0;
}
/**
Read one event from the master.
@param mysql MySQL connection.
@param rpl Replication stream information.
@param mi Master connection information.
@param suppress_warnings true when a normal net read timeout has caused us
to try a reconnect. We do not want to print
anything to the error log in this case because
this an abnormal event in an idle server.
@retval 'packet_error' Error.
@retval number Length of packet.
*/
static ulong read_event(MYSQL *mysql, MYSQL_RPL *rpl, Master_info *mi,
bool *suppress_warnings) {
DBUG_TRACE;
*suppress_warnings = false;
/*
my_real_read() will time us out
We check if we were told to die, and if not, try reading again
*/
#ifndef DBUG_OFF
if (disconnect_slave_event_count && !(mi->events_until_exit--))
return packet_error;
#endif
if (mysql_binlog_fetch(mysql, rpl)) {
if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED) {
/*
We are trying a normal reconnect after a read timeout;
we suppress prints to .err file as long as the reconnect
happens without problems
*/
*suppress_warnings = true;
} else if (!mi->abort_slave) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_ERROR_READING_FROM_SERVER,
mi->get_for_channel_str(), mysql_error(mysql), mysql_errno(mysql));
}
return packet_error;
}
/* Check if eof packet */
if (rpl->size == 0) {
LogErr(SYSTEM_LEVEL, ER_RPL_SLAVE_DUMP_THREAD_KILLED_BY_MASTER,
mi->get_for_channel_str(), ::server_uuid, mysql_error(mysql));
return packet_error;
}
DBUG_PRINT("exit", ("len: %lu net->read_pos[4]: %d", rpl->size,
mysql->net.read_pos[4]));
return rpl->size - 1;
}
/**
If this is a lagging slave (specified with CHANGE MASTER TO MASTER_DELAY = X),
delays accordingly. Also unlocks rli->data_lock.
Design note: this is the place to unlock rli->data_lock. The lock
must be held when reading delay info from rli, but it should not be
held while sleeping.
@param ev Event that is about to be executed.
@param thd The sql thread's THD object.
@param rli The sql thread's Relay_log_info structure.
@retval 0 If the delay timed out and the event shall be executed.
@retval nonzero If the delay was interrupted and the event shall be skipped.
*/
static int sql_delay_event(Log_event *ev, THD *thd, Relay_log_info *rli) {
time_t sql_delay = rli->get_sql_delay();
DBUG_TRACE;
mysql_mutex_assert_owner(&rli->data_lock);
DBUG_ASSERT(!rli->belongs_to_client());
if (sql_delay) {
int type = ev->get_type_code();
time_t sql_delay_end = 0;
if (rli->commit_timestamps_status == Relay_log_info::COMMIT_TS_UNKNOWN &&
(type == binary_log::GTID_LOG_EVENT ||
type == binary_log::ANONYMOUS_GTID_LOG_EVENT)) {
if (static_cast<Gtid_log_event *>(ev)->has_commit_timestamps &&
DBUG_EVALUATE_IF("sql_delay_without_timestamps", 0, 1)) {
rli->commit_timestamps_status = Relay_log_info::COMMIT_TS_FOUND;
} else {
rli->commit_timestamps_status = Relay_log_info::COMMIT_TS_NOT_FOUND;
}
}
if (rli->commit_timestamps_status == Relay_log_info::COMMIT_TS_FOUND) {
if (type == binary_log::GTID_LOG_EVENT ||
type == binary_log::ANONYMOUS_GTID_LOG_EVENT) {
/*
Calculate when we should execute the event.
The immediate master timestamp is expressed in microseconds.
Delayed replication is defined in seconds.
Hence convert immediate_commit_timestamp to seconds here.
*/
sql_delay_end = ceil((static_cast<Gtid_log_event *>(ev)
->immediate_commit_timestamp) /
1000000.00) +
sql_delay;
}
} else {
/*
the immediate master does not support commit timestamps
in Gtid_log_events
*/
if (type != binary_log::ROTATE_EVENT &&
type != binary_log::FORMAT_DESCRIPTION_EVENT &&
type != binary_log::PREVIOUS_GTIDS_LOG_EVENT) {
// Calculate when we should execute the event.
sql_delay_end = ev->common_header->when.tv_sec +
rli->mi->clock_diff_with_master + sql_delay;
}
}
if (sql_delay_end != 0) {
// The current time.
time_t now = my_time(0);
// The amount of time we will have to sleep before executing the event.
time_t nap_time = 0;
if (sql_delay_end > now) {
nap_time = sql_delay_end - now;
DBUG_PRINT("info",
("sql_delay= %lu "
"now= %ld "
"sql_delay_end= %ld "
"nap_time= %ld",
sql_delay, (long)now, (long)sql_delay_end, (long)nap_time));
DBUG_PRINT("info", ("delaying replication event %lu secs", nap_time));
rli->start_sql_delay(sql_delay_end);
mysql_mutex_unlock(&rli->data_lock);
return slave_sleep(thd, nap_time, sql_slave_killed, rli);
} else {
DBUG_PRINT("info", ("sql_delay= %lu "
"now= %ld "
"sql_delay_end= %ld ",
sql_delay, (long)now, (long)sql_delay_end));
}
}
}
mysql_mutex_unlock(&rli->data_lock);
return 0;
}
/**
Applies the given event and advances the relay log position.
This is needed by the sql thread to execute events from the binlog,
and by clients executing BINLOG statements. Conceptually, this
function does:
@code
ev->apply_event(rli);
ev->update_pos(rli);
@endcode
It also does the following maintainance:
- Initializes the thread's server_id and time; and the event's
thread.
- If !rli->belongs_to_client() (i.e., if it belongs to the slave
sql thread instead of being used for executing BINLOG
statements), it does the following things: (1) skips events if it
is needed according to the server id or slave_skip_counter; (2)
unlocks rli->data_lock; (3) sleeps if required by 'CHANGE MASTER
TO MASTER_DELAY=X'; (4) maintains the running state of the sql
thread (rli->thread_state).
- Reports errors as needed.
@param ptr_ev a pointer to a reference to the event to apply.
@param thd The client thread that executes the event (i.e., the
slave sql thread if called from a replication slave, or the client
thread if called to execute a BINLOG statement).
@param rli The relay log info (i.e., the slave's rli if called from
a replication slave, or the client's thd->rli_fake if called to
execute a BINLOG statement).
@note MTS can store NULL to @c ptr_ev location to indicate
the event is taken over by a Worker.
@retval SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK
OK.
@retval SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPLY_ERROR
Error calling ev->apply_event().
@retval SLAVE_APPLY_EVENT_AND_UPDATE_POS_UPDATE_POS_ERROR
No error calling ev->apply_event(), but error calling
ev->update_pos().
@retval SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPEND_JOB_ERROR
append_item_to_jobs() failed, thread was killed while waiting
for successful enqueue on worker.
*/
static enum enum_slave_apply_event_and_update_pos_retval
apply_event_and_update_pos(Log_event **ptr_ev, THD *thd, Relay_log_info *rli) {
int exec_res = 0;
bool skip_event = false;
Log_event *ev = *ptr_ev;
Log_event::enum_skip_reason reason = Log_event::EVENT_SKIP_NOT;
DBUG_TRACE;
DBUG_PRINT("exec_event",
("%s(type_code: %d; server_id: %d)", ev->get_type_str(),
ev->get_type_code(), ev->server_id));
DBUG_PRINT("info",
("thd->options: %s%s; rli->last_event_start_time: %lu",
FLAGSTR(thd->variables.option_bits, OPTION_NOT_AUTOCOMMIT),
FLAGSTR(thd->variables.option_bits, OPTION_BEGIN),
(ulong)rli->last_event_start_time));
/*
Execute the event to change the database and update the binary
log coordinates, but first we set some data that is needed for
the thread.
The event will be executed unless it is supposed to be skipped.
Queries originating from this server must be skipped. Low-level
events (Format_description_log_event, Rotate_log_event,
Stop_log_event) from this server must also be skipped. But for
those we don't want to modify 'group_master_log_pos', because
these events did not exist on the master.
Format_description_log_event is not completely skipped.
Skip queries specified by the user in 'slave_skip_counter'. We
can't however skip events that has something to do with the log
files themselves.
Filtering on own server id is extremely important, to ignore
execution of events created by the creation/rotation of the relay
log (remember that now the relay log starts with its Format_desc,
has a Rotate etc).
*/
/*
Set the unmasked and actual server ids from the event
*/
thd->server_id = ev->server_id; // use the original server id for logging
thd->unmasked_server_id = ev->common_header->unmasked_server_id;
thd->set_time(); // time the query
thd->lex->set_current_select(0);
if (!ev->common_header->when.tv_sec)
my_micro_time_to_timeval(my_micro_time(), &ev->common_header->when);
ev->thd = thd; // because up to this point, ev->thd == 0
if (!(rli->is_mts_recovery() &&
bitmap_is_set(&rli->recovery_groups, rli->mts_recovery_index))) {
reason = ev->shall_skip(rli);
}
#ifndef DBUG_OFF
if (rli->is_mts_recovery()) {
DBUG_PRINT("mts",
("Mts is recovering %d, number of bits set %d, "
"bitmap is set %d, index %lu.\n",
rli->is_mts_recovery(), bitmap_bits_set(&rli->recovery_groups),
bitmap_is_set(&rli->recovery_groups, rli->mts_recovery_index),
rli->mts_recovery_index));
}
#endif
if (reason == Log_event::EVENT_SKIP_COUNT) {
--rli->slave_skip_counter;
skip_event = true;
}
set_timespec_nsec(&rli->ts_exec[0], 0);
rli->stats_read_time += diff_timespec(&rli->ts_exec[0], &rli->ts_exec[1]);
if (reason == Log_event::EVENT_SKIP_NOT) {
// Sleeps if needed, and unlocks rli->data_lock.
if (sql_delay_event(ev, thd, rli))
return SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK;
exec_res = ev->apply_event(rli);
DBUG_EXECUTE_IF("simulate_stop_when_mts_in_group",
if (rli->mts_group_status == Relay_log_info::MTS_IN_GROUP &&
rli->curr_group_seen_begin)
DBUG_SET("+d,stop_when_mts_in_group"););
if (!exec_res && (ev->worker != rli)) {
if (ev->worker) {
Slave_job_item item = {ev, rli->get_event_relay_log_number(),
rli->get_event_start_pos()};
Slave_job_item *job_item = &item;
Slave_worker *w = (Slave_worker *)ev->worker;
// specially marked group typically with OVER_MAX_DBS_IN_EVENT_MTS db:s
bool need_sync = ev->is_mts_group_isolated();
// all events except BEGIN-query must be marked with a non-NULL Worker
DBUG_ASSERT(((Slave_worker *)ev->worker) == rli->last_assigned_worker);
DBUG_PRINT("Log_event::apply_event:",
("-> job item data %p to W_%lu", job_item->data, w->id));
// Reset mts in-group state
if (rli->mts_group_status == Relay_log_info::MTS_END_GROUP) {
// CGAP cleanup
rli->curr_group_assigned_parts.clear();
// reset the B-group and Gtid-group marker
rli->curr_group_seen_begin = rli->curr_group_seen_gtid = false;
rli->last_assigned_worker = nullptr;
}
/*
Stroring GAQ index of the group that the event belongs to
in the event. Deferred events are handled similarly below.
*/
ev->mts_group_idx = rli->gaq->assigned_group_index;
bool append_item_to_jobs_error = false;
if (rli->curr_group_da.size() > 0) {
/*
the current event sorted out which partion the current group
belongs to. It's time now to processed deferred array events.
*/
for (uint i = 0; i < rli->curr_group_da.size(); i++) {
Slave_job_item da_item = rli->curr_group_da[i];
DBUG_PRINT("mts", ("Assigning job %llu to worker %lu",
(da_item.data)->common_header->log_pos, w->id));
da_item.data->mts_group_idx =
rli->gaq->assigned_group_index; // similarly to above
if (!append_item_to_jobs_error)
append_item_to_jobs_error = append_item_to_jobs(&da_item, w, rli);
if (append_item_to_jobs_error) delete da_item.data;
}
rli->curr_group_da.clear();
}
if (append_item_to_jobs_error)
return SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPEND_JOB_ERROR;
DBUG_PRINT("mts", ("Assigning job %llu to worker %lu\n",
job_item->data->common_header->log_pos, w->id));
/* Notice `ev' instance can be destoyed after `append()' */
if (append_item_to_jobs(job_item, w, rli))
return SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPEND_JOB_ERROR;
if (need_sync) {
/*
combination of over-max db:s and end of the current group
forces to wait for the assigned groups completion by assigned
to the event worker.
Indeed MTS group status could be safely set to MTS_NOT_IN_GROUP
after wait_() returns.
No need to know a possible error out of synchronization call.
*/
(void)rli->current_mts_submode->wait_for_workers_to_finish(rli);
}
}
*ptr_ev = nullptr; // announcing the event is passed to w-worker
if (rli->is_parallel_exec() && rli->mts_events_assigned % 1024 == 1) {
time_t my_now = my_time(0);
if ((my_now - rli->mts_last_online_stat) >= mts_online_stat_period) {
LogErr(INFORMATION_LEVEL, ER_RPL_MTS_STATISTICS,
rli->get_for_channel_str(),
static_cast<unsigned long>(my_now - rli->mts_last_online_stat),
rli->mts_events_assigned, rli->mts_wq_overrun_cnt,
rli->mts_wq_overfill_cnt, rli->wq_size_waits_cnt,
rli->mts_total_wait_overlap.load(),
rli->mts_wq_no_underrun_cnt, rli->mts_total_wait_worker_avail);
rli->mts_last_online_stat = my_now;
}
}
}
} else
mysql_mutex_unlock(&rli->data_lock);
set_timespec_nsec(&rli->ts_exec[1], 0);
rli->stats_exec_time += diff_timespec(&rli->ts_exec[1], &rli->ts_exec[0]);
DBUG_PRINT("info", ("apply_event error = %d", exec_res));
if (exec_res == 0) {
/*
Positions are not updated here when an XID is processed. To make
a slave crash-safe, positions must be updated while processing a
XID event and as such do not need to be updated here again.
However, if the event needs to be skipped, this means that it
will not be processed and then positions need to be updated here.
DDL:s that are not yet committed, as indicated by
@c has_ddl_committed flag, visit the block.
See sql/rpl_rli.h for further details.
*/
int error = 0;
if (*ptr_ev &&
((ev->get_type_code() != binary_log::XID_EVENT &&
!is_committed_ddl(*ptr_ev)) ||
skip_event ||
(rli->is_mts_recovery() && !is_gtid_event(ev) &&
(ev->ends_group() || !rli->mts_recovery_group_seen_begin) &&
bitmap_is_set(&rli->recovery_groups, rli->mts_recovery_index)))) {
#ifndef DBUG_OFF
/*
This only prints information to the debug trace.
TODO: Print an informational message to the error log?
*/
static const char *const explain[] = {
// EVENT_SKIP_NOT,
"not skipped",
// EVENT_SKIP_IGNORE,
"skipped because event should be ignored",
// EVENT_SKIP_COUNT
"skipped because event skip counter was non-zero"};
DBUG_PRINT("info",
("OPTION_BEGIN: %d; IN_STMT: %d",
static_cast<bool>(thd->variables.option_bits & OPTION_BEGIN),
rli->get_flag(Relay_log_info::IN_STMT)));
DBUG_PRINT("skip_event",
("%s event was %s", ev->get_type_str(), explain[reason]));
#endif
error = ev->update_pos(rli);
/*
Slave skips an event if the slave_skip_counter is greater than zero.
We have to free thd's mem_root here after we update the positions
in the repository table if the event is a skipped event.
Otherwise, imagine a situation where slave_skip_counter is big number
and slave is skipping the events and updating the repository.
All the memory used while these operations are going on is never
freed unless slave starts executing the events (after slave_skip_counter
becomes zero).
Hence we free thd's mem_root here if it is a skipped event.
(freeing mem_root generally happens from Query_log_event::do_apply_event
or Rows_log_event::do_apply_event when they find the end of
the group event).
*/
if (skip_event) free_root(thd->mem_root, MYF(MY_KEEP_PREALLOC));
#ifndef DBUG_OFF
DBUG_PRINT("info", ("update_pos error = %d", error));
if (!rli->belongs_to_client()) {
char buf[22];
DBUG_PRINT("info",
("group %s %s", llstr(rli->get_group_relay_log_pos(), buf),
rli->get_group_relay_log_name()));
DBUG_PRINT("info",
("event %s %s", llstr(rli->get_event_relay_log_pos(), buf),
rli->get_event_relay_log_name()));
}
#endif
} else {
/*
INTVAR_EVENT, RAND_EVENT, USER_VAR_EVENT and ROWS_QUERY_LOG_EVENT are
deferred event. It means ev->worker is NULL.
*/
DBUG_ASSERT(*ptr_ev == ev || rli->is_parallel_exec() ||
(!ev->worker &&
(ev->get_type_code() == binary_log::INTVAR_EVENT ||
ev->get_type_code() == binary_log::RAND_EVENT ||
ev->get_type_code() == binary_log::USER_VAR_EVENT ||
ev->get_type_code() == binary_log::ROWS_QUERY_LOG_EVENT)));
rli->inc_event_relay_log_pos();
}
if (!error && rli->is_mts_recovery() &&
ev->get_type_code() != binary_log::ROTATE_EVENT &&
ev->get_type_code() != binary_log::FORMAT_DESCRIPTION_EVENT &&
ev->get_type_code() != binary_log::PREVIOUS_GTIDS_LOG_EVENT) {
if (ev->starts_group()) {
rli->mts_recovery_group_seen_begin = true;
} else if ((ev->ends_group() || !rli->mts_recovery_group_seen_begin) &&
!is_gtid_event(ev)) {
rli->mts_recovery_index++;
if (--rli->mts_recovery_group_cnt == 0) {
rli->mts_recovery_index = 0;
LogErr(INFORMATION_LEVEL, ER_RPL_MTS_RECOVERY_COMPLETE,
rli->get_for_channel_str(), rli->get_group_relay_log_name(),
rli->get_group_relay_log_pos(),
rli->get_group_master_log_name(),
rli->get_group_master_log_pos());
/*
Few tests wait for UNTIL_SQL_AFTER_MTS_GAPS completion.
Due to exisiting convention the status won't change
prior to slave restarts.
So making of UNTIL_SQL_AFTER_MTS_GAPS completion isdone here,
and only in the debug build to make the test to catch the change
despite a faulty design of UNTIL checking before execution.
*/
if (rli->until_condition ==
Relay_log_info::UNTIL_SQL_AFTER_MTS_GAPS) {
rli->until_condition = Relay_log_info::UNTIL_DONE;
}
// reset the Worker tables to remove last slave session time info
if ((error = rli->mts_finalize_recovery())) {
(void)Rpl_info_factory::reset_workers(rli);
}
}
rli->mts_recovery_group_seen_begin = false;
if (!error) error = rli->flush_info(true);
}
}
if (error) {
/*
The update should not fail, so print an error message and
return an error code.
TODO: Replace this with a decent error message when merged
with BUG#24954 (which adds several new error message).
*/
char buf[22];
rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR,
"It was not possible to update the positions"
" of the relay log information: the slave may"
" be in an inconsistent state."
" Stopped in %s position %s",
rli->get_group_relay_log_name(),
llstr(rli->get_group_relay_log_pos(), buf));
return SLAVE_APPLY_EVENT_AND_UPDATE_POS_UPDATE_POS_ERROR;
}
}
return exec_res ? SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPLY_ERROR
: SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK;
}
/**
Let the worker applying the current group to rollback and gracefully
finish its work before.
@param rli The slave's relay log info.
@param ev a pointer to the event on hold before applying this rollback
procedure.
@retval false The rollback succeeded.
@retval true There was an error while injecting events.
*/
static bool coord_handle_partial_binlogged_transaction(Relay_log_info *rli,
const Log_event *ev) {
DBUG_TRACE;
/*
This function is called holding the rli->data_lock.
We must return it still holding this lock, except in the case of returning
error.
*/
mysql_mutex_assert_owner(&rli->data_lock);
THD *thd = rli->info_thd;
if (!rli->curr_group_seen_begin) {
DBUG_PRINT("info", ("Injecting QUERY(BEGIN) to rollback worker"));
Log_event *begin_event = new Query_log_event(thd, STRING_WITH_LEN("BEGIN"),
true, /* using_trans */
false, /* immediate */
true, /* suppress_use */
0, /* error */
true /* ignore_command */);
((Query_log_event *)begin_event)->db = "";
begin_event->common_header->data_written = 0;
begin_event->server_id = ev->server_id;
/*
We must be careful to avoid SQL thread increasing its position
farther than the event that triggered this QUERY(BEGIN).
*/
begin_event->common_header->log_pos = ev->common_header->log_pos;
begin_event->future_event_relay_log_pos = ev->future_event_relay_log_pos;
if (apply_event_and_update_pos(&begin_event, thd, rli) !=
SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK) {
delete begin_event;
return true;
}
mysql_mutex_lock(&rli->data_lock);
}
DBUG_PRINT("info", ("Injecting QUERY(ROLLBACK) to rollback worker"));
Log_event *rollback_event = new Query_log_event(
thd, STRING_WITH_LEN("ROLLBACK"), true, /* using_trans */
false, /* immediate */
true, /* suppress_use */
0, /* error */
true /* ignore_command */);
((Query_log_event *)rollback_event)->db = "";
rollback_event->common_header->data_written = 0;
rollback_event->server_id = ev->server_id;
/*
We must be careful to avoid SQL thread increasing its position
farther than the event that triggered this QUERY(ROLLBACK).
*/
rollback_event->common_header->log_pos = ev->common_header->log_pos;
rollback_event->future_event_relay_log_pos = ev->future_event_relay_log_pos;
((Query_log_event *)rollback_event)->rollback_injected_by_coord = true;
if (apply_event_and_update_pos(&rollback_event, thd, rli) !=
SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK) {
delete rollback_event;
return true;
}
mysql_mutex_lock(&rli->data_lock);
return false;
}
/**
Top-level function for executing the next event in the relay log.
This is called from the SQL thread.
This function reads the event from the relay log, executes it, and
advances the relay log position. It also handles errors, etc.
This function may fail to apply the event for the following reasons:
- The position specfied by the UNTIL condition of the START SLAVE
command is reached.
- It was not possible to read the event from the log.
- The slave is killed.
- An error occurred when applying the event, and the event has been
tried slave_trans_retries times. If the event has been retried
fewer times, 0 is returned.
- init_info or init_relay_log_pos failed. (These are called
if a failure occurs when applying the event.)
- An error occurred when updating the binlog position.
@retval 0 The event was applied.
@retval 1 The event was not applied.
*/
static int exec_relay_log_event(THD *thd, Relay_log_info *rli,
Rpl_applier_reader *applier_reader) {
DBUG_TRACE;
/*
We acquire this mutex since we need it for all operations except
event execution. But we will release it in places where we will
wait for something for example inside of next_event().
*/
mysql_mutex_lock(&rli->data_lock);
Log_event *ev = nullptr;
#ifndef DBUG_OFF
if (!abort_slave_event_count || rli->events_until_exit--)
#endif
ev = applier_reader->read_next_event();
Log_event **ptr_ev = nullptr;
RLI_current_event_raii rli_c_ev(rli, ev);
if (ev != nullptr) {
/*
To avoid assigned event groups exceeding rli->checkpoint_group, it
need force to compute checkpoint.
*/
bool force = rli->rli_checkpoint_seqno >= rli->checkpoint_group;
if (force || rli->is_time_for_mts_checkpoint()) {
mysql_mutex_unlock(&rli->data_lock);
if (mts_checkpoint_routine(rli, force)) {
delete ev;
return 1;
}
mysql_mutex_lock(&rli->data_lock);
}
}
/*
It should be checked after calling mts_checkpoint_routine(), because that
function could be interrupted by kill while 'force' is true.
*/
if (sql_slave_killed(thd, rli)) {
mysql_mutex_unlock(&rli->data_lock);
delete ev;
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_ERROR_READING_RELAY_LOG_EVENTS,
rli->get_for_channel_str(), "slave SQL thread was killed");
return 1;
}
if (ev) {
enum enum_slave_apply_event_and_update_pos_retval exec_res;
ptr_ev = &ev;
/*
Even if we don't execute this event, we keep the master timestamp,
so that seconds behind master shows correct delta (there are events
that are not replayed, so we keep falling behind).
If it is an artificial event, or a relay log event (IO thread generated
event) or ev->when is set to 0, or a FD from master, or a heartbeat
event with server_id '0' then we don't update the last_master_timestamp.
In case of parallel execution last_master_timestamp is only updated when
a job is taken out of GAQ. Thus when last_master_timestamp is 0 (which
indicates that GAQ is empty, all slave workers are waiting for events from
the Coordinator), we need to initialize it with a timestamp from the first
event to be executed in parallel.
*/
if ((!rli->is_parallel_exec() || rli->last_master_timestamp == 0) &&
!(ev->is_artificial_event() || ev->is_relay_log_event() ||
ev->get_type_code() == binary_log::FORMAT_DESCRIPTION_EVENT ||
ev->server_id == 0)) {
rli->last_master_timestamp =
ev->common_header->when.tv_sec + (time_t)ev->exec_time;
DBUG_ASSERT(rli->last_master_timestamp >= 0);
}
if (rli->is_until_satisfied_before_dispatching_event(ev)) {
/*
Setting abort_slave flag because we do not want additional message about
error in query execution to be printed.
*/
rli->abort_slave = 1;
mysql_mutex_unlock(&rli->data_lock);
delete ev;
return 1;
}
{ /**
The following failure injecion works in cooperation with tests
setting @@global.debug= 'd,incomplete_group_in_relay_log'.
Xid or Commit events are not executed to force the slave sql
read hanging if the realy log does not have any more events.
*/
DBUG_EXECUTE_IF(
"incomplete_group_in_relay_log",
if ((ev->get_type_code() == binary_log::XID_EVENT) ||
((ev->get_type_code() == binary_log::QUERY_EVENT) &&
strcmp("COMMIT", ((Query_log_event *)ev)->query) == 0)) {
DBUG_ASSERT(thd->get_transaction()->cannot_safely_rollback(
Transaction_ctx::SESSION));
rli->abort_slave = 1;
mysql_mutex_unlock(&rli->data_lock);
delete ev;
rli->inc_event_relay_log_pos();
return 0;
};);
}
/*
GTID protocol will put a FORMAT_DESCRIPTION_EVENT from the master with
log_pos != 0 after each (re)connection if auto positioning is enabled.
This means that the SQL thread might have already started to apply the
current group but, as the IO thread had to reconnect, it left this
group incomplete and will start it again from the beginning.
So, before applying this FORMAT_DESCRIPTION_EVENT, we must let the
worker roll back the current group and gracefully finish its work,
before starting to apply the new (complete) copy of the group.
*/
if (ev->get_type_code() == binary_log::FORMAT_DESCRIPTION_EVENT &&
ev->server_id != ::server_id && ev->common_header->log_pos != 0 &&
rli->is_parallel_exec() && rli->curr_group_seen_gtid) {
if (coord_handle_partial_binlogged_transaction(rli, ev))
/*
In the case of an error, coord_handle_partial_binlogged_transaction
will not try to get the rli->data_lock again.
*/
return 1;
}
/* ptr_ev can change to NULL indicating MTS coorinator passed to a Worker */
exec_res = apply_event_and_update_pos(ptr_ev, thd, rli);
/*
Note: the above call to apply_event_and_update_pos executes
mysql_mutex_unlock(&rli->data_lock);
*/
/* For deferred events, the ptr_ev is set to NULL
in Deferred_log_events::add() function.
Hence deferred events wont be deleted here.
They will be deleted in Deferred_log_events::rewind() funciton.
*/
if (*ptr_ev) {
DBUG_ASSERT(*ptr_ev == ev); // event remains to belong to Coordinator
DBUG_EXECUTE_IF("dbug.calculate_sbm_after_previous_gtid_log_event", {
if (ev->get_type_code() == binary_log::PREVIOUS_GTIDS_LOG_EVENT) {
const char act[] =
"now signal signal.reached wait_for signal.done_sbm_calculation";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
}
};);
DBUG_EXECUTE_IF("dbug.calculate_sbm_after_fake_rotate_log_event", {
if (ev->get_type_code() == binary_log::ROTATE_EVENT &&
ev->is_artificial_event()) {
const char act[] =
"now signal signal.reached wait_for signal.done_sbm_calculation";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
}
};);
/*
Format_description_log_event should not be deleted because it will be
used to read info about the relay log's format; it will be deleted when
the SQL thread does not need it, i.e. when this thread terminates.
ROWS_QUERY_LOG_EVENT is destroyed at the end of the current statement
clean-up routine.
*/
if (ev->get_type_code() != binary_log::FORMAT_DESCRIPTION_EVENT &&
ev->get_type_code() != binary_log::ROWS_QUERY_LOG_EVENT) {
DBUG_PRINT("info", ("Deleting the event after it has been executed"));
delete ev;
/*
Raii guard is explicitly instructed to invalidate
otherwise bogus association of the execution context with the being
destroyed above event.
*/
ev = rli->current_event = nullptr;
}
}
/*
exec_res == SLAVE_APPLY_EVENT_AND_UPDATE_POS_UPDATE_POS_ERROR
update_log_pos failed: this should not happen, so we
don't retry.
exec_res == SLAVE_APPLY_EVENT_AND_UPDATE_POS_APPEND_JOB_ERROR
append_item_to_jobs() failed, this happened because
thread was killed while waiting for enqueue on worker.
*/
if (exec_res >= SLAVE_APPLY_EVENT_AND_UPDATE_POS_UPDATE_POS_ERROR) {
delete ev;
return 1;
}
if (slave_trans_retries) {
int temp_err = 0;
bool silent = false;
if (exec_res && !is_mts_worker(thd) /* no reexecution in MTS mode */ &&
(temp_err = rli->has_temporary_error(thd, 0, &silent)) &&
!thd->get_transaction()->cannot_safely_rollback(
Transaction_ctx::SESSION)) {
const char *errmsg;
/*
We were in a transaction which has been rolled back because of a
temporary error;
let's seek back to BEGIN log event and retry it all again.
Note, if lock wait timeout (innodb_lock_wait_timeout exceeded)
there is no rollback since 5.0.13 (ref: manual).
We have to not only seek but also
a) init_info(), to seek back to hot relay log's start for later
(for when we will come back to this hot log after re-processing the
possibly existing old logs where BEGIN is: applier_reader will
then need the cache to be at position 0 (see comments at beginning of
init_info()).
b) init_relay_log_pos(), because the BEGIN may be an older relay log.
*/
if (rli->trans_retries < slave_trans_retries) {
/*
The transactions has to be rolled back before
load_mi_and_rli_from_repositories is called. Because
load_mi_and_rli_from_repositories will starts a new
transaction if master_info_repository is TABLE.
*/
rli->cleanup_context(thd, 1);
/*
Temporary error status is both unneeded and harmful for following
open-and-lock slave system tables but store its number first for
monitoring purposes.
*/
uint temp_trans_errno = thd->get_stmt_da()->mysql_errno();
thd->clear_error();
applier_reader->close();
/*
We need to figure out if there is a test case that covers
this part. \Alfranio.
*/
if (load_mi_and_rli_from_repositories(rli->mi, false, SLAVE_SQL))
LogErr(ERROR_LEVEL,
ER_RPL_SLAVE_FAILED_TO_INIT_MASTER_INFO_STRUCTURE,
rli->get_for_channel_str());
else if (applier_reader->open(&errmsg))
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_CANT_INIT_RELAY_LOG_POSITION,
rli->get_for_channel_str(), errmsg);
else {
exec_res = SLAVE_APPLY_EVENT_AND_UPDATE_POS_OK;
/* chance for concurrent connection to get more locks */
slave_sleep(thd,
min<ulong>(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE),
sql_slave_killed, rli);
mysql_mutex_lock(&rli->data_lock); // because of SHOW STATUS
if (!silent) {
rli->trans_retries++;
if (rli->is_processing_trx()) {
rli->retried_processing(temp_trans_errno,
ER_THD_NONCONST(thd, temp_trans_errno),
rli->trans_retries);
}
}
rli->retried_trans++;
mysql_mutex_unlock(&rli->data_lock);
#ifndef DBUG_OFF
if (rli->trans_retries == 2 || rli->trans_retries == 6) {
DBUG_EXECUTE_IF("rpl_ps_tables_worker_retry", {
char const act[] =
"now SIGNAL signal.rpl_ps_tables_worker_retry_pause "
"WAIT_FOR signal.rpl_ps_tables_worker_retry_continue";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
}
#endif
DBUG_PRINT("info", ("Slave retries transaction "
"rli->trans_retries: %lu",
rli->trans_retries));
}
} else {
thd->fatal_error();
rli->report(ERROR_LEVEL, thd->get_stmt_da()->mysql_errno(),
"Slave SQL thread retried transaction %lu time(s) "
"in vain, giving up. Consider raising the value of "
"the slave_transaction_retries variable.",
rli->trans_retries);
}
} else if ((exec_res && !temp_err) ||
(opt_using_transactions &&
rli->get_group_relay_log_pos() ==
rli->get_event_relay_log_pos())) {
/*
Only reset the retry counter if the entire group succeeded
or failed with a non-transient error. On a successful
event, the execution will proceed as usual; in the case of a
non-transient error, the slave will stop with an error.
*/
rli->trans_retries = 0; // restart from fresh
DBUG_PRINT("info", ("Resetting retry counter, rli->trans_retries: %lu",
rli->trans_retries));
}
}
if (exec_res) {
delete ev;
/* Raii object is explicitly updated 'cos this branch doesn't end func */
rli->current_event = nullptr;
} else if (rli->is_until_satisfied_after_dispatching_event()) {
mysql_mutex_lock(&rli->data_lock);
rli->abort_slave = 1;
mysql_mutex_unlock(&rli->data_lock);
return 1;
}
return exec_res;
}
/*
It is impossible to read next event to finish the event group whenever a
read event error happens. So MTS group status is set to MTS_KILLED_GROUP to
force stop.
*/
if (rli->mts_group_status == Relay_log_info::MTS_IN_GROUP)
rli->mts_group_status = Relay_log_info::MTS_KILLED_GROUP;
mysql_mutex_unlock(&rli->data_lock);
rli->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_READ_FAILURE,
ER_THD(thd, ER_SLAVE_RELAY_LOG_READ_FAILURE),
"\
Could not parse relay log event entry. The possible reasons are: the master's \
binary log is corrupted (you can check this by running 'mysqlbinlog' on the \
binary log), the slave's relay log is corrupted (you can check this by running \
'mysqlbinlog' on the relay log), a network problem, the server was unable to \
fetch a keyring key required to open an encrypted relay log file, or a bug in \
the master's or slave's MySQL code. If you want to check the master's binary \
log or slave's relay log, you will be able to know their names by issuing \
'SHOW SLAVE STATUS' on this slave.\
");
return 1;
}
static bool check_io_slave_killed(THD *thd, Master_info *mi, const char *info) {
if (io_slave_killed(thd, mi)) {
if (info)
LogErr(INFORMATION_LEVEL, ER_RPL_IO_THREAD_KILLED, info,
mi->get_for_channel_str());
return true;
}
return false;
}
/**
@brief Try to reconnect slave IO thread.
@details Terminates current connection to master, sleeps for
@c mi->connect_retry msecs and initiates new connection with
@c safe_reconnect(). Variable pointed by @c retry_count is increased -
if it exceeds @c mi->retry_count then connection is not re-established
and function signals error.
Unless @c suppres_warnings is true, a warning is put in the server error log
when reconnecting. The warning message and messages used to report errors
are taken from @c messages array. In case @c mi->retry_count is exceeded,
no messages are added to the log.
@param[in] thd Thread context.
@param[in] mysql MySQL connection.
@param[in] mi Master connection information.
@param[in,out] retry_count Number of attempts to reconnect.
@param[in] suppress_warnings true when a normal net read timeout
has caused to reconnecting.
@param[in] messages Messages to print/log, see
reconnect_messages[] array.
@retval 0 OK.
@retval 1 There was an error.
*/
static int try_to_reconnect(THD *thd, MYSQL *mysql, Master_info *mi,
uint *retry_count, bool suppress_warnings,
const char *messages[SLAVE_RECON_MSG_MAX]) {
mi->slave_running = MYSQL_SLAVE_RUN_NOT_CONNECT;
thd->proc_info = messages[SLAVE_RECON_MSG_WAIT];
thd->clear_active_vio();
end_server(mysql);
if ((*retry_count)++) {
if (*retry_count > mi->retry_count) return 1; // Don't retry forever
slave_sleep(thd, mi->connect_retry, io_slave_killed, mi);
}
if (check_io_slave_killed(thd, mi, messages[SLAVE_RECON_MSG_KILLED_WAITING]))
return 1;
thd->proc_info = messages[SLAVE_RECON_MSG_AFTER];
if (!suppress_warnings) {
char llbuff[22];
/*
Raise a warining during registering on master/requesting dump.
Log a message reading event.
*/
if (messages[SLAVE_RECON_MSG_COMMAND][0]) {
char buf[256];
snprintf(buf, sizeof(buf), messages[SLAVE_RECON_MSG_FAILED],
mi->get_io_rpl_log_name(),
llstr(mi->get_master_log_pos(), llbuff));
mi->report(WARNING_LEVEL, ER_SLAVE_MASTER_COM_FAILURE,
ER_THD(thd, ER_SLAVE_MASTER_COM_FAILURE),
messages[SLAVE_RECON_MSG_COMMAND], buf);
} else {
LogErr(INFORMATION_LEVEL, ER_SLAVE_RECONNECT_FAILED,
mi->get_io_rpl_log_name(), llstr(mi->get_master_log_pos(), llbuff),
mi->get_for_channel_str());
}
}
if (safe_reconnect(thd, mysql, mi, 1) || io_slave_killed(thd, mi)) {
LogErr(INFORMATION_LEVEL, ER_SLAVE_KILLED_AFTER_RECONNECT);
return 1;
}
return 0;
}
/**
Slave IO thread entry point.
@param arg Pointer to Master_info struct that holds information for
the IO thread.
@return Always 0.
*/
extern "C" void *handle_slave_io(void *arg) {
THD *thd = nullptr; // needs to be first for thread_stack
bool thd_added = false;
MYSQL *mysql;
Master_info *mi = (Master_info *)arg;
Relay_log_info *rli = mi->rli;
char llbuff[22];
uint retry_count;
bool suppress_warnings;
int ret;
bool successfully_connected;
#ifndef DBUG_OFF
uint retry_count_reg = 0, retry_count_dump = 0, retry_count_event = 0;
#endif
Global_THD_manager *thd_manager = Global_THD_manager::get_instance();
// needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff
my_thread_init();
{
DBUG_TRACE;
DBUG_ASSERT(mi->inited);
mysql = nullptr;
retry_count = 0;
mysql_mutex_lock(&mi->run_lock);
/* Inform waiting threads that slave has started */
mi->slave_run_id++;
#ifndef DBUG_OFF
mi->events_until_exit = disconnect_slave_event_count;
#endif
thd = new THD; // note that contructor of THD uses DBUG_ !
THD_CHECK_SENTRY(thd);
mi->info_thd = thd;
#ifdef HAVE_PSI_THREAD_INTERFACE
// save the instrumentation for IO thread in mi->info_thd
struct PSI_thread *psi = PSI_THREAD_CALL(get_thread)();
thd_set_psi(mi->info_thd, psi);
#endif
thd->thread_stack = (char *)&thd; // remember where our stack is
mi->clear_error();
mi->slave_running = 1;
if (init_slave_thread(thd, SLAVE_THD_IO)) {
mysql_cond_broadcast(&mi->start_cond);
mysql_mutex_unlock(&mi->run_lock);
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed during slave I/O thread initialization ");
goto err;
}
thd_manager->add_thd(thd);
thd_added = true;
mi->abort_slave = 0;
mysql_mutex_unlock(&mi->run_lock);
mysql_cond_broadcast(&mi->start_cond);
DBUG_PRINT("master_info",
("log_file_name: '%s' position: %s", mi->get_master_log_name(),
llstr(mi->get_master_log_pos(), llbuff)));
/* This must be called before run any binlog_relay_io hooks */
RPL_MASTER_INFO = mi;
if (RUN_HOOK(binlog_relay_io, thread_start, (thd, mi))) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed to run 'thread_start' hook");
goto err;
}
if (!(mi->mysql = mysql = mysql_init(nullptr))) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR), "error in mysql_init()");
goto err;
}
mysql_extension_set_server_extn(mysql, &mi->server_extn);
THD_STAGE_INFO(thd, stage_connecting_to_master);
if (mi->is_set_network_namespace()) {
#ifdef HAVE_SETNS
if (set_network_namespace(mi->network_namespace)) goto err;
#else
// Network namespace not supported by the platform. Report error.
LogErr(ERROR_LEVEL, ER_NETWORK_NAMESPACES_NOT_SUPPORTED);
goto err;
#endif
// Save default value of network namespace
// Set network namespace before sockets be created
}
successfully_connected = !safe_connect(thd, mysql, mi);
// we can get killed during safe_connect
#ifdef HAVE_SETNS
if (mi->is_set_network_namespace()) {
// Restore original network namespace used to be before connection has
// been created
successfully_connected =
restore_original_network_namespace() | successfully_connected;
}
#endif
if (successfully_connected) {
LogErr(SYSTEM_LEVEL, ER_RPL_SLAVE_CONNECTED_TO_MASTER_REPLICATION_STARTED,
mi->get_for_channel_str(), mi->get_user(), mi->host, mi->port,
mi->get_io_rpl_log_name(),
llstr(mi->get_master_log_pos(), llbuff));
} else {
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_IO_THREAD_KILLED,
mi->get_for_channel_str());
goto err;
}
connected:
/*
When using auto positioning, the slave IO thread will always start reading
a transaction from the beginning of the transaction (transaction's first
event). So, we have to reset the transaction boundary parser after
(re)connecting.
If not using auto positioning, the Relay_log_info::rli_init_info() took
care of putting the mi->transaction_parser in the correct state when
initializing Received_gtid_set from relay log during slave server starts,
as the IO thread might had stopped in the middle of a transaction.
*/
if (mi->is_auto_position()) {
mi->transaction_parser.reset();
mi->clear_queueing_trx(true /* need_lock*/);
}
DBUG_EXECUTE_IF("dbug.before_get_running_status_yes", {
const char act[] =
"now "
"wait_for signal.io_thread_let_running";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
};);
DBUG_EXECUTE_IF("dbug.calculate_sbm_after_previous_gtid_log_event", {
/* Fake that thread started 3 minutes ago */
thd->start_time.tv_sec -= 180;
};);
DBUG_EXECUTE_IF("dbug.calculate_sbm_after_fake_rotate_log_event", {
/* Fake that thread started 3 minutes ago */
thd->start_time.tv_sec -= 180;
};);
mysql_mutex_lock(&mi->run_lock);
mi->slave_running = MYSQL_SLAVE_RUN_CONNECT;
mysql_mutex_unlock(&mi->run_lock);
THD_STAGE_INFO(thd, stage_checking_master_version);
ret = get_master_version_and_clock(mysql, mi);
if (!ret) ret = get_master_uuid(mysql, mi);
if (!ret) ret = io_thread_init_commands(mysql, mi);
if (ret == 1) /* Fatal error */
goto err;
if (ret == 2) {
if (check_io_slave_killed(
mi->info_thd, mi,
"Slave I/O thread killed "
"while calling get_master_version_and_clock(...)"))
goto err;
suppress_warnings = false;
/* Try to reconnect because the error was caused by a transient network
* problem */
if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_REG]))
goto err;
goto connected;
}
/*
Register ourselves with the master.
*/
THD_STAGE_INFO(thd, stage_registering_slave_on_master);
if (register_slave_on_master(mysql, mi, &suppress_warnings)) {
if (!check_io_slave_killed(thd, mi,
"Slave I/O thread killed "
"while registering slave on master")) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_IO_THREAD_CANT_REGISTER_ON_MASTER);
if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_REG]))
goto err;
} else
goto err;
goto connected;
}
DBUG_EXECUTE_IF(
"FORCE_SLAVE_TO_RECONNECT_REG", if (!retry_count_reg) {
retry_count_reg++;
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_FORCING_TO_RECONNECT_IO_THREAD,
mi->get_for_channel_str());
if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_REG]))
goto err;
goto connected;
});
DBUG_PRINT("info", ("Starting reading binary log from master"));
while (!io_slave_killed(thd, mi)) {
MYSQL_RPL rpl;
THD_STAGE_INFO(thd, stage_requesting_binlog_dump);
if (request_dump(thd, mysql, &rpl, mi, &suppress_warnings)) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_ERROR_REQUESTING_BINLOG_DUMP,
mi->get_for_channel_str());
if (check_io_slave_killed(thd, mi,
"Slave I/O thread killed while \
requesting master dump") ||
try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_DUMP]))
goto err;
goto connected;
}
DBUG_EXECUTE_IF(
"FORCE_SLAVE_TO_RECONNECT_DUMP", if (!retry_count_dump) {
retry_count_dump++;
LogErr(INFORMATION_LEVEL,
ER_RPL_SLAVE_FORCING_TO_RECONNECT_IO_THREAD,
mi->get_for_channel_str());
if (try_to_reconnect(thd, mysql, mi, &retry_count,
suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_DUMP]))
goto err;
goto connected;
});
const char *event_buf;
DBUG_ASSERT(mi->last_error().number == 0);
while (!io_slave_killed(thd, mi)) {
ulong event_len;
/*
We say "waiting" because read_event() will wait if there's nothing to
read. But if there's something to read, it will not wait. The
important thing is to not confuse users by saying "reading" whereas
we're in fact receiving nothing.
*/
THD_STAGE_INFO(thd, stage_waiting_for_master_to_send_event);
event_len = read_event(mysql, &rpl, mi, &suppress_warnings);
if (check_io_slave_killed(thd, mi,
"Slave I/O thread killed while \
reading event"))
goto err;
DBUG_EXECUTE_IF(
"FORCE_SLAVE_TO_RECONNECT_EVENT", if (!retry_count_event) {
retry_count_event++;
LogErr(INFORMATION_LEVEL,
ER_RPL_SLAVE_FORCING_TO_RECONNECT_IO_THREAD,
mi->get_for_channel_str());
if (try_to_reconnect(thd, mysql, mi, &retry_count,
suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_EVENT]))
goto err;
goto connected;
});
if (event_len == packet_error) {
uint mysql_error_number = mysql_errno(mysql);
switch (mysql_error_number) {
case CR_NET_PACKET_TOO_LARGE:
LogErr(ERROR_LEVEL,
ER_RPL_LOG_ENTRY_EXCEEDS_SLAVE_MAX_ALLOWED_PACKET,
slave_max_allowed_packet);
mi->report(
ERROR_LEVEL, ER_SERVER_NET_PACKET_TOO_LARGE, "%s",
"Got a packet bigger than 'slave_max_allowed_packet' bytes");
goto err;
case ER_MASTER_FATAL_ERROR_READING_BINLOG:
mi->report(ERROR_LEVEL,
ER_SERVER_MASTER_FATAL_ERROR_READING_BINLOG,
ER_THD(thd, ER_MASTER_FATAL_ERROR_READING_BINLOG),
mysql_error_number, mysql_error(mysql));
goto err;
case ER_OUT_OF_RESOURCES:
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_STOPPING_AS_MASTER_OOM);
mi->report(ERROR_LEVEL, ER_SERVER_OUT_OF_RESOURCES, "%s",
ER_THD(thd, ER_SERVER_OUT_OF_RESOURCES));
goto err;
}
if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings,
reconnect_messages[SLAVE_RECON_ACT_EVENT]))
goto err;
goto connected;
} // if (event_len == packet_error)
retry_count = 0; // ok event, reset retry counter
THD_STAGE_INFO(thd, stage_queueing_master_event_to_the_relay_log);
event_buf = (const char *)mysql->net.read_pos + 1;
DBUG_PRINT("info", ("IO thread received event of type %s",
Log_event::get_type_str(
(Log_event_type)event_buf[EVENT_TYPE_OFFSET])));
if (RUN_HOOK(binlog_relay_io, after_read_event,
(thd, mi, (const char *)mysql->net.read_pos + 1, event_len,
&event_buf, &event_len))) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed to run 'after_read_event' hook");
goto err;
}
/* XXX: 'synced' should be updated by queue_event to indicate
whether event has been synced to disk */
bool synced = 0;
#ifndef DBUG_OFF
bool was_in_trx = false;
if (mi->is_queueing_trx()) {
was_in_trx = true;
DBUG_EXECUTE_IF("rpl_ps_tables_queue", {
const char act[] =
"now SIGNAL signal.rpl_ps_tables_queue_before "
"WAIT_FOR signal.rpl_ps_tables_queue_finish";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
}
#endif
QUEUE_EVENT_RESULT queue_res = queue_event(mi, event_buf, event_len);
if (queue_res == QUEUE_EVENT_ERROR_QUEUING) {
mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE,
ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
"could not queue event from master");
goto err;
}
#ifndef DBUG_OFF
if (was_in_trx && !mi->is_queueing_trx()) {
DBUG_EXECUTE_IF("rpl_ps_tables", {
const char act[] =
"now SIGNAL signal.rpl_ps_tables_queue_after_finish "
"WAIT_FOR signal.rpl_ps_tables_queue_continue";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
}
#endif
if (RUN_HOOK(binlog_relay_io, after_queue_event,
(thd, mi, event_buf, event_len, synced))) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed to run 'after_queue_event' hook");
goto err;
}
/* The event was queued, but there was a failure flushing master info */
if (queue_res == QUEUE_EVENT_ERROR_FLUSHING_INFO) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed to flush master info.");
goto err;
}
DBUG_ASSERT(queue_res == QUEUE_EVENT_OK);
/*
Pause the IO thread execution and wait for
'continue_after_queue_event' signal to continue IO thread
execution.
*/
DBUG_EXECUTE_IF("pause_after_queue_event", {
const char act[] =
"now SIGNAL reached_after_queue_event "
"WAIT_FOR continue_after_queue_event";
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
/*
See if the relay logs take too much space.
We don't lock mi->rli->log_space_lock here; this dirty read saves time
and does not introduce any problem:
- if mi->rli->ignore_log_space_limit is 1 but becomes 0 just after (so
the clean value is 0), then we are reading only one more event as we
should, and we'll block only at the next event. No big deal.
- if mi->rli->ignore_log_space_limit is 0 but becomes 1 just after (so
the clean value is 1), then we are going into
wait_for_relay_log_space() for no reason, but this function will do a
clean read, notice the clean value and exit immediately.
*/
#ifndef DBUG_OFF
{
char llbuf1[22], llbuf2[22];
DBUG_PRINT("info", ("log_space_limit=%s log_space_total=%s \
ignore_log_space_limit=%d",
llstr(rli->log_space_limit, llbuf1),
llstr(rli->log_space_total, llbuf2),
(int)rli->ignore_log_space_limit));
}
#endif
if (rli->log_space_limit &&
rli->log_space_limit < rli->log_space_total &&
!rli->ignore_log_space_limit)
if (wait_for_relay_log_space(rli)) {
LogErr(ERROR_LEVEL,
ER_RPL_SLAVE_IO_THREAD_ABORTED_WAITING_FOR_RELAY_LOG_SPACE);
goto err;
}
DBUG_EXECUTE_IF("flush_after_reading_user_var_event", {
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::USER_VAR_EVENT) {
const char act[] =
"now signal Reached wait_for signal.flush_complete_continue";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
}
});
DBUG_EXECUTE_IF(
"stop_io_after_reading_gtid_log_event",
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::GTID_LOG_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF(
"stop_io_after_reading_query_log_event",
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::QUERY_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF(
"stop_io_after_reading_user_var_log_event",
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::USER_VAR_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF(
"stop_io_after_reading_table_map_event",
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::TABLE_MAP_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF(
"stop_io_after_reading_xid_log_event",
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::XID_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF(
"stop_io_after_reading_write_rows_log_event",
if (event_buf[EVENT_TYPE_OFFSET] == binary_log::WRITE_ROWS_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF(
"stop_io_after_reading_unknown_event",
if (event_buf[EVENT_TYPE_OFFSET] >= binary_log::ENUM_END_EVENT)
thd->killed = THD::KILLED_NO_VALUE;);
DBUG_EXECUTE_IF("stop_io_after_queuing_event",
thd->killed = THD::KILLED_NO_VALUE;);
/*
After event is flushed to relay log file, memory used
by thread's mem_root is not required any more.
Hence adding free_root(thd->mem_root,...) to do the
cleanup, otherwise a long running IO thread can
cause OOM error.
*/
free_root(thd->mem_root, MYF(MY_KEEP_PREALLOC));
}
}
// error = 0;
err:
// print the current replication position
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_IO_THREAD_EXITING,
mi->get_for_channel_str(), mi->get_io_rpl_log_name(),
llstr(mi->get_master_log_pos(), llbuff));
/* At this point the I/O thread will not try to reconnect anymore. */
mi->atomic_is_stopping = true;
(void)RUN_HOOK(binlog_relay_io, thread_stop, (thd, mi));
/*
Pause the IO thread and wait for 'continue_to_stop_io_thread'
signal to continue to shutdown the IO thread.
*/
DBUG_EXECUTE_IF("pause_after_io_thread_stop_hook", {
const char act[] =
"now SIGNAL reached_stopping_io_thread "
"WAIT_FOR continue_to_stop_io_thread";
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
};);
thd->reset_query();
thd->reset_db(NULL_CSTR);
if (mysql) {
/*
Here we need to clear the active VIO before closing the
connection with the master. The reason is that THD::awake()
might be called from terminate_slave_thread() because somebody
issued a STOP SLAVE. If that happends, the shutdown_active_vio()
can be called in the middle of closing the VIO associated with
the 'mysql' object, causing a crash.
*/
thd->clear_active_vio();
mysql_close(mysql);
mi->mysql = nullptr;
}
write_ignored_events_info_to_relay_log(thd, mi);
THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit);
mysql_mutex_lock(&mi->run_lock);
/*
Clean information used to start slave in order to avoid
security issues.
*/
mi->reset_start_info();
/* Forget the relay log's format */
mysql_mutex_lock(rli->relay_log.get_log_lock());
mi->set_mi_description_event(nullptr);
mysql_mutex_unlock(rli->relay_log.get_log_lock());
// destructor will not free it, because net.vio is 0
thd->get_protocol_classic()->end_net();
thd->release_resources();
THD_CHECK_SENTRY(thd);
if (thd_added) thd_manager->remove_thd(thd);
mi->abort_slave = 0;
mi->slave_running = 0;
mi->atomic_is_stopping = false;
mysql_mutex_lock(&mi->info_thd_lock);
mi->info_thd = nullptr;
mysql_mutex_unlock(&mi->info_thd_lock);
/*
The thd can only be destructed after indirect references
through mi->info_thd are cleared: mi->info_thd= NULL.
For instance, user thread might be issuing show_slave_status
and attempting to read mi->info_thd->get_proc_info().
Therefore thd must only be deleted after info_thd is set
to NULL.
*/
delete thd;
/*
Note: the order of the two following calls (first broadcast, then unlock)
is important. Otherwise a killer_thread can execute between the calls and
delete the mi structure leading to a crash! (see BUG#25306 for details)
*/
mysql_cond_broadcast(&mi->stop_cond); // tell the world we are done
DBUG_EXECUTE_IF("simulate_slave_delay_at_terminate_bug38694", sleep(5););
mysql_mutex_unlock(&mi->run_lock);
}
my_thread_end();
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_thread_state(0);
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
my_thread_exit(0);
return (0); // Avoid compiler warnings
}
/*
Check the temporary directory used by commands like
LOAD DATA INFILE.
*/
static int check_temp_dir(char *tmp_file, const char *channel_name) {
int fd;
MY_DIR *dirp;
char tmp_dir[FN_REFLEN];
size_t tmp_dir_size;
DBUG_TRACE;
/*
Get the directory from the temporary file.
*/
dirname_part(tmp_dir, tmp_file, &tmp_dir_size);
/*
Check if the directory exists.
*/
if (!(dirp = my_dir(tmp_dir, MYF(MY_WME)))) return 1;
my_dirend(dirp);
/*
Check permissions to create a file.
*/
// append the server UUID to the temp file name.
constexpr uint size_of_tmp_file_name = 768;
static_assert(size_of_tmp_file_name >= FN_REFLEN + TEMP_FILE_MAX_LEN, "");
char *unique_tmp_file_name = (char *)my_malloc(
key_memory_rpl_slave_check_temp_dir, size_of_tmp_file_name, MYF(0));
/*
In the case of Multisource replication, the file create
sometimes fail because of there is a race that a second SQL
thread might create the same file and the creation fails.
TO overcome this, we add a channel name to get a unique file name.
*/
/* @TODO: dangerous. Prevent this buffer flow */
snprintf(unique_tmp_file_name, size_of_tmp_file_name, "%s%s%s", tmp_file,
channel_name, server_uuid);
if ((fd = mysql_file_create(key_file_misc, unique_tmp_file_name, CREATE_MODE,
O_WRONLY | O_EXCL | O_NOFOLLOW, MYF(MY_WME))) < 0)
return 1;
/*
Clean up.
*/
mysql_file_close(fd, MYF(0));
mysql_file_delete(key_file_misc, unique_tmp_file_name, MYF(0));
my_free(unique_tmp_file_name);
return 0;
}
/*
Worker thread for the parallel execution of the replication events.
*/
extern "C" {
static void *handle_slave_worker(void *arg) {
THD *thd; /* needs to be first for thread_stack */
bool thd_added = false;
int error = 0;
Slave_worker *w = (Slave_worker *)arg;
Relay_log_info *rli = w->c_rli;
ulong purge_cnt = 0;
ulonglong purge_size = 0;
struct slave_job_item _item, *job_item = &_item;
Global_THD_manager *thd_manager = Global_THD_manager::get_instance();
#ifdef HAVE_PSI_THREAD_INTERFACE
struct PSI_thread *psi;
#endif
my_thread_init();
DBUG_TRACE;
thd = new THD;
if (!thd) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_CANT_INITIALIZE_SLAVE_WORKER,
rli->get_for_channel_str());
goto err;
}
mysql_mutex_lock(&w->info_thd_lock);
w->info_thd = thd;
mysql_mutex_unlock(&w->info_thd_lock);
thd->thread_stack = (char *)&thd;
#ifdef HAVE_PSI_THREAD_INTERFACE
// save the instrumentation for worker thread in w->info_thd
psi = PSI_THREAD_CALL(get_thread)();
thd_set_psi(w->info_thd, psi);
#endif
if (init_slave_thread(thd, SLAVE_THD_WORKER)) {
// todo make SQL thread killed
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_CANT_INITIALIZE_SLAVE_WORKER,
rli->get_for_channel_str());
goto err;
}
thd->rli_slave = w;
thd->init_query_mem_roots();
if (channel_map.is_group_replication_channel_name(rli->get_channel())) {
if (channel_map.is_group_replication_channel_name(rli->get_channel(),
true)) {
thd->rpl_thd_ctx.set_rpl_channel_type(GR_APPLIER_CHANNEL);
} else {
thd->rpl_thd_ctx.set_rpl_channel_type(GR_RECOVERY_CHANNEL);
}
} else {
thd->rpl_thd_ctx.set_rpl_channel_type(RPL_STANDARD_CHANNEL);
}
w->set_filter(rli->rpl_filter);
if ((w->deferred_events_collecting = w->rpl_filter->is_on()))
w->deferred_events = new Deferred_log_events();
DBUG_ASSERT(thd->rli_slave->info_thd == thd);
/* Set applier thread InnoDB priority */
set_thd_tx_priority(thd, rli->get_thd_tx_priority());
thd_manager->add_thd(thd);
thd_added = true;
if (w->update_is_transactional()) {
rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Error checking if the worker repository is transactional.");
goto err;
}
mysql_mutex_lock(&w->jobs_lock);
w->running_status = Slave_worker::RUNNING;
mysql_cond_signal(&w->jobs_cond);
mysql_mutex_unlock(&w->jobs_lock);
DBUG_ASSERT(thd->is_slave_error == 0);
w->stats_exec_time = w->stats_read_time = 0;
set_timespec_nsec(&w->ts_exec[0], 0);
set_timespec_nsec(&w->ts_exec[1], 0);
set_timespec_nsec(&w->stats_begin, 0);
// No need to report anything, all error handling will be performed in the
// slave SQL thread.
if (!rli->check_privilege_checks_user())
rli->initialize_security_context(w->info_thd); // Worker security context
// initialization with
// `PRIVILEGE_CHECKS_USER`
while (!error) {
error = slave_worker_exec_job_group(w, rli);
}
/*
Cleanup after an error requires clear_error() go first.
Otherwise assert(!all) in binlog_rollback()
*/
thd->clear_error();
w->cleanup_context(thd, error);
mysql_mutex_lock(&w->jobs_lock);
while (de_queue(&w->jobs, job_item)) {
purge_cnt++;
purge_size += job_item->data->common_header->data_written;
DBUG_ASSERT(job_item->data);
delete job_item->data;
}
DBUG_ASSERT(w->jobs.len == 0);
mysql_mutex_unlock(&w->jobs_lock);
mysql_mutex_lock(&rli->pending_jobs_lock);
rli->pending_jobs -= purge_cnt;
rli->mts_pending_jobs_size -= purge_size;
DBUG_ASSERT(rli->mts_pending_jobs_size < rli->mts_pending_jobs_size_max);
mysql_mutex_unlock(&rli->pending_jobs_lock);
/*
In MTS case cleanup_after_session() has be called explicitly.
TODO: to make worker thd be deleted before Slave_worker instance.
*/
if (thd->rli_slave) {
w->cleanup_after_session();
thd->rli_slave = nullptr;
}
mysql_mutex_lock(&w->jobs_lock);
struct timespec stats_end;
set_timespec_nsec(&stats_end, 0);
DBUG_PRINT("info",
("Worker %lu statistics: "
"events processed = %lu "
"online time = %llu "
"events exec time = %llu "
"events read time = %llu "
"hungry waits = %lu "
"priv queue overfills = %llu ",
w->id, w->events_done, diff_timespec(&stats_end, &w->stats_begin),
w->stats_exec_time, w->stats_read_time, w->wq_empty_waits,
w->jobs.waited_overfill));
w->running_status = Slave_worker::NOT_RUNNING;
mysql_cond_signal(&w->jobs_cond); // famous last goodbye
mysql_mutex_unlock(&w->jobs_lock);
err:
if (thd) {
/*
The slave code is very bad. Notice that it is missing
several clean up calls here. I've just added what was
necessary to avoid valgrind errors.
/Alfranio
*/
thd->get_protocol_classic()->end_net();
/*
to avoid close_temporary_tables() closing temp tables as those
are Coordinator's burden.
*/
thd->system_thread = NON_SYSTEM_THREAD;
thd->release_resources();
THD_CHECK_SENTRY(thd);
if (thd_added) thd_manager->remove_thd(thd);
delete thd;
}
my_thread_end();
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_thread_state(0);
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
my_thread_exit(0);
return 0;
}
} // extern "C"
/**
Orders jobs by comparing relay log information.
*/
int mts_event_coord_cmp(LOG_POS_COORD *id1, LOG_POS_COORD *id2) {
longlong filecmp = strcmp(id1->file_name, id2->file_name);
longlong poscmp = id1->pos - id2->pos;
return (filecmp < 0
? -1
: (filecmp > 0 ? 1 : (poscmp < 0 ? -1 : (poscmp > 0 ? 1 : 0))));
}
bool mts_recovery_groups(Relay_log_info *rli) {
Log_event *ev = nullptr;
bool is_error = false;
bool flag_group_seen_begin = false;
uint recovery_group_cnt = 0;
bool not_reached_commit = true;
// Value-initialization, to avoid compiler warnings on push_back.
Slave_job_group job_worker = Slave_job_group();
LOG_INFO linfo;
my_off_t offset = 0;
MY_BITMAP *groups = &rli->recovery_groups;
THD *thd = current_thd;
DBUG_TRACE;
DBUG_ASSERT(rli->slave_parallel_workers == 0);
/*
Although mts_recovery_groups() is reentrant it returns
early if the previous invocation raised any bit in
recovery_groups bitmap.
*/
if (rli->is_mts_recovery()) return 0;
/*
Parallel applier recovery is based on master log name and
position, on Group Replication we have several masters what
makes impossible to recover parallel applier from that information.
Since we always have GTID_MODE=ON on Group Replication, we can
ignore the positions completely, seek the current relay log to the
beginning and start from there. Already applied transactions will be
skipped due to GTIDs auto skip feature and applier will resume from
the last applied transaction.
*/
if (channel_map.is_group_replication_channel_name(rli->get_channel(), true)) {
rli->recovery_parallel_workers = 0;
rli->mts_recovery_group_cnt = 0;
rli->set_group_relay_log_pos(BIN_LOG_HEADER_SIZE);
return 0;
}
/*
Save relay log position to compare with worker's position.
*/
LOG_POS_COORD cp = {const_cast<char *>(rli->get_group_master_log_name()),
rli->get_group_master_log_pos()};
/*
Gathers information on valuable workers and stores it in
above_lwm_jobs in asc ordered by the master binlog coordinates.
*/
Prealloced_array<Slave_job_group, 16> above_lwm_jobs(PSI_NOT_INSTRUMENTED);
above_lwm_jobs.reserve(rli->recovery_parallel_workers);
/*
When info tables are used and autocommit= 0 we force a new
transaction start to avoid table access deadlocks when START SLAVE
is executed after STOP SLAVE with MTS enabled.
*/
if (is_autocommit_off_and_infotables(thd))
if (trans_begin(thd)) goto err;
for (uint id = 0; id < rli->recovery_parallel_workers; id++) {
Slave_worker *worker =
Rpl_info_factory::create_worker(opt_rli_repository_id, id, rli, true);
if (!worker) {
if (is_autocommit_off_and_infotables(thd)) trans_rollback(thd);
goto err;
}
LOG_POS_COORD w_last = {
const_cast<char *>(worker->get_group_master_log_name()),
worker->get_group_master_log_pos()};
if (mts_event_coord_cmp(&w_last, &cp) > 0) {
/*
Inserts information into a dynamic array for further processing.
The jobs/workers are ordered by the last checkpoint positions
workers have seen.
*/
job_worker.worker = worker;
job_worker.checkpoint_log_pos = worker->checkpoint_master_log_pos;
job_worker.checkpoint_log_name = worker->checkpoint_master_log_name;
above_lwm_jobs.push_back(job_worker);
} else {
/*
Deletes the worker because its jobs are included in the latest
checkpoint.
*/
delete worker;
}
}
/*
When info tables are used and autocommit= 0 we force transaction
commit to avoid table access deadlocks when START SLAVE is executed
after STOP SLAVE with MTS enabled.
*/
if (is_autocommit_off_and_infotables(thd))
if (trans_commit(thd)) goto err;
/*
In what follows, the group Recovery Bitmap is constructed.
seek(lwm);
while(w= next(above_lwm_w))
do
read G
if G == w->last_comm
w.B << group_cnt++;
RB |= w.B;
break;
else
group_cnt++;
while(!eof);
continue;
*/
DBUG_ASSERT(!rli->recovery_groups_inited);
if (!above_lwm_jobs.empty()) {
bitmap_init(groups, nullptr, MTS_MAX_BITS_IN_GROUP, false);
rli->recovery_groups_inited = true;
bitmap_clear_all(groups);
}
rli->mts_recovery_group_cnt = 0;
for (Slave_job_group *jg = above_lwm_jobs.begin(); jg != above_lwm_jobs.end();
++jg) {
Slave_worker *w = jg->worker;
LOG_POS_COORD w_last = {const_cast<char *>(w->get_group_master_log_name()),
w->get_group_master_log_pos()};
LogErr(INFORMATION_LEVEL,
ER_RPL_MTS_GROUP_RECOVERY_RELAY_LOG_INFO_FOR_WORKER, w->id,
w->get_group_relay_log_name(), w->get_group_relay_log_pos(),
w->get_group_master_log_name(), w->get_group_master_log_pos());
recovery_group_cnt = 0;
not_reached_commit = true;
if (rli->relay_log.find_log_pos(&linfo, rli->get_group_relay_log_name(),
1)) {
LogErr(ERROR_LEVEL, ER_RPL_ERROR_LOOKING_FOR_LOG,
rli->get_group_relay_log_name());
goto err;
}
offset = rli->get_group_relay_log_pos();
Relaylog_file_reader relaylog_file_reader(opt_slave_sql_verify_checksum);
for (int checking = 0; not_reached_commit; checking++) {
if (relaylog_file_reader.open(linfo.log_file_name, offset)) {
LogErr(ERROR_LEVEL, ER_BINLOG_FILE_OPEN_FAILED,
relaylog_file_reader.get_error_str());
goto err;
}
while (not_reached_commit &&
(ev = relaylog_file_reader.read_event_object())) {
DBUG_ASSERT(ev->is_valid());
if (ev->get_type_code() == binary_log::ROTATE_EVENT ||
ev->get_type_code() == binary_log::FORMAT_DESCRIPTION_EVENT ||
ev->get_type_code() == binary_log::PREVIOUS_GTIDS_LOG_EVENT) {
delete ev;
ev = nullptr;
continue;
}
DBUG_PRINT(
"mts",
("Event Recoverying relay log info "
"group_mster_log_name %s, event_master_log_pos %llu type code %u.",
linfo.log_file_name, ev->common_header->log_pos,
ev->get_type_code()));
if (ev->starts_group()) {
flag_group_seen_begin = true;
} else if ((ev->ends_group() || !flag_group_seen_begin) &&
!is_gtid_event(ev)) {
int ret = 0;
LOG_POS_COORD ev_coord = {
const_cast<char *>(rli->get_group_master_log_name()),
ev->common_header->log_pos};
flag_group_seen_begin = false;
recovery_group_cnt++;
LogErr(INFORMATION_LEVEL, ER_RPL_MTS_GROUP_RECOVERY_RELAY_LOG_INFO,
rli->get_group_master_log_name(), ev->common_header->log_pos);
if ((ret = mts_event_coord_cmp(&ev_coord, &w_last)) == 0) {
#ifndef DBUG_OFF
for (uint i = 0; i <= w->worker_checkpoint_seqno; i++) {
if (bitmap_is_set(&w->group_executed, i))
DBUG_PRINT("mts", ("Bit %u is set.", i));
else
DBUG_PRINT("mts", ("Bit %u is not set.", i));
}
#endif
DBUG_PRINT("mts",
("Doing a shift ini(%lu) end(%lu).",
(w->worker_checkpoint_seqno + 1) - recovery_group_cnt,
w->worker_checkpoint_seqno));
for (uint i = (w->worker_checkpoint_seqno + 1) - recovery_group_cnt,
j = 0;
i <= w->worker_checkpoint_seqno; i++, j++) {
if (bitmap_is_set(&w->group_executed, i)) {
DBUG_PRINT("mts", ("Setting bit %u.", j));
bitmap_fast_test_and_set(groups, j);
}
}
not_reached_commit = false;
} else
DBUG_ASSERT(ret < 0);
}
delete ev;
ev = nullptr;
}
relaylog_file_reader.close();
offset = BIN_LOG_HEADER_SIZE;
if (not_reached_commit && rli->relay_log.find_next_log(&linfo, 1)) {
LogErr(ERROR_LEVEL, ER_RPL_CANT_FIND_FOLLOWUP_FILE,
linfo.log_file_name);
goto err;
}
}
rli->mts_recovery_group_cnt =
(rli->mts_recovery_group_cnt < recovery_group_cnt
? recovery_group_cnt
: rli->mts_recovery_group_cnt);
}
DBUG_ASSERT(!rli->recovery_groups_inited ||
rli->mts_recovery_group_cnt <= groups->n_bits);
goto end;
err:
is_error = true;
end:
for (Slave_job_group *jg = above_lwm_jobs.begin(); jg != above_lwm_jobs.end();
++jg) {
delete jg->worker;
}
if (rli->mts_recovery_group_cnt == 0) rli->clear_mts_recovery_groups();
return is_error;
}
bool mts_checkpoint_routine(Relay_log_info *rli, bool force) {
ulong cnt;
bool error = false;
time_t ts = 0;
DBUG_TRACE;
#ifndef DBUG_OFF
if (DBUG_EVALUATE_IF("check_slave_debug_group", 1, 0)) {
if (!rli->gaq->count_done(rli)) return false;
}
DBUG_EXECUTE_IF("mts_checkpoint", {
const char act[] = "now signal mts_checkpoint_start";
DBUG_ASSERT(!debug_sync_set_action(rli->info_thd, STRING_WITH_LEN(act)));
};);
#endif
/*
rli->checkpoint_group can have two possible values due to
two possible status of the last (being scheduled) group.
*/
DBUG_ASSERT(!rli->gaq->full() ||
((rli->rli_checkpoint_seqno == rli->checkpoint_group - 1 &&
(rli->mts_group_status == Relay_log_info::MTS_IN_GROUP ||
rli->mts_group_status == Relay_log_info::MTS_KILLED_GROUP)) ||
rli->rli_checkpoint_seqno == rli->checkpoint_group));
do {
if (!is_mts_db_partitioned(rli)) mysql_mutex_lock(&rli->mts_gaq_LOCK);
cnt = rli->gaq->move_queue_head(&rli->workers);
if (!is_mts_db_partitioned(rli)) mysql_mutex_unlock(&rli->mts_gaq_LOCK);
#ifndef DBUG_OFF
if (DBUG_EVALUATE_IF("check_slave_debug_group", 1, 0) &&
cnt != opt_mts_checkpoint_period)
LogErr(ERROR_LEVEL, ER_RPL_MTS_CHECKPOINT_PERIOD_DIFFERS_FROM_CNT);
#endif
} while (!sql_slave_killed(rli->info_thd, rli) && cnt == 0 && force &&
!DBUG_EVALUATE_IF("check_slave_debug_group", 1, 0) &&
(my_sleep(rli->mts_coordinator_basic_nap), 1));
/*
This checks how many consecutive jobs where processed.
If this value is different than zero the checkpoint
routine can proceed. Otherwise, there is nothing to be
done.
*/
if (cnt == 0) goto end;
/*
The workers have completed cnt jobs from the gaq. This means that we
should increment C->jobs_done by cnt.
*/
if (!is_mts_worker(rli->info_thd) && !is_mts_db_partitioned(rli)) {
DBUG_PRINT("info", ("jobs_done this itr=%ld", cnt));
static_cast<Mts_submode_logical_clock *>(rli->current_mts_submode)
->jobs_done += cnt;
}
/* TODO:
to turn the least occupied selection in terms of jobs pieces
*/
for (Slave_worker **it = rli->workers.begin(); it != rli->workers.begin();
++it) {
Slave_worker *w_i = *it;
rli->least_occupied_workers[w_i->id] = w_i->jobs.len;
};
std::sort(rli->least_occupied_workers.begin(),
rli->least_occupied_workers.end());
mysql_mutex_lock(&rli->data_lock);
/*
"Coordinator::commit_positions"
rli->gaq->lwm has been updated in move_queue_head() and
to contain all but rli->group_master_log_name which
is altered solely by Coordinator at special checkpoints.
*/
rli->set_group_master_log_pos(rli->gaq->lwm.group_master_log_pos);
rli->set_group_relay_log_pos(rli->gaq->lwm.group_relay_log_pos);
DBUG_PRINT(
"mts",
("New checkpoint %llu %llu %s", rli->gaq->lwm.group_master_log_pos,
rli->gaq->lwm.group_relay_log_pos, rli->gaq->lwm.group_relay_log_name));
if (rli->gaq->lwm.group_relay_log_name[0] != 0)
rli->set_group_relay_log_name(rli->gaq->lwm.group_relay_log_name);
/*
todo: uncomment notifies when UNTIL will be supported
rli->notify_group_master_log_name_update();
rli->notify_group_relay_log_name_update();
Todo: optimize with if (wait_flag) broadcast
waiter: set wait_flag; waits....; drops wait_flag;
*/
error = rli->flush_info(true);
mysql_cond_broadcast(&rli->data_cond);
mysql_mutex_unlock(&rli->data_lock);
/*
We need to ensure that this is never called at this point when
cnt is zero. This value means that the checkpoint information
will be completely reset.
*/
/*
Update the rli->last_master_timestamp for reporting correct
Seconds_behind_master.
If GAQ is empty, set it to zero.
Else, update it with the timestamp of the first job of the Slave_job_queue
which was assigned in the Log_event::get_slave_worker() function.
*/
ts = rli->gaq->empty()
? 0
: reinterpret_cast<Slave_job_group *>(rli->gaq->head_queue())->ts;
rli->reset_notified_checkpoint(cnt, ts, true);
/* end-of "Coordinator::"commit_positions" */
end:
error = error || rli->info_thd->killed != THD::NOT_KILLED;
#ifndef DBUG_OFF
if (DBUG_EVALUATE_IF("check_slave_debug_group", 1, 0)) DBUG_SUICIDE();
DBUG_EXECUTE_IF("mts_checkpoint", {
const char act[] = "now signal mts_checkpoint_end";
DBUG_ASSERT(!debug_sync_set_action(rli->info_thd, STRING_WITH_LEN(act)));
};);
#endif
set_timespec_nsec(&rli->last_clock, 0);
return error;
}
/**
Instantiation of a Slave_worker and forking out a single Worker thread.
@param rli Coordinator's Relay_log_info pointer
@param i identifier of the Worker
@return 0 suppress or 1 if fails
*/
static int slave_start_single_worker(Relay_log_info *rli, ulong i) {
int error = 0;
my_thread_handle th;
Slave_worker *w = nullptr;
mysql_mutex_assert_owner(&rli->run_lock);
if (!(w = Rpl_info_factory::create_worker(opt_rli_repository_id, i, rli,
false))) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_WORKER_THREAD_CREATION_FAILED,
rli->get_for_channel_str());
error = 1;
goto err;
}
if (w->init_worker(rli, i)) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_WORKER_THREAD_CREATION_FAILED,
rli->get_for_channel_str());
error = 1;
goto err;
}
// We assume that workers are added in sequential order here.
DBUG_ASSERT(i == rli->workers.size());
if (i >= rli->workers.size()) rli->workers.resize(i + 1);
rli->workers[i] = w;
if (DBUG_EVALUATE_IF("mts_worker_thread_fails", i == 1, 0) ||
(error =
mysql_thread_create(key_thread_slave_worker, &th, &connection_attrib,
handle_slave_worker, (void *)w))) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_WORKER_THREAD_CREATION_FAILED_WITH_ERRNO,
rli->get_for_channel_str(), error);
error = 1;
goto err;
}
mysql_mutex_lock(&w->jobs_lock);
if (w->running_status == Slave_worker::NOT_RUNNING)
mysql_cond_wait(&w->jobs_cond, &w->jobs_lock);
mysql_mutex_unlock(&w->jobs_lock);
// Least occupied inited with zero
{
ulong jobs_len = w->jobs.len;
rli->least_occupied_workers.push_back(jobs_len);
}
err:
if (error && w) {
// Free the current submode object
delete w->current_mts_submode;
w->current_mts_submode = 0;
delete w;
/*
Any failure after array inserted must follow with deletion
of just created item.
*/
if (rli->workers.size() == i + 1) rli->workers.erase(i);
}
return error;
}
/**
Initialization of the central rli members for Coordinator's role,
communication channels such as Assigned Partition Hash (APH),
and starting the Worker pool.
@param rli Pointer to Coordinator's Relay_log_info instance.
@param n Number of configured Workers in the upcoming session.
@param[out] mts_inited If the initialization processed was started.
@return 0 success
non-zero as failure
*/
static int slave_start_workers(Relay_log_info *rli, ulong n, bool *mts_inited) {
uint i;
int error = 0;
/**
gtid_monitoring_info must be cleared when MTS is enabled or
workers_copy_pfs has elements
*/
bool clear_gtid_monitoring_info = false;
mysql_mutex_assert_owner(&rli->run_lock);
if (n == 0 && rli->mts_recovery_group_cnt == 0) {
rli->workers.clear();
rli->clear_processing_trx();
goto end;
}
*mts_inited = true;
/*
The requested through argument number of Workers can be different
from the previous time which ended with an error. Thereby
the effective number of configured Workers is max of the two.
*/
rli->init_workers(max(n, rli->recovery_parallel_workers));
rli->last_assigned_worker = nullptr; // associated with curr_group_assigned
// Least_occupied_workers array to hold items size of Slave_jobs_queue::len
rli->least_occupied_workers.resize(n);
/*
GAQ queue holds seqno:s of scheduled groups. C polls workers in
@c opt_mts_checkpoint_period to update GAQ (see @c next_event())
The length of GAQ is set to be equal to checkpoint_group.
Notice, the size matters for mts_checkpoint_routine's progress loop.
*/
rli->gaq = new Slave_committed_queue(rli->checkpoint_group, n);
if (!rli->gaq->inited) return 1;
// length of WQ is actually constant though can be made configurable
rli->mts_slave_worker_queue_len_max = mts_slave_worker_queue_len_max;
rli->mts_pending_jobs_size = 0;
rli->mts_pending_jobs_size_max = ::opt_mts_pending_jobs_size_max;
rli->mts_wq_underrun_w_id = MTS_WORKER_UNDEF;
rli->mts_wq_excess_cnt = 0;
rli->mts_wq_overrun_cnt = 0;
rli->mts_wq_oversize = false;
rli->mts_coordinator_basic_nap = mts_coordinator_basic_nap;
rli->mts_worker_underrun_level = mts_worker_underrun_level;
rli->curr_group_seen_begin = rli->curr_group_seen_gtid = false;
rli->curr_group_isolated = false;
rli->rli_checkpoint_seqno = 0;
rli->mts_last_online_stat = my_time(0);
rli->mts_group_status = Relay_log_info::MTS_NOT_IN_GROUP;
clear_gtid_monitoring_info = true;
if (init_hash_workers(rli)) // MTS: mapping_db_to_worker
{
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_FAILED_TO_INIT_PARTITIONS_HASH);
error = 1;
goto err;
}
for (i = 0; i < n; i++) {
if ((error = slave_start_single_worker(rli, i))) goto err;
rli->slave_parallel_workers++;
}
end:
/*
Free the buffer that was being used to report worker's status through
the table performance_schema.table_replication_applier_status_by_worker
between stop slave and next start slave.
*/
for (int i = static_cast<int>(rli->workers_copy_pfs.size()) - 1; i >= 0;
i--) {
delete rli->workers_copy_pfs[i];
if (!clear_gtid_monitoring_info) clear_gtid_monitoring_info = true;
}
rli->workers_copy_pfs.clear();
// Effective end of the recovery right now when there is no gaps
if (!error && rli->mts_recovery_group_cnt == 0) {
if ((error = rli->mts_finalize_recovery()))
(void)Rpl_info_factory::reset_workers(rli);
if (!error) error = rli->flush_info(true);
}
err:
if (clear_gtid_monitoring_info) rli->clear_gtid_monitoring_info();
return error;
}
/*
Ending Worker threads.
Not in case Coordinator is killed itself, it first waits for
Workers have finished their assignements, and then updates checkpoint.
Workers are notified with setting KILLED status
and waited for their acknowledgment as specified by
worker's running_status.
Coordinator finalizes with its MTS running status to reset few objects.
*/
static void slave_stop_workers(Relay_log_info *rli, bool *mts_inited) {
THD *thd = rli->info_thd;
if (!*mts_inited)
return;
else if (rli->slave_parallel_workers == 0)
goto end;
/*
If request for stop slave is received notify worker
to stop.
*/
// Initialize worker exit count and max_updated_index to 0 during each stop.
rli->exit_counter = 0;
rli->max_updated_index = (rli->until_condition != Relay_log_info::UNTIL_NONE)
? rli->mts_groups_assigned
: 0;
if (!rli->workers.empty()) {
for (int i = static_cast<int>(rli->workers.size()) - 1; i >= 0; i--) {
Slave_worker *w = rli->workers[i];
struct slave_job_item item = {nullptr, 0, 0};
struct slave_job_item *job_item = &item;
mysql_mutex_lock(&w->jobs_lock);
if (w->running_status != Slave_worker::RUNNING) {
mysql_mutex_unlock(&w->jobs_lock);
continue;
}
w->running_status = Slave_worker::STOP;
(void)set_max_updated_index_on_stop(w, job_item);
mysql_cond_signal(&w->jobs_cond);
mysql_mutex_unlock(&w->jobs_lock);
DBUG_PRINT("info", ("Notifying worker %lu%s to exit, thd %p", w->id,
w->get_for_channel_str(), w->info_thd));
}
}
thd_proc_info(thd, "Waiting for workers to exit");
for (Slave_worker **it = rli->workers.begin(); it != rli->workers.end();
++it) {
Slave_worker *w = *it;
mysql_mutex_lock(&w->jobs_lock);
while (w->running_status != Slave_worker::NOT_RUNNING) {
PSI_stage_info old_stage;
DBUG_ASSERT(w->running_status == Slave_worker::ERROR_LEAVING ||
w->running_status == Slave_worker::STOP ||
w->running_status == Slave_worker::STOP_ACCEPTED);
thd->ENTER_COND(&w->jobs_cond, &w->jobs_lock,
&stage_slave_waiting_workers_to_exit, &old_stage);
mysql_cond_wait(&w->jobs_cond, &w->jobs_lock);
mysql_mutex_unlock(&w->jobs_lock);
thd->EXIT_COND(&old_stage);
mysql_mutex_lock(&w->jobs_lock);
}
mysql_mutex_unlock(&w->jobs_lock);
}
for (Slave_worker **it = rli->workers.begin(); it != rli->workers.end();
++it) {
Slave_worker *w = *it;
/*
Make copies for reporting through the performance schema tables.
This is preserved until the next START SLAVE.
*/
Slave_worker *worker_copy = new Slave_worker(
nullptr,
#ifdef HAVE_PSI_INTERFACE
&key_relay_log_info_run_lock, &key_relay_log_info_data_lock,
&key_relay_log_info_sleep_lock, &key_relay_log_info_thd_lock,
&key_relay_log_info_data_cond, &key_relay_log_info_start_cond,
&key_relay_log_info_stop_cond, &key_relay_log_info_sleep_cond,
#endif
w->id, rli->get_channel());
worker_copy->copy_values_for_PFS(w->id, w->running_status, w->info_thd,
w->last_error(),
w->get_gtid_monitoring_info());
rli->workers_copy_pfs.push_back(worker_copy);
}
/// @todo: consider to propagate an error out of the function
if (thd->killed == THD::NOT_KILLED) (void)mts_checkpoint_routine(rli, false);
while (!rli->workers.empty()) {
Slave_worker *w = rli->workers.back();
// Free the current submode object
delete w->current_mts_submode;
w->current_mts_submode = 0;
rli->workers.pop_back();
delete w;
}
struct timespec stats_end;
set_timespec_nsec(&stats_end, 0);
DBUG_PRINT(
"info",
("Total MTS session statistics: "
"events processed = %llu; "
"online time = %llu "
"worker queues filled over overrun level = %lu "
"waited due a Worker queue full = %lu "
"waited due the total size = %lu "
"total wait at clock conflicts = %llu "
"found (count) workers occupied = %lu "
"waited when workers occupied = %llu",
rli->mts_events_assigned, diff_timespec(&stats_end, &rli->stats_begin),
rli->mts_wq_overrun_cnt, rli->mts_wq_overfill_cnt,
rli->wq_size_waits_cnt, rli->mts_total_wait_overlap.load(),
rli->mts_wq_no_underrun_cnt, rli->mts_total_wait_worker_avail));
DBUG_ASSERT(rli->pending_jobs == 0);
DBUG_ASSERT(rli->mts_pending_jobs_size == 0);
end:
rli->mts_group_status = Relay_log_info::MTS_NOT_IN_GROUP;
destroy_hash_workers(rli);
delete rli->gaq;
rli->least_occupied_workers.clear();
// Destroy buffered events of the current group prior to exit.
for (uint i = 0; i < rli->curr_group_da.size(); i++)
delete rli->curr_group_da[i].data;
rli->curr_group_da.clear(); // GCDA
rli->curr_group_assigned_parts.clear(); // GCAP
rli->deinit_workers();
rli->workers_array_initialized = false;
rli->slave_parallel_workers = 0;
*mts_inited = false;
}
/**
Processes the outcome of applying an event, logs it properly if it's an error
and return the proper error code to trigger.
@return the error code to bubble up in the execution stack.
*/
static int report_apply_event_error(THD *thd, Relay_log_info *rli) {
DBUG_TRACE;
longlong slave_errno = 0;
/*
retrieve as much info as possible from the thd and, error
codes and warnings and print this to the error log as to
allow the user to locate the error
*/
uint32 const last_errno = rli->last_error().number;
if (thd->is_error()) {
char const *const errmsg = thd->get_stmt_da()->message_text();
DBUG_PRINT("info", ("thd->get_stmt_da()->get_mysql_errno()=%d; "
"rli->last_error.number=%d",
thd->get_stmt_da()->mysql_errno(), last_errno));
if (last_errno == 0) {
/*
This function is reporting an error which was not reported
while executing exec_relay_log_event().
*/
rli->report(ERROR_LEVEL, thd->get_stmt_da()->mysql_errno(), "%s", errmsg);
} else if (last_errno != thd->get_stmt_da()->mysql_errno()) {
/*
* An error was reported while executing exec_relay_log_event()
* however the error code differs from what is in the thread.
* This function prints out more information to help finding
* what caused the problem.
*/
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_ADDITIONAL_ERROR_INFO_FROM_DA, errmsg,
thd->get_stmt_da()->mysql_errno());
}
}
/* Print any warnings issued */
Diagnostics_area::Sql_condition_iterator it =
thd->get_stmt_da()->sql_conditions();
const Sql_condition *err;
/*
Added controlled slave thread cancel for replication
of user-defined variables.
*/
bool udf_error = false;
while ((err = it++)) {
if (err->mysql_errno() == ER_CANT_OPEN_LIBRARY) udf_error = true;
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_ERROR_INFO_FROM_DA, err->message_text(),
err->mysql_errno());
}
if (udf_error)
slave_errno = ER_RPL_SLAVE_ERROR_LOADING_USER_DEFINED_LIBRARY;
else
slave_errno = ER_RPL_SLAVE_ERROR_RUNNING_QUERY;
return slave_errno;
}
/**
Slave SQL thread entry point.
@param arg Pointer to Relay_log_info object that holds information
for the SQL thread.
@return Always 0.
*/
extern "C" void *handle_slave_sql(void *arg) {
THD *thd; /* needs to be first for thread_stack */
bool thd_added = false;
char llbuff[22], llbuff1[22];
char saved_log_name[FN_REFLEN];
char saved_master_log_name[FN_REFLEN];
my_off_t saved_log_pos = 0;
my_off_t saved_master_log_pos = 0;
my_off_t saved_skip = 0;
Relay_log_info *rli = ((Master_info *)arg)->rli;
const char *errmsg;
longlong slave_errno = 0;
bool mts_inited = false;
Global_THD_manager *thd_manager = Global_THD_manager::get_instance();
Commit_order_manager *commit_order_mngr = nullptr;
Rpl_applier_reader applier_reader(rli);
Relay_log_info::enum_priv_checks_status priv_check_status =
Relay_log_info::enum_priv_checks_status::SUCCESS;
// needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff
my_thread_init();
{
DBUG_TRACE;
DBUG_ASSERT(rli->inited);
mysql_mutex_lock(&rli->run_lock);
DBUG_ASSERT(!rli->slave_running);
errmsg = nullptr;
#ifndef DBUG_OFF
rli->events_until_exit = abort_slave_event_count;
#endif
thd = new THD; // note that contructor of THD uses DBUG_ !
thd->thread_stack = (char *)&thd; // remember where our stack is
mysql_mutex_lock(&rli->info_thd_lock);
rli->info_thd = thd;
#ifdef HAVE_PSI_THREAD_INTERFACE
// save the instrumentation for SQL thread in rli->info_thd
struct PSI_thread *psi = PSI_THREAD_CALL(get_thread)();
thd_set_psi(rli->info_thd, psi);
#endif
if (rli->channel_mts_submode != MTS_PARALLEL_TYPE_DB_NAME)
rli->current_mts_submode = new Mts_submode_logical_clock();
else
rli->current_mts_submode = new Mts_submode_database();
if (opt_slave_preserve_commit_order &&
rli->opt_slave_parallel_workers > 0 && opt_bin_log &&
opt_log_slave_updates)
commit_order_mngr =
new Commit_order_manager(rli->opt_slave_parallel_workers);
rli->set_commit_order_manager(commit_order_mngr);
if (channel_map.is_group_replication_channel_name(rli->get_channel())) {
if (channel_map.is_group_replication_channel_name(rli->get_channel(),
true)) {
thd->rpl_thd_ctx.set_rpl_channel_type(GR_APPLIER_CHANNEL);
} else {
thd->rpl_thd_ctx.set_rpl_channel_type(GR_RECOVERY_CHANNEL);
}
} else {
thd->rpl_thd_ctx.set_rpl_channel_type(RPL_STANDARD_CHANNEL);
}
mysql_mutex_unlock(&rli->info_thd_lock);
/* Inform waiting threads that slave has started */
rli->slave_run_id++;
rli->slave_running = 1;
rli->reported_unsafe_warning = false;
rli->sql_thread_kill_accepted = false;
if (init_slave_thread(thd, SLAVE_THD_SQL)) {
/*
TODO: this is currently broken - slave start and change master
will be stuck if we fail here
*/
mysql_cond_broadcast(&rli->start_cond);
mysql_mutex_unlock(&rli->run_lock);
rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed during slave thread initialization");
goto err;
}
thd->init_query_mem_roots();
if ((rli->deferred_events_collecting = rli->rpl_filter->is_on()))
rli->deferred_events = new Deferred_log_events();
thd->rli_slave = rli;
DBUG_ASSERT(thd->rli_slave->info_thd == thd);
thd->temporary_tables = rli->save_temporary_tables; // restore temp tables
set_thd_in_use_temporary_tables(
rli); // (re)set sql_thd in use for saved temp tables
/* Set applier thread InnoDB priority */
set_thd_tx_priority(thd, rli->get_thd_tx_priority());
thd_manager->add_thd(thd);
thd_added = true;
rli->stats_exec_time = rli->stats_read_time = 0;
set_timespec_nsec(&rli->ts_exec[0], 0);
set_timespec_nsec(&rli->ts_exec[1], 0);
set_timespec_nsec(&rli->stats_begin, 0);
if (RUN_HOOK(binlog_relay_io, applier_start, (thd, rli->mi))) {
mysql_cond_broadcast(&rli->start_cond);
mysql_mutex_unlock(&rli->run_lock);
rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed to run 'applier_start' hook");
goto err;
}
/* MTS: starting the worker pool */
if (slave_start_workers(rli, rli->opt_slave_parallel_workers,
&mts_inited) != 0) {
mysql_cond_broadcast(&rli->start_cond);
mysql_mutex_unlock(&rli->run_lock);
rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Failed during slave workers initialization");
goto err;
}
/*
We are going to set slave_running to 1. Assuming slave I/O thread is
alive and connected, this is going to make Seconds_Behind_Master be 0
i.e. "caught up". Even if we're just at start of thread. Well it's ok, at
the moment we start we can think we are caught up, and the next second we
start receiving data so we realize we are not caught up and
Seconds_Behind_Master grows. No big deal.
*/
rli->abort_slave = 0;
/*
Reset errors for a clean start (otherwise, if the master is idle, the SQL
thread may execute no Query_log_event, so the error will remain even
though there's no problem anymore). Do not reset the master timestamp
(imagine the slave has caught everything, the STOP SLAVE and START SLAVE:
as we are not sure that we are going to receive a query, we want to
remember the last master timestamp (to say how many seconds behind we are
now.
But the master timestamp is reset by RESET SLAVE & CHANGE MASTER.
*/
rli->clear_error();
if (rli->workers_array_initialized) {
for (size_t i = 0; i < rli->get_worker_count(); i++) {
rli->get_worker(i)->clear_error();
}
}
if (rli->update_is_transactional()) {
mysql_cond_broadcast(&rli->start_cond);
mysql_mutex_unlock(&rli->run_lock);
rli->report(
ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Error checking if the relay log repository is transactional.");
goto err;
}
if (!rli->is_transactional())
rli->report(
WARNING_LEVEL, 0,
"If a crash happens this configuration does not guarantee that "
"the relay "
"log info will be consistent");
mysql_cond_broadcast(&rli->start_cond);
mysql_mutex_unlock(&rli->run_lock);
DEBUG_SYNC(thd, "after_start_slave");
// tell the I/O thread to take relay_log_space_limit into account from now
// on
mysql_mutex_lock(&rli->log_space_lock);
rli->ignore_log_space_limit = 0;
mysql_mutex_unlock(&rli->log_space_lock);
rli->trans_retries = 0; // start from "no error"
DBUG_PRINT("info", ("rli->trans_retries: %lu", rli->trans_retries));
if (applier_reader.open(&errmsg)) {
rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, "%s", errmsg);
goto err;
}
THD_CHECK_SENTRY(thd);
DBUG_ASSERT(rli->info_thd == thd);
DBUG_PRINT("master_info", ("log_file_name: %s position: %s",
rli->get_group_master_log_name(),
llstr(rli->get_group_master_log_pos(), llbuff)));
if (check_temp_dir(rli->slave_patternload_file, rli->get_channel())) {
rli->report(ERROR_LEVEL, thd->get_stmt_da()->mysql_errno(),
"Unable to use slave's temporary directory %s - %s",
slave_load_tmpdir, thd->get_stmt_da()->message_text());
goto err;
}
priv_check_status = rli->check_privilege_checks_user();
if (!!priv_check_status) {
rli->report_privilege_check_error(ERROR_LEVEL, priv_check_status,
false /* to client*/);
rli->set_privilege_checks_user_corrupted(true);
goto err;
}
priv_check_status =
rli->initialize_applier_security_context(); // Applier security context
// initialization with
// `PRIVILEGE_CHECKS_USER`
if (!!priv_check_status) {
rli->report_privilege_check_error(ERROR_LEVEL, priv_check_status,
false /* to client*/);
goto err;
}
if (rli->is_privilege_checks_user_null())
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_SQL_THREAD_STARTING,
rli->get_for_channel_str(), rli->get_rpl_log_name(),
llstr(rli->get_group_master_log_pos(), llbuff),
rli->get_group_relay_log_name(),
llstr(rli->get_group_relay_log_pos(), llbuff1));
else
LogErr(INFORMATION_LEVEL,
ER_RPL_SLAVE_SQL_THREAD_STARTING_WITH_PRIVILEGE_CHECKS,
rli->get_for_channel_str(), rli->get_rpl_log_name(),
llstr(rli->get_group_master_log_pos(), llbuff),
rli->get_group_relay_log_name(),
llstr(rli->get_group_relay_log_pos(), llbuff1),
rli->get_privilege_checks_username().c_str(),
rli->get_privilege_checks_hostname().c_str(),
opt_always_activate_granted_roles == 0 ? "DEFAULT" : "ALL");
/* execute init_slave variable */
if (opt_init_slave.length) {
execute_init_command(thd, &opt_init_slave, &LOCK_sys_init_slave);
if (thd->is_slave_error) {
rli->report(ERROR_LEVEL, ER_SERVER_SLAVE_INIT_QUERY_FAILED,
ER_THD(current_thd, ER_SERVER_SLAVE_INIT_QUERY_FAILED),
thd->get_stmt_da()->mysql_errno(),
thd->get_stmt_da()->message_text());
goto err;
}
}
/*
First check until condition - probably there is nothing to execute. We
do not want to wait for next event in this case.
*/
mysql_mutex_lock(&rli->data_lock);
if (rli->slave_skip_counter) {
strmake(saved_log_name, rli->get_group_relay_log_name(), FN_REFLEN - 1);
strmake(saved_master_log_name, rli->get_group_master_log_name(),
FN_REFLEN - 1);
saved_log_pos = rli->get_group_relay_log_pos();
saved_master_log_pos = rli->get_group_master_log_pos();
saved_skip = rli->slave_skip_counter;
}
if (rli->is_until_satisfied_at_start_slave()) {
mysql_mutex_unlock(&rli->data_lock);
goto err;
}
mysql_mutex_unlock(&rli->data_lock);
/* Read queries from the IO/THREAD until this thread is killed */
while (!sql_slave_killed(thd, rli)) {
THD_STAGE_INFO(thd, stage_reading_event_from_the_relay_log);
DBUG_ASSERT(rli->info_thd == thd);
THD_CHECK_SENTRY(thd);
if (saved_skip && rli->slave_skip_counter == 0) {
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_SKIP_COUNTER_EXECUTED,
(ulong)saved_skip, saved_log_name, (ulong)saved_log_pos,
saved_master_log_name, (ulong)saved_master_log_pos,
rli->get_group_relay_log_name(),
(ulong)rli->get_group_relay_log_pos(),
rli->get_group_master_log_name(),
(ulong)rli->get_group_master_log_pos());
saved_skip = 0;
}
if (exec_relay_log_event(thd, rli, &applier_reader)) {
DBUG_PRINT("info", ("exec_relay_log_event() failed"));
// do not scare the user if SQL thread was simply killed or stopped
if (!sql_slave_killed(thd, rli)) {
slave_errno = report_apply_event_error(thd, rli);
}
goto err;
}
}
err:
/* At this point the SQL thread will not try to work anymore. */
rli->atomic_is_stopping = true;
(void)RUN_HOOK(
binlog_relay_io, applier_stop,
(thd, rli->mi, rli->is_error() || !rli->sql_thread_kill_accepted));
slave_stop_workers(rli, &mts_inited); // stopping worker pool
/* Thread stopped. Print the current replication position to the log */
if (slave_errno)
LogErr(ERROR_LEVEL, slave_errno, rli->get_rpl_log_name(),
llstr(rli->get_group_master_log_pos(), llbuff));
else
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_SQL_THREAD_EXITING,
rli->get_for_channel_str(), rli->get_rpl_log_name(),
llstr(rli->get_group_master_log_pos(), llbuff));
delete rli->current_mts_submode;
rli->current_mts_submode = 0;
rli->clear_mts_recovery_groups();
/*
Some events set some playgrounds, which won't be cleared because thread
stops. Stopping of this thread may not be known to these events ("stop"
request is detected only by the present function, not by events), so we
must "proactively" clear playgrounds:
*/
thd->clear_error();
rli->cleanup_context(thd, 1);
/*
Some extra safety, which should not been needed (normally, event deletion
should already have done these assignments (each event which sets these
variables is supposed to set them to 0 before terminating)).
*/
thd->set_catalog(NULL_CSTR);
thd->reset_query();
thd->reset_db(NULL_CSTR);
/*
Pause the SQL thread and wait for 'continue_to_stop_sql_thread'
signal to continue to shutdown the SQL thread.
*/
DBUG_EXECUTE_IF("pause_after_sql_thread_stop_hook", {
const char act[] =
"now SIGNAL reached_stopping_sql_thread "
"WAIT_FOR continue_to_stop_sql_thread";
DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
};);
THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit);
mysql_mutex_lock(&rli->run_lock);
/* We need data_lock, at least to wake up any waiting master_pos_wait() */
mysql_mutex_lock(&rli->data_lock);
applier_reader.close();
DBUG_ASSERT(rli->slave_running == 1); // tracking buffer overrun
/* When master_pos_wait() wakes up it will check this and terminate */
rli->slave_running = 0;
rli->atomic_is_stopping = false;
/* Forget the relay log's format */
if (rli->set_rli_description_event(nullptr)) {
#ifndef DBUG_OFF
bool set_rli_description_event_failed = false;
#endif
DBUG_ASSERT(set_rli_description_event_failed);
}
/* Wake up master_pos_wait() */
DBUG_PRINT("info",
("Signaling possibly waiting master_pos_wait() functions"));
mysql_cond_broadcast(&rli->data_cond);
mysql_mutex_unlock(&rli->data_lock);
rli->ignore_log_space_limit = 0; /* don't need any lock */
/* we die so won't remember charset - re-update them on next thread start */
rli->cached_charset_invalidate();
rli->save_temporary_tables = thd->temporary_tables;
/*
TODO: see if we can do this conditionally in next_event() instead
to avoid unneeded position re-init
*/
thd->temporary_tables =
0; // remove tempation from destructor to close them
// destructor will not free it, because we are weird
thd->get_protocol_classic()->end_net();
DBUG_ASSERT(rli->info_thd == thd);
THD_CHECK_SENTRY(thd);
mysql_mutex_lock(&rli->info_thd_lock);
rli->info_thd = nullptr;
if (commit_order_mngr) {
delete commit_order_mngr;
rli->set_commit_order_manager(nullptr);
}
mysql_mutex_unlock(&rli->info_thd_lock);
set_thd_in_use_temporary_tables(
rli); // (re)set info_thd in use for saved temp tables
thd->release_resources();
THD_CHECK_SENTRY(thd);
if (thd_added) thd_manager->remove_thd(thd);
/*
The thd can only be destructed after indirect references
through mi->rli->info_thd are cleared: mi->rli->info_thd= NULL.
For instance, user thread might be issuing show_slave_status
and attempting to read mi->rli->info_thd->get_proc_info().
Therefore thd must only be deleted after info_thd is set
to NULL.
*/
delete thd;
/*
Note: the order of the broadcast and unlock calls below (first broadcast,
then unlock) is important. Otherwise a killer_thread can execute between
the calls and delete the mi structure leading to a crash! (see BUG#25306
for details)
*/
mysql_cond_broadcast(&rli->stop_cond);
DBUG_EXECUTE_IF("simulate_slave_delay_at_terminate_bug38694", sleep(5););
mysql_mutex_unlock(&rli->run_lock); // tell the world we are done
}
my_thread_end();
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_thread_state(0);
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
my_thread_exit(0);
return 0; // Avoid compiler warnings
}
/**
Used by the slave IO thread when it receives a rotate event from the
master.
Updates the master info with the place in the next binary log where
we should start reading. Rotate the relay log to avoid mixed-format
relay logs.
@param mi master_info for the slave
@param rev The rotate log event read from the master
@note The caller must hold mi->data_lock before invoking this function.
@retval 0 ok
@retval 1 error
*/
static int process_io_rotate(Master_info *mi, Rotate_log_event *rev) {
DBUG_TRACE;
mysql_mutex_assert_owner(mi->rli->relay_log.get_log_lock());
if (unlikely(!rev->is_valid())) return 1;
#ifndef DBUG_OFF
/*
If we do not do this, we will be getting the first
rotate event forever, so we need to not disconnect after one.
*/
if (disconnect_slave_event_count) mi->events_until_exit++;
#endif
/*
Master will send a FD event immediately after the Roate event, so don't log
the current FD event.
*/
int ret = rotate_relay_log(mi, false, false, true);
mysql_mutex_lock(&mi->data_lock);
/* Safe copy as 'rev' has been "sanitized" in Rotate_log_event's ctor */
memcpy(const_cast<char *>(mi->get_master_log_name()), rev->new_log_ident,
rev->ident_len + 1);
mi->set_master_log_pos(rev->pos);
DBUG_PRINT("info",
("new (master_log_name, master_log_pos): ('%s', %lu)",
mi->get_master_log_name(), (ulong)mi->get_master_log_pos()));
mysql_mutex_unlock(&mi->data_lock);
return ret;
}
/**
Store an event received from the master connection into the relay
log.
@param mi The Master_info object representing this connection.
@param buf Pointer to the event data.
@param event_len Length of event data.
@param do_flush_mi True to flush master info after successfully queuing the
event.
@retval QUEUE_EVENT_OK on success.
@retval QUEUE_EVENT_ERROR_QUEUING if there was an error while queuing.
@retval QUEUE_EVENT_ERROR_FLUSHING_INFO if there was an error while
flushing master info.
@todo Make this a member of Master_info.
*/
QUEUE_EVENT_RESULT queue_event(Master_info *mi, const char *buf,
ulong event_len, bool do_flush_mi) {
QUEUE_EVENT_RESULT res = QUEUE_EVENT_OK;
ulong inc_pos = 0;
Relay_log_info *rli = mi->rli;
mysql_mutex_t *log_lock = rli->relay_log.get_log_lock();
ulong s_id;
int lock_count = 0;
DBUG_EXECUTE_IF("wait_in_the_middle_of_trx", {
/*
See `gr_flush_relay_log_no_split_trx.test`
1) Add a debug sync point that holds and makes the applier thread to
wait, in the middle of a transaction -
`signal.rpl_requested_for_a_flush`.
*/
DBUG_SET("-d,wait_in_the_middle_of_trx");
const char dbug_wait[] = "now WAIT_FOR signal.rpl_requested_for_a_flush";
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(dbug_wait)));
});
/*
FD_q must have been prepared for the first R_a event
inside get_master_version_and_clock()
Show-up of FD:s affects checksum_alg at once because
that changes FD_queue.
*/
enum_binlog_checksum_alg checksum_alg =
mi->checksum_alg_before_fd != binary_log::BINLOG_CHECKSUM_ALG_UNDEF
? mi->checksum_alg_before_fd
: mi->rli->relay_log.relay_log_checksum_alg;
const char *save_buf =
nullptr; // needed for checksumming the fake Rotate event
char rot_buf[LOG_EVENT_HEADER_LEN + Binary_log_event::ROTATE_HEADER_LEN +
FN_REFLEN];
Gtid gtid = {0, 0};
ulonglong immediate_commit_timestamp = 0;
ulonglong original_commit_timestamp = 0;
Log_event_type event_type = (Log_event_type)buf[EVENT_TYPE_OFFSET];
DBUG_ASSERT(checksum_alg == binary_log::BINLOG_CHECKSUM_ALG_OFF ||
checksum_alg == binary_log::BINLOG_CHECKSUM_ALG_UNDEF ||
checksum_alg == binary_log::BINLOG_CHECKSUM_ALG_CRC32);
DBUG_TRACE;
/*
Pause the IO thread execution and wait for 'continue_queuing_event'
signal to continue IO thread execution.
*/
DBUG_EXECUTE_IF("pause_on_queuing_event", {
const char act[] =
"now SIGNAL reached_queuing_event "
"WAIT_FOR continue_queuing_event";
DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
/*
FD_queue checksum alg description does not apply in a case of
FD itself. The one carries both parts of the checksum data.
*/
if (event_type == binary_log::FORMAT_DESCRIPTION_EVENT) {
checksum_alg = Log_event_footer::get_checksum_alg(buf, event_len);
}
// does not hold always because of old binlog can work with NM
// DBUG_ASSERT(checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
// should hold unless manipulations with RL. Tests that do that
// will have to refine the clause.
DBUG_ASSERT(mi->rli->relay_log.relay_log_checksum_alg !=
binary_log::BINLOG_CHECKSUM_ALG_UNDEF);
// Emulate the network corruption
DBUG_EXECUTE_IF(
"corrupt_queue_event",
if (event_type != binary_log::FORMAT_DESCRIPTION_EVENT) {
char *debug_event_buf_c = const_cast<char *>(buf);
int debug_cor_pos = rand() % (event_len - BINLOG_CHECKSUM_LEN);
debug_event_buf_c[debug_cor_pos] = ~debug_event_buf_c[debug_cor_pos];
DBUG_PRINT("info",
("Corrupt the event at queue_event: byte on position %d",
debug_cor_pos));
DBUG_SET("");
});
binary_log_debug::debug_checksum_test =
DBUG_EVALUATE_IF("simulate_checksum_test_failure", true, false);
binary_log_debug::debug_checksum_test =
DBUG_EVALUATE_IF("gr_simulate_checksum_test_failure", true,
binary_log_debug::debug_checksum_test);
if (Log_event_footer::event_checksum_test(
const_cast<uchar *>(pointer_cast<const uchar *>(buf)), event_len,
checksum_alg)) {
mi->report(ERROR_LEVEL, ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE, "%s",
ER_THD(current_thd, ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE));
goto err;
}
/*
From now, and up to finishing queuing the event, no other thread is allowed
to write to the relay log, or to rotate it.
*/
mysql_mutex_lock(log_lock);
DBUG_ASSERT(lock_count == 0);
lock_count = 1;
if (mi->get_mi_description_event() == nullptr) {
LogErr(ERROR_LEVEL, ER_RPL_SLAVE_QUEUE_EVENT_FAILED_INVALID_CONFIGURATION,
mi->get_channel());
goto err;
}
/*
Simulate an unknown ignorable log event by rewriting a Xid
log event before queuing it into relay log.
*/
DBUG_EXECUTE_IF(
"simulate_unknown_ignorable_log_event_with_xid",
if (event_type == binary_log::XID_EVENT) {
uchar *ev_buf = const_cast<uchar *>(pointer_cast<const uchar *>(buf));
/* Overwrite the log event type with an unknown type. */
ev_buf[EVENT_TYPE_OFFSET] = binary_log::ENUM_END_EVENT + 1;
/* Set LOG_EVENT_IGNORABLE_F for the log event. */
int2store(ev_buf + FLAGS_OFFSET,
uint2korr(ev_buf + FLAGS_OFFSET) | LOG_EVENT_IGNORABLE_F);
/* Recalc event's CRC */
ha_checksum ev_crc = checksum_crc32(0L, nullptr, 0);
ev_crc = checksum_crc32(ev_crc, (const uchar *)ev_buf,
event_len - BINLOG_CHECKSUM_LEN);
int4store(&ev_buf[event_len - BINLOG_CHECKSUM_LEN], ev_crc);
/*
We will skip writing this event to the relay log in order to let
the startup procedure to not finding it and assuming this transaction
is incomplete.
But we have to keep the unknown ignorable error to let the
"stop_io_after_reading_unknown_event" debug point to work after
"queuing" this event.
*/
mysql_mutex_lock(&mi->data_lock);
mi->set_master_log_pos(mi->get_master_log_pos() + event_len);
lock_count = 2;
goto end;
});
/*
This transaction parser is used to ensure that the GTID of the transaction
(if it has one) will only be added to the Retrieved_Gtid_Set after the
last event of the transaction be queued.
It will also be used to avoid rotating the relay log in the middle of
a transaction.
*/
if (mi->transaction_parser.feed_event(buf, event_len,
mi->get_mi_description_event(), true)) {
/*
The transaction parser detected a problem while changing state and threw
a warning message. We are taking care of avoiding transaction boundary
issues, but it can happen.
Transaction boundary errors might happen mostly because of bad master
positioning in 'CHANGE MASTER TO' (or bad manipulation of master.info)
when GTID auto positioning is off. Errors can also happen when using
cross-version replication, replicating from a master that supports more
event types than this slave.
The IO thread will keep working and queuing events regardless of the
transaction parser error, but we will throw another warning message to
log the relay log file and position of the parser error to help
forensics.
*/
LogErr(WARNING_LEVEL,
ER_RPL_SLAVE_IO_THREAD_DETECTED_UNEXPECTED_EVENT_SEQUENCE,
mi->get_master_log_name(), mi->get_master_log_pos());
}
switch (event_type) {
case binary_log::STOP_EVENT:
/*
We needn't write this event to the relay log. Indeed, it just indicates
a master server shutdown. The only thing this does is cleaning. But
cleaning is already done on a per-master-thread basis (as the master
server is shutting down cleanly, it has written all DROP TEMPORARY TABLE
prepared statements' deletion are TODO only when we binlog prep stmts).
We don't even increment mi->get_master_log_pos(), because we may be just
after a Rotate event. Btw, in a few milliseconds we are going to have a
Start event from the next binlog (unless the master is presently running
without --log-bin).
*/
do_flush_mi = false;
goto end;
case binary_log::ROTATE_EVENT: {
Format_description_log_event *fde = mi->get_mi_description_event();
enum_binlog_checksum_alg fde_checksum_alg = fde->footer()->checksum_alg;
if (fde_checksum_alg != checksum_alg)
fde->footer()->checksum_alg = checksum_alg;
Rotate_log_event rev(buf, fde);
fde->footer()->checksum_alg = fde_checksum_alg;
if (unlikely(process_io_rotate(mi, &rev))) {
// This error will be reported later at handle_slave_io().
goto err;
}
/*
Checksum special cases for the fake Rotate (R_f) event caused by the
protocol of events generation and serialization in RL where Rotate of
master is queued right next to FD of slave. Since it's only FD that
carries the alg desc of FD_s has to apply to R_m. Two special rules
apply only to the first R_f which comes in before any FD_m. The 2nd R_f
should be compatible with the FD_s that must have taken over the last
seen FD_m's (A).
RSC_1: If OM \and fake Rotate \and slave is configured to
to compute checksum for its first FD event for RL
the fake Rotate gets checksummed here.
*/
if (uint4korr(&buf[0]) == 0 &&
checksum_alg == binary_log::BINLOG_CHECKSUM_ALG_OFF &&
mi->rli->relay_log.relay_log_checksum_alg !=
binary_log::BINLOG_CHECKSUM_ALG_OFF) {
ha_checksum rot_crc = checksum_crc32(0L, nullptr, 0);
event_len += BINLOG_CHECKSUM_LEN;
memcpy(rot_buf, buf, event_len - BINLOG_CHECKSUM_LEN);
int4store(&rot_buf[EVENT_LEN_OFFSET],
uint4korr(rot_buf + EVENT_LEN_OFFSET) + BINLOG_CHECKSUM_LEN);
rot_crc = checksum_crc32(rot_crc, (const uchar *)rot_buf,
event_len - BINLOG_CHECKSUM_LEN);
int4store(&rot_buf[event_len - BINLOG_CHECKSUM_LEN], rot_crc);
DBUG_ASSERT(event_len == uint4korr(&rot_buf[EVENT_LEN_OFFSET]));
DBUG_ASSERT(
mi->get_mi_description_event()->common_footer->checksum_alg ==
mi->rli->relay_log.relay_log_checksum_alg);
/* the first one */
DBUG_ASSERT(mi->checksum_alg_before_fd !=
binary_log::BINLOG_CHECKSUM_ALG_UNDEF);
save_buf = buf;
buf = rot_buf;
} else
/*
RSC_2: If NM \and fake Rotate \and slave does not compute checksum
the fake Rotate's checksum is stripped off before relay-logging.
*/
if (uint4korr(&buf[0]) == 0 &&
checksum_alg != binary_log::BINLOG_CHECKSUM_ALG_OFF &&
mi->rli->relay_log.relay_log_checksum_alg ==
binary_log::BINLOG_CHECKSUM_ALG_OFF) {
event_len -= BINLOG_CHECKSUM_LEN;
memcpy(rot_buf, buf, event_len);
int4store(&rot_buf[EVENT_LEN_OFFSET],
uint4korr(rot_buf + EVENT_LEN_OFFSET) - BINLOG_CHECKSUM_LEN);
DBUG_ASSERT(event_len == uint4korr(&rot_buf[EVENT_LEN_OFFSET]));
DBUG_ASSERT(
mi->get_mi_description_event()->common_footer->checksum_alg ==
mi->rli->relay_log.relay_log_checksum_alg);
/* the first one */
DBUG_ASSERT(mi->checksum_alg_before_fd !=
binary_log::BINLOG_CHECKSUM_ALG_UNDEF);
save_buf = buf;
buf = rot_buf;
}
/*
Now the I/O thread has just changed its mi->get_master_log_name(), so
incrementing mi->get_master_log_pos() is nonsense.
*/
inc_pos = 0;
break;
}
case binary_log::FORMAT_DESCRIPTION_EVENT: {
/*
Create an event, and save it (when we rotate the relay log, we will have
to write this event again).
*/
/*
We are the only thread which reads/writes mi_description_event.
The relay_log struct does not move (though some members of it can
change), so we needn't any lock (no rli->data_lock, no log lock).
*/
// mark it as undefined that is irrelevant anymore
mi->checksum_alg_before_fd = binary_log::BINLOG_CHECKSUM_ALG_UNDEF;
Format_description_log_event *new_fdle;
Log_event *ev = nullptr;
if (binlog_event_deserialize(reinterpret_cast<const unsigned char *>(buf),
event_len, mi->get_mi_description_event(),
true, &ev) != Binlog_read_error::SUCCESS) {
// This error will be reported later at handle_slave_io().
goto err;
}
new_fdle = dynamic_cast<Format_description_log_event *>(ev);
if (new_fdle->common_footer->checksum_alg ==
binary_log::BINLOG_CHECKSUM_ALG_UNDEF)
new_fdle->common_footer->checksum_alg =
binary_log::BINLOG_CHECKSUM_ALG_OFF;
mi->set_mi_description_event(new_fdle);
/* installing new value of checksum Alg for relay log */
mi->rli->relay_log.relay_log_checksum_alg =
new_fdle->common_footer->checksum_alg;
/*
Though this does some conversion to the slave's format, this will
preserve the master's binlog format version, and number of event types.
*/
/*
If the event was not requested by the slave (the slave did not ask for
it), i.e. has end_log_pos=0, we do not increment
mi->get_master_log_pos()
*/
inc_pos = uint4korr(buf + LOG_POS_OFFSET) ? event_len : 0;
DBUG_PRINT("info", ("binlog format is now %d",
mi->get_mi_description_event()->binlog_version));
} break;
case binary_log::HEARTBEAT_LOG_EVENT: {
/*
HB (heartbeat) cannot come before RL (Relay)
*/
Heartbeat_log_event hb(buf, mi->get_mi_description_event());
if (!hb.is_valid()) {
char errbuf[1024];
char llbuf[22];
sprintf(errbuf,
"inconsistent heartbeat event content; the event's data: "
"log_file_name %-.512s log_pos %s",
hb.get_log_ident(), llstr(hb.common_header->log_pos, llbuf));
mi->report(ERROR_LEVEL, ER_SLAVE_HEARTBEAT_FAILURE,
ER_THD(current_thd, ER_SLAVE_HEARTBEAT_FAILURE), errbuf);
goto err;
}
mysql_mutex_lock(&mi->data_lock);
mi->received_heartbeats++;
mi->last_heartbeat = my_getsystime() / 10;
/*
During GTID protocol, if the master skips transactions,
a heartbeat event is sent to the slave at the end of last
skipped transaction to update coordinates.
I/O thread receives the heartbeat event and updates mi
only if the received heartbeat position is greater than
mi->get_master_log_pos(). This event is written to the
relay log as an ignored Rotate event. SQL thread reads
the rotate event only to update the coordinates corresponding
to the last skipped transaction. Note that,
we update only the positions and not the file names, as a ROTATE
EVENT from the master prior to this will update the file name.
*/
if (mi->is_auto_position() &&
mi->get_master_log_pos() < hb.common_header->log_pos &&
mi->get_master_log_name() != nullptr) {
DBUG_ASSERT(memcmp(const_cast<char *>(mi->get_master_log_name()),
hb.get_log_ident(), hb.get_ident_len()) == 0);
DBUG_EXECUTE_IF("reached_heart_beat_queue_event", {
const char act[] =
"now SIGNAL check_slave_master_info WAIT_FOR "
"proceed_write_rotate";
DBUG_ASSERT(
!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
};);
mi->set_master_log_pos(hb.common_header->log_pos);
/*
Put this heartbeat event in the relay log as a Rotate Event.
*/
inc_pos = 0;
mysql_mutex_unlock(&mi->data_lock);
if (write_rotate_to_master_pos_into_relay_log(
mi->info_thd, mi, false
/* force_flush_mi_info */))
goto end;
do_flush_mi = false; /* write_rotate_... above flushed master info */
} else
mysql_mutex_unlock(&mi->data_lock);
/*
compare local and event's versions of log_file, log_pos.
Heartbeat is sent only after an event corresponding to the corrdinates
the heartbeat carries.
Slave can not have a difference in coordinates except in the only
special case when mi->get_master_log_name(), mi->get_master_log_pos()
have never been updated by Rotate event i.e when slave does not have
any history with the master (and thereafter mi->get_master_log_pos() is
NULL).
TODO: handling `when' for SHOW SLAVE STATUS' snds behind
*/
if (memcmp(const_cast<char *>(mi->get_master_log_name()),
hb.get_log_ident(), hb.get_ident_len()) ||
(mi->get_master_log_pos() > hb.common_header->log_pos)) {
/* missed events of heartbeat from the past */
char errbuf[1024];
char llbuf[22];
sprintf(errbuf,
"heartbeat is not compatible with local info; "
"the event's data: log_file_name %-.512s log_pos %s",
hb.get_log_ident(), llstr(hb.common_header->log_pos, llbuf));
mi->report(ERROR_LEVEL, ER_SLAVE_HEARTBEAT_FAILURE,
ER_THD(current_thd, ER_SLAVE_HEARTBEAT_FAILURE), errbuf);
goto err;
}
goto end;
} break;
case binary_log::PREVIOUS_GTIDS_LOG_EVENT: {
/*
This event does not have any meaning for the slave and
was just sent to show the slave the master is making
progress and avoid possible deadlocks.
So at this point, the event is replaced by a rotate
event what will make the slave to update what it knows
about the master's coordinates.
*/
inc_pos = 0;
mysql_mutex_lock(&mi->data_lock);
mi->set_master_log_pos(mi->get_master_log_pos() + event_len);
mysql_mutex_unlock(&mi->data_lock);
if (write_rotate_to_master_pos_into_relay_log(
mi->info_thd, mi, true /* force_flush_mi_info */))
goto err;
do_flush_mi = false; /* write_rotate_... above flushed master info */
goto end;
} break;
case binary_log::GTID_LOG_EVENT: {
/*
This can happen if the master uses GTID_MODE=OFF_PERMISSIVE, and
sends GTID events to the slave. A possible scenario is that user
does not follow the upgrade procedure for GTIDs, and creates a
topology like A->B->C, where A uses GTID_MODE=ON_PERMISSIVE, B
uses GTID_MODE=OFF_PERMISSIVE, and C uses GTID_MODE=OFF. Each
connection is allowed, but the master A will generate GTID
transactions which will be sent through B to C. Then C will hit
this error.
*/
if (mi->get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_OFF) {
mi->report(
ERROR_LEVEL, ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF,
ER_THD(current_thd, ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF),
mi->get_master_log_name(), mi->get_master_log_pos());
goto err;
}
Gtid_log_event gtid_ev(buf, mi->get_mi_description_event());
rli->get_sid_lock()->rdlock();
gtid.sidno = gtid_ev.get_sidno(rli->get_gtid_set()->get_sid_map());
rli->get_sid_lock()->unlock();
if (gtid.sidno < 0) goto err;
gtid.gno = gtid_ev.get_gno();
original_commit_timestamp = gtid_ev.original_commit_timestamp;
immediate_commit_timestamp = gtid_ev.immediate_commit_timestamp;
inc_pos = event_len;
} break;
case binary_log::ANONYMOUS_GTID_LOG_EVENT: {
/*
This cannot normally happen, because the master has a check that
prevents it from sending anonymous events when auto_position is
enabled. However, the master could be something else than
mysqld, which could contain bugs that we have no control over.
So we need this check on the slave to be sure that whoever is on
the other side of the protocol does not break the protocol.
*/
if (mi->is_auto_position()) {
mi->report(
ERROR_LEVEL, ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION,
ER_THD(current_thd, ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION),
mi->get_master_log_name(), mi->get_master_log_pos());
goto err;
}
/*
This can happen if the master uses GTID_MODE=ON_PERMISSIVE, and
sends an anonymous event to the slave. A possible scenario is
that user does not follow the upgrade procedure for GTIDs, and
creates a topology like A->B->C, where A uses
GTID_MODE=OFF_PERMISSIVE, B uses GTID_MODE=ON_PERMISSIVE, and C
uses GTID_MODE=ON. Each connection is allowed, but the master A
will generate anonymous transactions which will be sent through
B to C. Then C will hit this error.
*/
else {
if (mi->get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_ON) {
mi->report(ERROR_LEVEL, ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON,
ER_THD(current_thd,
ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON),
mi->get_master_log_name(), mi->get_master_log_pos());
goto err;
}
}
/*
save the original_commit_timestamp and the immediate_commit_timestamp to
be later used for monitoring
*/
Gtid_log_event anon_gtid_ev(buf, mi->get_mi_description_event());
original_commit_timestamp = anon_gtid_ev.original_commit_timestamp;
immediate_commit_timestamp = anon_gtid_ev.immediate_commit_timestamp;
}
/* fall through */
default:
inc_pos = event_len;
break;
}
/*
Simulate an unknown ignorable log event by rewriting the write_rows log
event and previous_gtids log event before writing them in relay log.
*/
DBUG_EXECUTE_IF(
"simulate_unknown_ignorable_log_event",
if (event_type == binary_log::WRITE_ROWS_EVENT ||
event_type == binary_log::PREVIOUS_GTIDS_LOG_EVENT) {
char *event_buf = const_cast<char *>(buf);
/* Overwrite the log event type with an unknown type. */
event_buf[EVENT_TYPE_OFFSET] = binary_log::ENUM_END_EVENT + 1;
/* Set LOG_EVENT_IGNORABLE_F for the log event. */
int2store(event_buf + FLAGS_OFFSET,
uint2korr(event_buf + FLAGS_OFFSET) | LOG_EVENT_IGNORABLE_F);
});
/*
If this event is originating from this server, don't queue it.
We don't check this for 3.23 events because it's simpler like this; 3.23
will be filtered anyway by the SQL slave thread which also tests the
server id (we must also keep this test in the SQL thread, in case somebody
upgrades a 4.0 slave which has a not-filtered relay log).
ANY event coming from ourselves can be ignored: it is obvious for queries;
for STOP_EVENT/ROTATE_EVENT/START_EVENT: these cannot come from ourselves
(--log-slave-updates would not log that) unless this slave is also its
direct master (an unsupported, useless setup!).
*/
s_id = uint4korr(buf + SERVER_ID_OFFSET);
/*
If server_id_bits option is set we need to mask out irrelevant bits
when checking server_id, but we still put the full unmasked server_id
into the Relay log so that it can be accessed when applying the event
*/
s_id &= opt_server_id_mask;
if ((s_id == ::server_id && !mi->rli->replicate_same_server_id) ||
/*
the following conjunction deals with IGNORE_SERVER_IDS, if set
If the master is on the ignore list, execution of
format description log events and rotate events is necessary.
*/
(mi->ignore_server_ids->dynamic_ids.size() > 0 &&
mi->shall_ignore_server_id(s_id) &&
/* everything is filtered out from non-master */
(s_id != mi->master_id ||
/* for the master meta information is necessary */
(event_type != binary_log::FORMAT_DESCRIPTION_EVENT &&
event_type != binary_log::ROTATE_EVENT)))) {
/*
Do not write it to the relay log.
a) We still want to increment mi->get_master_log_pos(), so that we won't
re-read this event from the master if the slave IO thread is now
stopped/restarted (more efficient if the events we are ignoring are big
LOAD DATA INFILE).
b) We want to record that we are skipping events, for the information of
the slave SQL thread, otherwise that thread may let
rli->group_relay_log_pos stay too small if the last binlog's event is
ignored.
But events which were generated by this slave and which do not exist in
the master's binlog (i.e. Format_desc, Rotate & Stop) should not increment
mi->get_master_log_pos().
If the event is originated remotely and is being filtered out by
IGNORE_SERVER_IDS it increments mi->get_master_log_pos()
as well as rli->group_relay_log_pos.
*/
if (!(s_id == ::server_id && !mi->rli->replicate_same_server_id) ||
(event_type != binary_log::FORMAT_DESCRIPTION_EVENT &&
event_type != binary_log::ROTATE_EVENT &&
event_type != binary_log::STOP_EVENT)) {
rli->relay_log.lock_binlog_end_pos();
mi->set_master_log_pos(mi->get_master_log_pos() + inc_pos);
memcpy(rli->ign_master_log_name_end, mi->get_master_log_name(),
FN_REFLEN);
DBUG_ASSERT(rli->ign_master_log_name_end[0]);
rli->ign_master_log_pos_end = mi->get_master_log_pos();
// the slave SQL thread needs to re-check
rli->relay_log.update_binlog_end_pos(false /*need_lock*/);
rli->relay_log.unlock_binlog_end_pos();
}
DBUG_PRINT(
"info",
("master_log_pos: %lu, event originating from %u server, ignored",
(ulong)mi->get_master_log_pos(), uint4korr(buf + SERVER_ID_OFFSET)));
} else {
bool is_error = false;
/* write the event to the relay log */
if (likely(rli->relay_log.write_buffer(buf, event_len, mi) == 0)) {
DBUG_SIGNAL_WAIT_FOR(current_thd,
"pause_on_queue_event_after_write_buffer",
"receiver_reached_pause_on_queue_event",
"receiver_continue_queuing_event");
mysql_mutex_lock(&mi->data_lock);
lock_count = 2;
mi->set_master_log_pos(mi->get_master_log_pos() + inc_pos);
DBUG_PRINT("info",
("master_log_pos: %lu", (ulong)mi->get_master_log_pos()));
/*
If we are starting an anonymous transaction, we will discard
the GTID of the partial transaction that was not finished (if
there is one) when calling mi->started_queueing().
*/
#ifndef DBUG_OFF
if (event_type == binary_log::ANONYMOUS_GTID_LOG_EVENT) {
if (!mi->get_queueing_trx_gtid()->is_empty()) {
DBUG_PRINT("info", ("Discarding Gtid(%d, %lld) as the transaction "
"wasn't complete and we found an "
"ANONYMOUS_GTID_LOG_EVENT.",
mi->get_queueing_trx_gtid()->sidno,
mi->get_queueing_trx_gtid()->gno));
}
}
#endif
/*
We have to mark this GTID (either anonymous or not) as started
to be queued.
Also, if this event is a GTID_LOG_EVENT, we have to store its GTID to
add to the Retrieved_Gtid_Set later, when the last event of the
transaction be queued. The call to mi->started_queueing() will save
the GTID to be used later.
*/
if (event_type == binary_log::GTID_LOG_EVENT ||
event_type == binary_log::ANONYMOUS_GTID_LOG_EVENT) {
// set the timestamp for the start time of queueing this transaction
mi->started_queueing(gtid, original_commit_timestamp,
immediate_commit_timestamp);
}
} else {
/*
We failed to write the event and didn't updated slave positions.
We have to "rollback" the transaction parser state, or else, when
restarting the I/O thread without GTID auto positing the parser
would assume the failed event as queued.
*/
mi->transaction_parser.rollback();
is_error = true;
}
if (save_buf != nullptr) buf = save_buf;
if (is_error) {
// This error will be reported later at handle_slave_io().
goto err;
}
}
goto end;
err:
res = QUEUE_EVENT_ERROR_QUEUING;
end:
if (res == QUEUE_EVENT_OK && do_flush_mi) {
/*
Take a ride in the already locked LOCK_log to flush master info.
JAG: TODO: Notice that we could only flush master info if we are
not in the middle of a transaction. Having a proper
relay log recovery can allow us to do this.
*/
if (lock_count == 1) {
mysql_mutex_lock(&mi->data_lock);
lock_count = 2;
}
if (flush_master_info(mi, false /*force*/, lock_count == 0 /*need_lock*/,
false /*flush_relay_log*/))
res = QUEUE_EVENT_ERROR_FLUSHING_INFO;
}
if (lock_count >= 2) mysql_mutex_unlock(&mi->data_lock);
if (lock_count >= 1) mysql_mutex_unlock(log_lock);
DBUG_PRINT("info", ("queue result: %d", res));
return res;
}
/**
Hook to detach the active VIO before closing a connection handle.
The client API might close the connection (and associated data)
in case it encounters a unrecoverable (network) error. This hook
is called from the client code before the VIO handle is deleted
allows the thread to detach the active vio so it does not point
to freed memory.
Other calls to THD::clear_active_vio throughout this module are
redundant due to the hook but are left in place for illustrative
purposes.
*/
void slave_io_thread_detach_vio() {
THD *thd = current_thd;
if (thd && thd->slave_thread) thd->clear_active_vio();
}
/*
Try to connect until successful or slave killed
SYNPOSIS
safe_connect()
thd Thread handler for slave
mysql MySQL connection handle
mi Replication handle
RETURN
0 ok
# Error
*/
static int safe_connect(THD *thd, MYSQL *mysql, Master_info *mi) {
DBUG_TRACE;
return connect_to_master(thd, mysql, mi, 0, 0);
}
/*
SYNPOSIS
connect_to_master()
IMPLEMENTATION
Try to connect until successful or slave killed or we have retried
mi->retry_count times
*/
static int connect_to_master(THD *thd, MYSQL *mysql, Master_info *mi,
bool reconnect, bool suppress_warnings) {
int slave_was_killed = 0;
int last_errno = -2; // impossible error
ulong err_count = 0;
char llbuff[22];
char password[MAX_PASSWORD_LENGTH + 1];
size_t password_size = sizeof(password);
DBUG_TRACE;
set_slave_max_allowed_packet(thd, mysql);
#ifndef DBUG_OFF
mi->events_until_exit = disconnect_slave_event_count;
#endif
ulong client_flag = CLIENT_REMEMBER_OPTIONS;
/* Always reset public key to remove cached copy */
mysql_reset_server_public_key();
mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&slave_net_timeout);
mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, (char *)&slave_net_timeout);
if (mi->bind_addr[0]) {
DBUG_PRINT("info", ("bind_addr: %s", mi->bind_addr));
mysql_options(mysql, MYSQL_OPT_BIND, mi->bind_addr);
}
#ifdef HAVE_OPENSSL
/* By default the channel is not configured to use SSL */
enum mysql_ssl_mode ssl_mode = SSL_MODE_DISABLED;
if (mi->ssl) {
/* The channel is configured to use SSL */
mysql_ssl_set(mysql, mi->ssl_key[0] ? mi->ssl_key : 0,
mi->ssl_cert[0] ? mi->ssl_cert : 0,
mi->ssl_ca[0] ? mi->ssl_ca : 0,
mi->ssl_capath[0] ? mi->ssl_capath : 0,
mi->ssl_cipher[0] ? mi->ssl_cipher : 0);
mysql_options(mysql, MYSQL_OPT_SSL_CRL, mi->ssl_crl[0] ? mi->ssl_crl : 0);
mysql_options(mysql, MYSQL_OPT_TLS_VERSION,
mi->tls_version[0] ? mi->tls_version : 0);
mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH,
mi->ssl_crlpath[0] ? mi->ssl_crlpath : 0);
if (mi->ssl_verify_server_cert)
ssl_mode = SSL_MODE_VERIFY_IDENTITY;
else if (mi->ssl_ca[0] || mi->ssl_capath[0])
ssl_mode = SSL_MODE_VERIFY_CA;
else
ssl_mode = SSL_MODE_REQUIRED;
}
mysql_options(mysql, MYSQL_OPT_SSL_MODE, &ssl_mode);
#endif
mysql_options(mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
opt_slave_compressed_protocol ? COMPRESSION_ALGORITHM_ZLIB
: mi->compression_algorithm);
mysql_options(mysql, MYSQL_OPT_ZSTD_COMPRESSION_LEVEL,
&mi->zstd_compression_level);
/*
If server's default charset is not supported (like utf16, utf32) as client
charset, then set client charset to 'latin1' (default client charset).
*/
if (is_supported_parser_charset(default_charset_info))
mysql_options(mysql, MYSQL_SET_CHARSET_NAME, default_charset_info->csname);
else {
LogErr(INFORMATION_LEVEL, ER_RPL_SLAVE_CANT_USE_CHARSET,
default_charset_info->csname, default_client_charset_info->csname);
mysql_options(mysql, MYSQL_SET_CHARSET_NAME,
default_client_charset_info->csname);
}
if (mi->is_start_plugin_auth_configured()) {
DBUG_PRINT("info", ("Slaving is using MYSQL_DEFAULT_AUTH %s",
mi->get_start_plugin_auth()));
mysql_options(mysql, MYSQL_DEFAULT_AUTH, mi->get_start_plugin_auth());
}
if (mi->is_start_plugin_dir_configured()) {
DBUG_PRINT("info", ("Slaving is using MYSQL_PLUGIN_DIR %s",
mi->get_start_plugin_dir()));
mysql_options(mysql, MYSQL_PLUGIN_DIR, mi->get_start_plugin_dir());
}
/* Set MYSQL_PLUGIN_DIR in case master asks for an external authentication
plugin */
else if (opt_plugin_dir_ptr && *opt_plugin_dir_ptr)
mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir_ptr);
if (mi->public_key_path[0]) {
/* Set public key path */
DBUG_PRINT("info", ("Set master's public key path"));
mysql_options(mysql, MYSQL_SERVER_PUBLIC_KEY, mi->public_key_path);
}
/* Get public key from master */
DBUG_PRINT("info", ("Set preference to get public key from master"));
mysql_options(mysql, MYSQL_OPT_GET_SERVER_PUBLIC_KEY, &mi->get_public_key);
if (!mi->is_start_user_configured())
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_INSECURE_CHANGE_MASTER);
if (mi->get_password(password, &password_size)) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Unable to configure password when attempting to "
"connect to the master server. Connection attempt "
"terminated.");
return 1;
}
const char *user = mi->get_user();
if (user == nullptr || user[0] == 0) {
mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
ER_THD(thd, ER_SLAVE_FATAL_ERROR),
"Invalid (empty) username when attempting to "
"connect to the master server. Connection attempt "
"terminated.");
return 1;
}
mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqld");
mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_role",
"binary_log_listener");
mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD,
"_client_replication_channel_name", mi->get_channel());
while (!(slave_was_killed = io_slave_killed(thd, mi)) &&
(reconnect ? mysql_reconnect(mysql) != 0
: mysql_real_connect(mysql, mi->host, user, password, 0,
mi->port, 0, client_flag) == 0)) {
/*
SHOW SLAVE STATUS will display the number of retries which
would be real retry counts instead of mi->retry_count for
each connection attempt by 'Last_IO_Error' entry.
*/
last_errno = mysql_errno(mysql);
suppress_warnings = 0;
mi->report(ERROR_LEVEL, last_errno,
"error %s to master '%s@%s:%d'"
" - retry-time: %d retries: %lu message: %s",
(reconnect ? "reconnecting" : "connecting"), mi->get_user(),
mi->host, mi->port, mi->connect_retry, err_count + 1,
mysql_error(mysql));
/*
By default we try forever. The reason is that failure will trigger
master election, so if the user did not set mi->retry_count we
do not want to have election triggered on the first failure to
connect
*/
if (++err_count == mi->retry_count) {
slave_was_killed = 1;
break;
}
slave_sleep(thd, mi->connect_retry, io_slave_killed, mi);
}
if (!slave_was_killed) {
mi->clear_error(); // clear possible left over reconnect error
if (reconnect) {
if (!suppress_warnings)
LogErr(
SYSTEM_LEVEL, ER_RPL_SLAVE_CONNECTED_TO_MASTER_REPLICATION_RESUMED,
mi->get_for_channel_str(), mi->get_user(), mi->host, mi->port,
mi->get_io_rpl_log_name(), llstr(mi->get_master_log_pos(), llbuff));
} else {
query_logger.general_log_print(thd, COM_CONNECT_OUT, "%s@%s:%d",
mi->get_user(), mi->host, mi->port);
}
thd->set_active_vio(mysql->net.vio);
}
mysql->reconnect = 1;
DBUG_PRINT("exit", ("slave_was_killed: %d", slave_was_killed));
return slave_was_killed;
}
/*
safe_reconnect()
IMPLEMENTATION
Try to connect until successful or slave killed or we have retried
mi->retry_count times
*/
static int safe_reconnect(THD *thd, MYSQL *mysql, Master_info *mi,
bool suppress_warnings) {
DBUG_TRACE;
return connect_to_master(thd, mysql, mi, 1, suppress_warnings);
}
/*
Rotate a relay log (this is used only by FLUSH LOGS; the automatic rotation
because of size is simpler because when we do it we already have all relevant
locks; here we don't, so this function is mainly taking locks).
Returns nothing as we cannot catch any error (MYSQL_BIN_LOG::new_file()
is void).
*/
int rotate_relay_log(Master_info *mi, bool log_master_fd, bool need_lock,
bool need_log_space_lock) {
DBUG_TRACE;
Relay_log_info *rli = mi->rli;
if (need_lock)
mysql_mutex_lock(rli->relay_log.get_log_lock());
else
mysql_mutex_assert_owner(rli->relay_log.get_log_lock());
DBUG_EXECUTE_IF("crash_before_rotate_relaylog", DBUG_SUICIDE(););
int error = 0;
/*
We need to test inited because otherwise, new_file() will attempt to lock
LOCK_log, which may not be inited (if we're not a slave).
*/
if (!rli->inited) {
DBUG_PRINT("info", ("rli->inited == 0"));
goto end;
}
/* If the relay log is closed, new_file() will do nothing. */
if (log_master_fd)
error =
rli->relay_log.new_file_without_locking(mi->get_mi_description_event());
else
error = rli->relay_log.new_file_without_locking(nullptr);
if (error != 0) goto end;
/*
We harvest now, because otherwise BIN_LOG_HEADER_SIZE will not immediately
be counted, so imagine a succession of FLUSH LOGS and assume the slave
threads are started:
relay_log_space decreases by the size of the deleted relay log, but does
not increase, so flush-after-flush we may become negative, which is wrong.
Even if this will be corrected as soon as a query is replicated on the
slave (because the I/O thread will then call harvest_bytes_written() which
will harvest all these BIN_LOG_HEADER_SIZE we forgot), it may give strange
output in SHOW SLAVE STATUS meanwhile. So we harvest now.
If the log is closed, then this will just harvest the last writes, probably
0 as they probably have been harvested.
*/
rli->relay_log.harvest_bytes_written(rli, need_log_space_lock);
end:
if (need_lock) mysql_mutex_unlock(rli->relay_log.get_log_lock());
return error;
}
/**
flushes the relay logs of a replication channel.
@param[in] mi Master_info corresponding to the
channel.
@param[in] thd the client thread carrying the command.
@return
@retval 1 fail
@retval 0 ok
@retval -1 deferred flush
*/
int flush_relay_logs(Master_info *mi, THD *thd) {
DBUG_TRACE;
int error = 0;
if (mi) {
Relay_log_info *rli = mi->rli;
if (rli->inited) {
// Rotate immediately if one is true:
if ((!is_group_replication_plugin_loaded() || // GR is disabled
!mi->transaction_parser
.is_inside_transaction() || // not inside a transaction
!channel_map.is_group_replication_channel_name(
mi->get_channel(), true) || // channel isn't GR applier channel
!mi->slave_running) && // the I/O thread isn't running
DBUG_EVALUATE_IF("deferred_flush_relay_log",
!channel_map.is_group_replication_channel_name(
mi->get_channel(), true),
true)) {
if (rotate_relay_log(mi)) error = 1;
}
// Postpone the rotate action, delegating it to the I/O thread
else {
channel_map.unlock();
mi->request_rotate(thd);
channel_map.rdlock();
error = -1;
}
}
}
return error;
}
/**
Entry point for FLUSH RELAYLOGS command or to flush relaylogs for
the FLUSH LOGS command.
FLUSH LOGS or FLUSH RELAYLOGS needs to flush the relaylogs of all
the replciaiton channels in multisource replication.
FLUSH RELAYLOGS FOR CHANNEL flushes only the relaylogs pertaining to
a channel.
@param[in] thd the client thread carrying the command.
@return
@retval true fail
@retval false success
*/
bool flush_relay_logs_cmd(THD *thd) {
DBUG_TRACE;
Master_info *mi = nullptr;
LEX *lex = thd->lex;
bool error = false;
channel_map.rdlock();
/*
lex->mi.channel is NULL, for FLUSH LOGS or when the client thread
is not present. (See tmp_thd in the caller).
When channel is not provided, lex->mi.for_channel is false.
*/
if (!lex->mi.channel || !lex->mi.for_channel) {
bool flush_was_deferred{false};
enum_channel_type channel_types[] = {SLAVE_REPLICATION_CHANNEL,
GROUP_REPLICATION_CHANNEL};
for (auto channel_type : channel_types) {
mi_map already_processed;
do {
flush_was_deferred = false;
for (mi_map::iterator it = channel_map.begin(channel_type);
it != channel_map.end(channel_type); it++) {
if (already_processed.find(it->first) != already_processed.end())
continue;
mi = it->second;
already_processed.insert(std::make_pair(it->first, mi));
int flush_status = flush_relay_logs(mi, thd);
flush_was_deferred = (flush_status == -1);
error = (flush_status == 1);
if (flush_status != 0) break;
}
} while (flush_was_deferred);
}
} else {
mi = channel_map.get_mi(lex->mi.channel);
if (mi) {
error = (flush_relay_logs(mi, thd) == 1);
} else {
if (thd->system_thread == SYSTEM_THREAD_SLAVE_SQL ||
thd->system_thread == SYSTEM_THREAD_SLAVE_WORKER) {
/*
Log warning on SQL or worker threads.
*/
LogErr(WARNING_LEVEL, ER_RPL_SLAVE_INCORRECT_CHANNEL, lex->mi.channel);
} else {
/*
Return error on client sessions.
*/
error = true;
my_error(ER_SLAVE_CHANNEL_DOES_NOT_EXIST, MYF(0), lex->mi.channel);
}
}
}
channel_map.unlock();
return error;
}
bool reencrypt_relay_logs() {
DBUG_TRACE;
Master_info *mi;
channel_map.rdlock();
enum_channel_type channel_types[] = {SLAVE_REPLICATION_CHANNEL,
GROUP_REPLICATION_CHANNEL};
for (auto channel_type : channel_types) {
for (mi_map::iterator it = channel_map.begin(channel_type);
it != channel_map.end(channel_type); it++) {
mi = it->second;
if (mi != nullptr) {
Relay_log_info *rli = mi->rli;
if (rli != nullptr && rli->inited && rli->relay_log.reencrypt_logs()) {
channel_map.unlock();
return true;
}
}
}
}
channel_map.unlock();
return false;
}
/**
Detects, based on master's version (as found in the relay log), if master
has a certain bug.
@param rli Relay_log_info which tells the master's version
@param bug_id Number of the bug as found in bugs.mysql.com
@param report bool report error message, default true
@param pred Predicate function that will be called with @c param to
check for the bug. If the function return @c true, the bug is present,
otherwise, it is not.
@param param State passed to @c pred function.
@return true if master has the bug, false if it does not.
*/
bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report,
bool (*pred)(const void *), const void *param) {
struct st_version_range_for_one_bug {
uint bug_id;
const uchar introduced_in[3]; // first version with bug
const uchar fixed_in[3]; // first version with fix
};
static struct st_version_range_for_one_bug versions_for_all_bugs[] = {
{24432, {5, 0, 24}, {5, 0, 38}}, {24432, {5, 1, 12}, {5, 1, 17}},
{33029, {5, 0, 0}, {5, 0, 58}}, {33029, {5, 1, 0}, {5, 1, 12}},
{37426, {5, 1, 0}, {5, 1, 26}},
};
const uchar *master_ver =
rli->get_rli_description_event()->server_version_split;
DBUG_ASSERT(sizeof(rli->get_rli_description_event()->server_version_split) ==
3);
for (uint i = 0;
i < sizeof(versions_for_all_bugs) / sizeof(*versions_for_all_bugs);
i++) {
const uchar *introduced_in = versions_for_all_bugs[i].introduced_in,
*fixed_in = versions_for_all_bugs[i].fixed_in;
if ((versions_for_all_bugs[i].bug_id == bug_id) &&
(memcmp(introduced_in, master_ver, 3) <= 0) &&
(memcmp(fixed_in, master_ver, 3) > 0) &&
(pred == nullptr || (*pred)(param))) {
if (!report) return true;
// a short message for SHOW SLAVE STATUS (message length constraints)
my_printf_error(ER_UNKNOWN_ERROR,
"master may suffer from"
" http://bugs.mysql.com/bug.php?id=%u"
" so slave stops; check error log on slave"
" for more info",
MYF(0), bug_id);
// a verbose message for the error log
enum loglevel report_level = INFORMATION_LEVEL;
if (!ignored_error_code(ER_UNKNOWN_ERROR)) {
report_level = ERROR_LEVEL;
current_thd->is_slave_error = 1;
} else if (log_error_verbosity >= 2)
report_level = WARNING_LEVEL;
if (report_level != INFORMATION_LEVEL)
rli->report(report_level, ER_SERVER_UNKNOWN_ERROR,
"According to the master's version ('%s'),"
" it is probable that master suffers from this bug:"
" http://bugs.mysql.com/bug.php?id=%u"
" and thus replicating the current binary log event"
" may make the slave's data become different from the"
" master's data."
" To take no risk, slave refuses to replicate"
" this event and stops."
" We recommend that all updates be stopped on the"
" master and slave, that the data of both be"
" manually synchronized,"
" that master's binary logs be deleted,"
" that master be upgraded to a version at least"
" equal to '%d.%d.%d'. Then replication can be"
" restarted.",
rli->get_rli_description_event()->server_version, bug_id,
fixed_in[0], fixed_in[1], fixed_in[2]);
return true;
}
}
return false;
}
/**
BUG#33029, For all 5.0 up to 5.0.58 exclusive, and 5.1 up to 5.1.12
exclusive, if one statement in a SP generated AUTO_INCREMENT value
by the top statement, all statements after it would be considered
generated AUTO_INCREMENT value by the top statement, and a
erroneous INSERT_ID value might be associated with these statement,
which could cause duplicate entry error and stop the slave.
Detect buggy master to work around.
*/
bool rpl_master_erroneous_autoinc(THD *thd) {
if (thd->rli_slave && thd->rli_slave->info_thd == thd) {
Relay_log_info *c_rli = thd->rli_slave->get_c_rli();
DBUG_EXECUTE_IF("simulate_bug33029", return true;);
return rpl_master_has_bug(c_rli, 33029, false, nullptr, nullptr);
}
return false;
}
/**
a copy of active_mi->rli->slave_skip_counter, for showing in SHOW GLOBAL
VARIABLES, INFORMATION_SCHEMA.GLOBAL_VARIABLES and @@sql_slave_skip_counter
without taking all the mutexes needed to access
active_mi->rli->slave_skip_counter properly.
*/
uint sql_slave_skip_counter;
/**
Executes a START SLAVE statement.
@param thd Pointer to THD object for the client thread
executing the statement.
@param connection_param Connection parameters for starting threads
@param master_param Master parameters used for starting threads
@param thread_mask_input The thread mask that identifies which threads to
start. If 0 is passed (start no thread) then this
parameter is ignored and all stopped threads are
started
@param mi Pointer to Master_info object for the slave's IO
thread.
@param set_mts_settings If true, the channel uses the server MTS
configured settings when starting the applier
thread.
@retval false success
@retval true error
*/
bool start_slave(THD *thd, LEX_SLAVE_CONNECTION *connection_param,
LEX_MASTER_INFO *master_param, int thread_mask_input,
Master_info *mi, bool set_mts_settings) {
bool is_error = false;
int thread_mask;
DBUG_TRACE;
/*
START SLAVE command should ignore 'read-only' and 'super_read_only'
options so that it can update 'mysql.slave_master_info' and
'mysql.slave_relay_log_info' replication repository tables.
*/
thd->set_skip_readonly_check();
Security_context *sctx = thd->security_context();
if (!sctx->check_access(SUPER_ACL) &&
!sctx->has_global_grant(STRING_WITH_LEN("REPLICATION_SLAVE_ADMIN"))
.first) {
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
"SUPER or REPLICATION_SLAVE_ADMIN");
return 1;
}
mi->channel_wrlock();
if (connection_param->user || connection_param->password) {
if (!thd->get_ssl()) {
push_warning(thd, Sql_condition::SL_NOTE, ER_INSECURE_PLAIN_TEXT,
ER_THD(thd, ER_INSECURE_PLAIN_TEXT));
}
}
lock_slave_threads(mi); // this allows us to cleanly read slave_running
// Get a mask of _stopped_ threads
init_thread_mask(&thread_mask, mi, 1 /* inverse */);
/*
Below we will start all stopped threads. But if the user wants to
start only one thread, do as if the other thread was running (as we
don't wan't to touch the other thread), so set the bit to 0 for the
other thread
*/
if (thread_mask_input) {
thread_mask &= thread_mask_input;
}
if (thread_mask) // some threads are stopped, start them
{
if (load_mi_and_rli_from_repositories(mi, false, thread_mask)) {
is_error = true;
my_error(ER_MASTER_INFO, MYF(0));
} else if (*mi->host || !(thread_mask & SLAVE_IO)) {
/*
If we will start IO thread we need to take care of possible
options provided through the START SLAVE if there is any.
*/
if (thread_mask & SLAVE_IO) {
if (connection_param->user) {
mi->set_start_user_configured(true);
mi->set_user(connection_param->user);
}
if (connection_param->password) {
mi->set_start_user_configured(true);
mi->set_password(connection_param->password);
}
if (connection_param->plugin_auth)
mi->set_plugin_auth(connection_param->plugin_auth);
if (connection_param->plugin_dir)
mi->set_plugin_dir(connection_param->plugin_dir);
}
/*
If we will start SQL thread we will care about UNTIL options If
not and they are specified we will ignore them and warn user
about this fact.
*/
if (thread_mask & SLAVE_SQL) {
/*
sql_slave_skip_counter only effects the applier thread which is
first started. So after sql_slave_skip_counter is copied to
rli->slave_skip_counter, it is reset to 0.
*/
mysql_mutex_lock(&LOCK_sql_slave_skip_counter);
mi->rli->slave_skip_counter = sql_slave_skip_counter;
sql_slave_skip_counter = 0;
mysql_mutex_unlock(&LOCK_sql_slave_skip_counter);
/*
To cache the MTS system var values and used them in the following
runtime. The system vars can change meanwhile but having no other
effects.
It also allows the per channel definition of this variables.
*/
if (set_mts_settings) {
mi->rli->opt_slave_parallel_workers = opt_mts_slave_parallel_workers;
if (mts_parallel_option == MTS_PARALLEL_TYPE_DB_NAME)
mi->rli->channel_mts_submode = MTS_PARALLEL_TYPE_DB_NAME;
else
mi->rli->channel_mts_submode = MTS_PARALLEL_TYPE_LOGICAL_CLOCK;
#ifndef DBUG_OFF
if (!DBUG_EVALUATE_IF("check_slave_debug_group", 1, 0))
#endif
mi->rli->checkpoint_group = opt_mts_checkpoint_group;
}
int slave_errno = mi->rli->init_until_option(thd, master_param);
if (slave_errno) {
my_error(slave_errno, MYF(0));
is_error = true;
}
if (!is_error) is_error = check_slave_sql_config_conflict(mi->rli);
} else if (master_param->pos || master_param->relay_log_pos ||
master_param->gtid)
push_warning(thd, Sql_condition::SL_NOTE, ER_UNTIL_COND_IGNORED,
ER_THD(thd, ER_UNTIL_COND_IGNORED));
if (!is_error)
is_error =
start_slave_threads(false /*need_lock_slave=false*/,
true /*wait_for_start=true*/, mi, thread_mask);
} else {
is_error = true;
my_error(ER_BAD_SLAVE, MYF(0));
}
} else {
/* no error if all threads are already started, only a warning */
push_warning_printf(
thd, Sql_condition::SL_NOTE, ER_SLAVE_CHANNEL_WAS_RUNNING,
ER_THD(thd, ER_SLAVE_CHANNEL_WAS_RUNNING), mi->get_channel());
}
/*
Clean up start information if there was an attempt to start
the IO thread to avoid any security issue.
*/
if (is_error && (thread_mask & SLAVE_IO) == SLAVE_IO) mi->reset_start_info();
unlock_slave_threads(mi);
mi->channel_unlock();
return is_error;
}
/**
Execute a STOP SLAVE statement.
@param thd Pointer to THD object for the client thread executing
the statement.
@param mi Pointer to Master_info object for the slave's IO
thread.
@param net_report If true, saves the exit status into Diagnostics_area.
@param for_one_channel If the method is being invoked only for one channel
@param push_temp_tables_warning If it should push a "have temp tables
warning" once having open temp tables. This
avoids multiple warnings when there is more
than one channel with open temp tables.
This parameter can be removed when the
warning is issued with per-channel
information.
@retval 0 success
@retval 1 error
*/
int stop_slave(THD *thd, Master_info *mi, bool net_report, bool for_one_channel,
bool *push_temp_tables_warning) {
DBUG_TRACE;
int slave_errno;
if (!thd) thd = current_thd;
/*
STOP SLAVE command should ignore 'read-only' and 'super_read_only'
options so that it can update 'mysql.slave_master_info' and
'mysql.slave_relay_log_info' replication repository tables.
*/
thd->set_skip_readonly_check();
Security_context *sctx = thd->security_context();
if (!sctx->check_access(SUPER_ACL) &&
!sctx->has_global_grant(STRING_WITH_LEN("REPLICATION_SLAVE_ADMIN"))
.first) {
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
"SUPER or REPLICATION_SLAVE_ADMIN");
return 1;
}
mi->channel_wrlock();
THD_STAGE_INFO(thd, stage_killing_slave);
int thread_mask;
lock_slave_threads(mi);
DBUG_EXECUTE_IF("simulate_hold_run_locks_on_stop_slave", my_sleep(10000000););
// Get a mask of _running_ threads
init_thread_mask(&thread_mask, mi, 0 /* not inverse*/);
/*
Below we will stop all running threads.
But if the user wants to stop only one thread, do as if the other thread
was stopped (as we don't wan't to touch the other thread), so set the
bit to 0 for the other thread
*/
if (thd->lex->slave_thd_opt) thread_mask &= thd->lex->slave_thd_opt;
if (thread_mask) {
slave_errno =
terminate_slave_threads(mi, thread_mask, rpl_stop_slave_timeout,
false /*need_lock_term=false*/);
} else {
// no error if both threads are already stopped, only a warning
slave_errno = 0;
push_warning_printf(
thd, Sql_condition::SL_NOTE, ER_SLAVE_CHANNEL_WAS_NOT_RUNNING,
ER_THD(thd, ER_SLAVE_CHANNEL_WAS_NOT_RUNNING), mi->get_channel());
}
/*
If the slave has open temp tables and there is a following CHANGE MASTER
there is a possibility that the temporary tables are left open forever.
Though we dont restrict failover here, we do warn users. In future, we
should have a command to delete open temp tables the slave has replicated.
See WL#7441 regarding this command.
*/
if (mi->rli->atomic_channel_open_temp_tables && *push_temp_tables_warning) {
push_warning(thd, Sql_condition::SL_WARNING,
ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO,
ER_THD(thd, ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO));
*push_temp_tables_warning = false;
}
unlock_slave_threads(mi);
mi->channel_unlock();
if (slave_errno) {
if ((slave_errno == ER_STOP_SLAVE_SQL_THREAD_TIMEOUT) ||
(slave_errno == ER_STOP_SLAVE_IO_THREAD_TIMEOUT)) {
push_warning(thd, Sql_condition::SL_NOTE, slave_errno,
ER_THD_NONCONST(thd, slave_errno));
/*
If new slave_errno is added in the if() condition above then make sure
that there are no % in the error message or change the logging API
to use verbatim() to avoid % substitutions.
*/
longlong log_errno = (slave_errno == ER_STOP_SLAVE_SQL_THREAD_TIMEOUT)
? ER_RPL_SLAVE_SQL_THREAD_STOP_CMD_EXEC_TIMEOUT
: ER_RPL_SLAVE_IO_THREAD_STOP_CMD_EXEC_TIMEOUT;
LogErr(WARNING_LEVEL, log_errno);
}
if (net_report) my_error(slave_errno, MYF(0));
return 1;
} else if (net_report && for_one_channel)
my_ok(thd);
return 0;
}
/**
Execute a RESET SLAVE (for all channels), used in Multisource replication.
If resetting of a particular channel fails, it exits out.
@param[in] thd THD object of the client.
@retval 0 success
@retval 1 error
*/
int reset_slave(THD *thd) {
DBUG_TRACE;
channel_map.assert_some_wrlock();
Master_info *mi = nullptr;
int result = 0;
mi_map::iterator it, gr_channel_map_it;
if (thd->lex->reset_slave_info.all) {
/* First do reset_slave for default channel */
mi = channel_map.get_default_channel_mi();
if (mi && reset_slave(thd, mi, thd->lex->reset_slave_info.all)) return 1;
/* Do while iteration for rest of the channels */
it = channel_map.begin();
while (it != channel_map.end()) {
if (!it->first.compare(channel_map.get_default_channel())) {
it++;
continue;
}
mi = it->second;
DBUG_ASSERT(mi);
if ((result = reset_slave(thd, mi, thd->lex->reset_slave_info.all)))
break;
it = channel_map.begin();
}
/* RESET group replication specific channels */
gr_channel_map_it = channel_map.begin(GROUP_REPLICATION_CHANNEL);
while (gr_channel_map_it != channel_map.end(GROUP_REPLICATION_CHANNEL)) {
mi = gr_channel_map_it->second;
DBUG_ASSERT(mi);
/*
We cannot RESET a group replication channel while the group
replication is running.
*/
if (is_group_replication_running()) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"RESET SLAVE ALL FOR CHANNEL", mi->get_channel());
return 1;
}
if ((result = reset_slave(thd, mi, thd->lex->reset_slave_info.all)))
break;
gr_channel_map_it = channel_map.begin(GROUP_REPLICATION_CHANNEL);
}
} else {
it = channel_map.begin();
while (it != channel_map.end()) {
mi = it->second;
DBUG_ASSERT(mi);
if ((result = reset_slave(thd, mi, thd->lex->reset_slave_info.all)))
break;
it++;
}
/*
RESET group replication specific channels.
We cannot RESET a group replication channel while the group
replication is running.
*/
gr_channel_map_it = channel_map.begin(GROUP_REPLICATION_CHANNEL);
while (gr_channel_map_it != channel_map.end(GROUP_REPLICATION_CHANNEL)) {
mi = gr_channel_map_it->second;
DBUG_ASSERT(mi);
if (is_group_replication_running()) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"RESET SLAVE FOR CHANNEL", mi->get_channel());
return 1;
}
if ((result = reset_slave(thd, mi, thd->lex->reset_slave_info.all)))
break;
gr_channel_map_it++;
}
}
return result;
}
/**
Execute a RESET SLAVE statement.
Locks slave threads and unlocks the slave threads after executing
reset slave.
@param thd Pointer to THD object of the client thread executing the
statement.
@param mi Pointer to Master_info object for the slave.
@param reset_all Do a full reset or only clean master info structures
@retval 0 success
@retval !=0 error
*/
int reset_slave(THD *thd, Master_info *mi, bool reset_all) {
int thread_mask = 0, error = 0;
const char *errmsg = "Unknown error occurred while reseting slave";
DBUG_TRACE;
bool is_default_channel =
strcmp(mi->get_channel(), channel_map.get_default_channel()) == 0;
/*
RESET SLAVE command should ignore 'read-only' and 'super_read_only'
options so that it can update 'mysql.slave_master_info' and
'mysql.slave_relay_log_info' replication repository tables.
*/
thd->set_skip_readonly_check();
mi->channel_wrlock();
lock_slave_threads(mi);
init_thread_mask(&thread_mask, mi, 0 /* not inverse */);
if (thread_mask) // We refuse if any slave thread is running
{
my_error(ER_SLAVE_CHANNEL_MUST_STOP, MYF(0), mi->get_channel());
error = ER_SLAVE_CHANNEL_MUST_STOP;
unlock_slave_threads(mi);
mi->channel_unlock();
goto err;
}
ha_reset_slave(thd);
// delete relay logs, clear relay log coordinates
if ((error = mi->rli->purge_relay_logs(thd, &errmsg,
reset_all && !is_default_channel))) {
my_error(ER_RELAY_LOG_FAIL, MYF(0), errmsg);
error = ER_RELAY_LOG_FAIL;
unlock_slave_threads(mi);
mi->channel_unlock();
goto err;
}
/* Clear master's log coordinates and associated information */
DBUG_ASSERT(!mi->rli || !mi->rli->slave_running); // none writes in rli table
if (remove_info(mi)) {
error = ER_UNKNOWN_ERROR;
my_error(ER_UNKNOWN_ERROR, MYF(0));
unlock_slave_threads(mi);
mi->channel_unlock();
goto err;
}
if (!reset_all) {
mi->init_master_log_pos();
mi->master_uuid[0] = 0;
/*
This shall prevent the channel to vanish if server is restarted
after this RESET SLAVE and before the channel be started.
*/
mysql_mutex_lock(&mi->data_lock);
if (mi->reset && opt_mi_repository_id == INFO_REPOSITORY_TABLE &&
opt_rli_repository_id == INFO_REPOSITORY_TABLE &&
(mi->flush_info(true))) {
error = ER_MASTER_INFO;
my_error(ER_MASTER_INFO, MYF(0));
mysql_mutex_unlock(&mi->data_lock);
unlock_slave_threads(mi);
mi->channel_unlock();
goto err;
}
mysql_mutex_unlock(&mi->data_lock);
}
unlock_slave_threads(mi);
(void)RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi));
/*
RESET SLAVE ALL deletes the channels(except default channel), so their mi
and rli objects are removed. For default channel, its mi and rli are
deleted and recreated to keep in clear status.
*/
if (reset_all) {
bool is_default =
!strcmp(mi->get_channel(), channel_map.get_default_channel());
channel_map.delete_mi(mi->get_channel());
if (is_default) {
if (!Rpl_info_factory::create_mi_and_rli_objects(
opt_mi_repository_id, opt_rli_repository_id,
channel_map.get_default_channel(), true, &channel_map)) {
error = ER_MASTER_INFO;
my_message(ER_MASTER_INFO, ER_THD(thd, ER_MASTER_INFO), MYF(0));
}
}
} else {
mi->channel_unlock();
}
err:
return error;
}
/**
Entry function for RESET SLAVE command. Function either resets
the slave for all channels or for a single channel.
When RESET SLAVE ALL is given, the slave_info_objects (mi, rli & workers)
are destroyed.
@param[in] thd the client thread with the command.
@return
@retval false OK
@retval true not OK
*/
bool reset_slave_cmd(THD *thd) {
DBUG_TRACE;
Master_info *mi;
LEX *lex = thd->lex;
bool res = true; // default, an error
channel_map.wrlock();
if (!is_slave_configured()) {
my_error(ER_SLAVE_CONFIGURATION, MYF(0));
channel_map.unlock();
return res = true;
}
if (!lex->mi.for_channel)
res = reset_slave(thd);
else {
mi = channel_map.get_mi(lex->mi.channel);
/*
If the channel being used is a group replication channel and
group_replication is still running we need to disable RESET SLAVE [ALL]
command.
*/
if (mi &&
channel_map.is_group_replication_channel_name(mi->get_channel(),
true) &&
is_group_replication_running()) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"RESET SLAVE [ALL] FOR CHANNEL", mi->get_channel());
channel_map.unlock();
return true;
}
if (mi)
res = reset_slave(thd, mi, thd->lex->reset_slave_info.all);
else if (strcmp(channel_map.get_default_channel(), lex->mi.channel))
my_error(ER_SLAVE_CHANNEL_DOES_NOT_EXIST, MYF(0), lex->mi.channel);
}
channel_map.unlock();
return res;
}
/**
This function checks if the given CHANGE MASTER command has any receive
option being set or changed.
- used in change_master().
@param lex_mi structure that holds all change master options given on the
change master command.
@retval false No change master receive option.
@retval true At least one receive option was there.
*/
static bool have_change_master_receive_option(const LEX_MASTER_INFO *lex_mi) {
bool have_receive_option = false;
DBUG_TRACE;
/* Check if *at least one* receive option is given on change master command*/
if (lex_mi->host || lex_mi->user || lex_mi->password ||
lex_mi->log_file_name || lex_mi->pos || lex_mi->bind_addr ||
lex_mi->network_namespace || lex_mi->port || lex_mi->connect_retry ||
lex_mi->server_id || lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->retry_count_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl_key || lex_mi->ssl_cert || lex_mi->ssl_ca ||
lex_mi->ssl_capath || lex_mi->tls_version || lex_mi->ssl_cipher ||
lex_mi->ssl_crl || lex_mi->ssl_crlpath ||
lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE ||
lex_mi->public_key_path ||
lex_mi->get_public_key != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->zstd_compression_level || lex_mi->compression_algorithm)
have_receive_option = true;
return have_receive_option;
}
/**
This function checks all possible cases in which compression algorithm,
compression level can be configured for a channel.
- used in change_receive_options
@param lex_mi pointer to structure holding all options specified
as part of change master to statement
@param mi pointer to structure holding all options specified
as part of change master to statement after performing
necessary checks
@retval false in case of success
@retval true in case of failures
*/
static bool change_master_set_compression(THD *, const LEX_MASTER_INFO *lex_mi,
Master_info *mi) {
DBUG_TRACE;
if (lex_mi->compression_algorithm) {
if (validate_compression_attributes(lex_mi->compression_algorithm,
lex_mi->channel, false))
return true;
DBUG_ASSERT(sizeof(mi->compression_algorithm) >
strlen(lex_mi->compression_algorithm));
strcpy(mi->compression_algorithm, lex_mi->compression_algorithm);
}
/* level specified */
if (lex_mi->zstd_compression_level) {
/* vaildate compression level */
if (!is_zstd_compression_level_valid(lex_mi->zstd_compression_level)) {
my_error(ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT, MYF(0),
lex_mi->zstd_compression_level, lex_mi->channel);
return true;
}
mi->zstd_compression_level = lex_mi->zstd_compression_level;
}
return false;
}
/**
This function checks if the given CHANGE MASTER command has any execute
option being set or changed.
- used in change_master().
@param lex_mi structure that holds all change master options given on the
change master command.
@param[out] need_relay_log_purge
- If relay_log_file/relay_log_pos options are used,
we wont delete relaylogs. We set this boolean flag to false.
- If relay_log_file/relay_log_pos options are NOT used,
we return the boolean flag UNCHANGED.
- Used in change_receive_options() and change_master().
@retval false No change master execute option.
@retval true At least one execute option was there.
*/
static bool have_change_master_execute_option(const LEX_MASTER_INFO *lex_mi,
bool *need_relay_log_purge) {
bool have_execute_option = false;
DBUG_TRACE;
/* Check if *at least one* execute option is given on change master command*/
if (lex_mi->relay_log_name || lex_mi->relay_log_pos ||
lex_mi->sql_delay != -1 || lex_mi->privilege_checks_username != nullptr ||
lex_mi->privilege_checks_none)
have_execute_option = true;
if (lex_mi->relay_log_name || lex_mi->relay_log_pos)
*need_relay_log_purge = false;
return have_execute_option;
}
/**
This function is called if the change master command had at least one
receive option. This function then sets or alters the receive option(s)
given in the command. The execute options are handled in the function
change_execute_options()
- used in change_master().
- Receiver threads should be stopped when this function is called.
@param thd Pointer to THD object for the client thread executing the
statement.
@param lex_mi structure that holds all change master options given on the
change master command.
Coming from the an executing statement or set directly this
shall contain connection settings like hostname, user, password
and other settings like the number of connection retries.
@param mi Pointer to Master_info object belonging to the slave's IO
thread.
@retval 0 no error i.e., success.
@retval !=0 error.
*/
static int change_receive_options(THD *thd, LEX_MASTER_INFO *lex_mi,
Master_info *mi) {
int ret = 0; /* return value. Set if there is an error. */
DBUG_TRACE;
/*
If the user specified host or port without binlog or position,
reset binlog's name to FIRST and position to 4.
*/
if ((lex_mi->host && strcmp(lex_mi->host, mi->host)) ||
(lex_mi->port && lex_mi->port != mi->port)) {
/*
This is necessary because the primary key, i.e. host or port, has
changed.
The repository does not support direct changes on the primary key,
so the row is dropped and re-inserted with a new primary key. If we
don't do that, the master info repository we will end up with several
rows.
*/
if (mi->clean_info()) {
ret = 1;
goto err;
}
mi->master_uuid[0] = 0;
mi->master_id = 0;
}
if ((lex_mi->host || lex_mi->port) && !lex_mi->log_file_name &&
!lex_mi->pos) {
char *var_master_log_name = nullptr;
var_master_log_name = const_cast<char *>(mi->get_master_log_name());
var_master_log_name[0] = '\0';
mi->set_master_log_pos(BIN_LOG_HEADER_SIZE);
}
if (lex_mi->log_file_name) mi->set_master_log_name(lex_mi->log_file_name);
if (lex_mi->pos) {
mi->set_master_log_pos(lex_mi->pos);
}
if (lex_mi->log_file_name && !lex_mi->pos)
push_warning(thd, Sql_condition::SL_WARNING,
ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS,
ER_THD(thd, ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS));
DBUG_PRINT("info", ("master_log_pos: %lu", (ulong)mi->get_master_log_pos()));
if (lex_mi->user || lex_mi->password) {
if (!thd->get_ssl()) {
push_warning(thd, Sql_condition::SL_NOTE, ER_INSECURE_PLAIN_TEXT,
ER_THD(thd, ER_INSECURE_PLAIN_TEXT));
}
push_warning(thd, Sql_condition::SL_NOTE, ER_INSECURE_CHANGE_MASTER,
ER_THD(thd, ER_INSECURE_CHANGE_MASTER));
}
if (lex_mi->user) mi->set_user(lex_mi->user);
if (lex_mi->password) mi->set_password(lex_mi->password);
if (lex_mi->host) strmake(mi->host, lex_mi->host, sizeof(mi->host) - 1);
if (lex_mi->bind_addr)
strmake(mi->bind_addr, lex_mi->bind_addr, sizeof(mi->bind_addr) - 1);
if (lex_mi->network_namespace)
strmake(mi->network_namespace, lex_mi->network_namespace,
sizeof(mi->network_namespace) - 1);
/*
Setting channel's port number explicitly to '0' should be allowed.
Eg: 'group_replication_recovery' channel (*after recovery is done*)
or 'group_replication_applier' channel wants to set the port number
to '0' as there is no actual network usage on these channels.
*/
if (lex_mi->port || lex_mi->port_opt == LEX_MASTER_INFO::LEX_MI_ENABLE)
mi->port = lex_mi->port;
if (lex_mi->connect_retry) mi->connect_retry = lex_mi->connect_retry;
if (lex_mi->retry_count_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
mi->retry_count = lex_mi->retry_count;
if (lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
mi->heartbeat_period = lex_mi->heartbeat_period;
else if (lex_mi->host || lex_mi->port) {
/*
If the user specified host or port or both without heartbeat_period,
we use default value for heartbeat_period. By default, We want to always
have heartbeat enabled when we switch master unless
master_heartbeat_period is explicitly set to zero (heartbeat disabled).
Here is the default value for heartbeat period if CHANGE MASTER did not
specify it. (no data loss in conversion as hb period has a max)
*/
mi->heartbeat_period =
min<float>(SLAVE_MAX_HEARTBEAT_PERIOD, (slave_net_timeout / 2.0f));
DBUG_ASSERT(mi->heartbeat_period > (float)0.001 ||
mi->heartbeat_period == 0);
// counter is cleared if master is CHANGED.
mi->received_heartbeats = 0;
// clear timestamp of last heartbeat as well.
mi->last_heartbeat = 0;
}
/*
reset the last time server_id list if the current CHANGE MASTER
is mentioning IGNORE_SERVER_IDS= (...)
*/
if (lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE)
mi->ignore_server_ids->dynamic_ids.clear();
for (size_t i = 0; i < lex_mi->repl_ignore_server_ids.size(); i++) {
ulong s_id = lex_mi->repl_ignore_server_ids[i];
if (s_id == ::server_id && replicate_same_server_id) {
ret = ER_SLAVE_IGNORE_SERVER_IDS;
my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), static_cast<int>(s_id));
goto err;
} else {
// Keep the array sorted, ignore duplicates.
mi->ignore_server_ids->dynamic_ids.insert_unique(s_id);
}
}
if (lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
mi->ssl = (lex_mi->ssl == LEX_MASTER_INFO::LEX_MI_ENABLE);
if (lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
mi->ssl_verify_server_cert =
(lex_mi->ssl_verify_server_cert == LEX_MASTER_INFO::LEX_MI_ENABLE);
if (lex_mi->public_key_path)
strmake(mi->public_key_path, lex_mi->public_key_path,
sizeof(mi->public_key_path) - 1);
if (lex_mi->get_public_key != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
mi->get_public_key =
(lex_mi->get_public_key == LEX_MASTER_INFO::LEX_MI_ENABLE);
if (lex_mi->ssl_ca)
strmake(mi->ssl_ca, lex_mi->ssl_ca, sizeof(mi->ssl_ca) - 1);
if (lex_mi->ssl_capath)
strmake(mi->ssl_capath, lex_mi->ssl_capath, sizeof(mi->ssl_capath) - 1);
if (lex_mi->tls_version)
strmake(mi->tls_version, lex_mi->tls_version, sizeof(mi->tls_version) - 1);
if (lex_mi->ssl_cert)
strmake(mi->ssl_cert, lex_mi->ssl_cert, sizeof(mi->ssl_cert) - 1);
if (lex_mi->ssl_cipher)
strmake(mi->ssl_cipher, lex_mi->ssl_cipher, sizeof(mi->ssl_cipher) - 1);
if (lex_mi->ssl_key)
strmake(mi->ssl_key, lex_mi->ssl_key, sizeof(mi->ssl_key) - 1);
if (lex_mi->ssl_crl)
strmake(mi->ssl_crl, lex_mi->ssl_crl, sizeof(mi->ssl_crl) - 1);
if (lex_mi->ssl_crlpath)
strmake(mi->ssl_crlpath, lex_mi->ssl_crlpath, sizeof(mi->ssl_crlpath) - 1);
#ifndef HAVE_OPENSSL
if (lex_mi->ssl || lex_mi->ssl_ca || lex_mi->ssl_capath || lex_mi->ssl_cert ||
lex_mi->ssl_cipher || lex_mi->ssl_key || lex_mi->ssl_verify_server_cert ||
lex_mi->ssl_crl || lex_mi->ssl_crlpath || lex_mi->tls_version)
push_warning(thd, Sql_condition::SL_NOTE, ER_SLAVE_IGNORED_SSL_PARAMS,
ER_THD(thd, ER_SLAVE_IGNORED_SSL_PARAMS));
#endif
ret = change_master_set_compression(thd, lex_mi, mi);
if (ret) goto err;
err:
return ret;
}
/**
This function is called if the change master command had at least one
execute option. This function then sets or alters the execute option(s)
given in the command. The receive options are handled in the function
change_receive_options()
- used in change_master().
- Execute threads should be stopped before this function is called.
@param lex_mi structure that holds all change master options given on the
change master command.
@param mi Pointer to Master_info object belonging to the slave's IO
thread.
@return false if the execute options were successfully set and true,
otherwise.
*/
static bool change_execute_options(LEX_MASTER_INFO *lex_mi, Master_info *mi) {
DBUG_TRACE;
if (lex_mi->privilege_checks_username != nullptr ||
lex_mi->privilege_checks_none) {
Relay_log_info::enum_priv_checks_status error{
mi->rli->set_privilege_checks_user(
lex_mi->privilege_checks_username,
lex_mi->privilege_checks_none ? nullptr
: lex_mi->privilege_checks_hostname)};
if (!!error) {
mi->rli->report_privilege_check_error(
ERROR_LEVEL, error, true /* to client*/, mi->rli->get_channel(),
lex_mi->privilege_checks_username, lex_mi->privilege_checks_hostname);
return true;
}
}
if (lex_mi->relay_log_name) {
char relay_log_name[FN_REFLEN];
mi->rli->relay_log.make_log_name(relay_log_name, lex_mi->relay_log_name);
mi->rli->set_group_relay_log_name(relay_log_name);
mi->rli->is_group_master_log_pos_invalid = true;
}
if (lex_mi->relay_log_pos) {
mi->rli->set_group_relay_log_pos(lex_mi->relay_log_pos);
mi->rli->is_group_master_log_pos_invalid = true;
}
if (lex_mi->sql_delay != -1) mi->rli->set_sql_delay(lex_mi->sql_delay);
return false;
}
/**
This function shall issue a deprecation warning if
there are server ids tokenized from the CHANGE MASTER
TO command while @@global.gtid_mode=ON.
*/
static void issue_deprecation_warnings_for_channel(THD *thd) {
LEX_MASTER_INFO *lex_mi = &thd->lex->mi;
/*
Deprecation of GTID_MODE + IGNORE_SERVER_IDS
Generate deprecation warning when user executes CHANGE
MASTER TO IGNORE_SERVER_IDS if GTID_MODE=ON.
*/
enum_gtid_mode gtid_mode = get_gtid_mode(GTID_MODE_LOCK_CHANNEL_MAP);
if (lex_mi->repl_ignore_server_ids.size() > 0 && gtid_mode == GTID_MODE_ON) {
push_warning_printf(thd, Sql_condition::SL_WARNING,
ER_WARN_DEPRECATED_SYNTAX,
ER_THD(thd, ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT),
"CHANGE MASTER TO ... IGNORE_SERVER_IDS='...' "
"(when @@GLOBAL.GTID_MODE = ON)");
}
}
/**
Execute a CHANGE MASTER statement.
Apart from changing the receive/execute configurations/positions,
this function also does the following:
- May leave replicated open temporary table after warning.
- Purges relay logs if no threads running and no relay log file/pos options.
- Delete worker info in mysql.slave_worker_info table if applier not running.
@param thd Pointer to THD object for the client thread executing
the statement.
@param mi Pointer to Master_info object belonging to the slave's
IO thread.
@param lex_mi Lex information with master connection data.
Coming from the an executing statement or set directly
this shall contain connection settings like hostname,
user, password and other settings like the number of
connection retries.
@param preserve_logs If the decision of purging the logs should be always be
false even if no relay log name/position is given to
the method. The preserve_logs parameter will not be
respected when the relay log info repository is not
initialized.
@retval 0 success
@retval !=0 error
*/
int change_master(THD *thd, Master_info *mi, LEX_MASTER_INFO *lex_mi,
bool preserve_logs) {
int error = 0;
/* Do we have at least one receive related (IO thread) option? */
bool have_receive_option = false;
/* Do we have at least one execute related (SQL/coord/worker) option? */
bool have_execute_option = false;
/* If there are no mts gaps, we delete the rows in this table. */
bool mts_remove_worker_info = false;
/* used as a bit mask to indicate running slave threads. */
int thread_mask;
/*
Relay logs are purged only if both receive and execute threads are
stopped before executing CHANGE MASTER and relay_log_file/relay_log_pos
options are not used.
*/
bool need_relay_log_purge = 1;
/*
We want to save the old receive configurations so that we can use them to
print the changes in these configurations (from-to form). This is used in
LogErr() later.
*/
char saved_host[HOSTNAME_LENGTH + 1], saved_bind_addr[HOSTNAME_LENGTH + 1];
uint saved_port = 0;
char saved_log_name[FN_REFLEN];
my_off_t saved_log_pos = 0;
DBUG_TRACE;
/*
CHANGE MASTER command should ignore 'read-only' and 'super_read_only'
options so that it can update 'mysql.slave_master_info' replication
repository tables.
*/
thd->set_skip_readonly_check();
mi->channel_wrlock();
/*
When we change master, we first decide which thread is running and
which is not. We dont want this assumption to break while we change master.
Suppose we decide that receiver thread is running and thus it is
safe to change receive related options in mi. By this time if
the receive thread is started, we may have a race condition between
the client thread and receiver thread.
*/
lock_slave_threads(mi);
/*
Get a bit mask for the slave threads that are running.
Since the third argument is 0, thread_mask after the function
returns stands for running threads.
*/
init_thread_mask(&thread_mask, mi, 0);
/*
change master with master_auto_position=1 requires stopping both
receiver and applier threads. If any slave thread is running,
we report an error.
*/
if (thread_mask) /* If any thread is running */
{
if (lex_mi->auto_position != LEX_MASTER_INFO::LEX_MI_UNCHANGED) {
error = ER_SLAVE_CHANNEL_MUST_STOP;
my_error(ER_SLAVE_CHANNEL_MUST_STOP, MYF(0), mi->get_channel());
goto err;
}
/*
Prior to WL#6120, we imposed the condition that STOP SLAVE is required
before CHANGE MASTER. Since the slave threads die on STOP SLAVE, it was
fine if we purged relay logs.
Now that we do allow CHANGE MASTER with a running receiver/applier thread,
we need to make sure that the relay logs are purged only if both
receiver and applier threads are stopped otherwise we could lose events.
The idea behind purging relay logs if both the threads are stopped is to
keep consistency with the old behavior. If the user/application is doing
a CHANGE MASTER without stopping any one thread, the relay log purge
should be controlled via the 'relay_log_purge' option.
*/
need_relay_log_purge = 0;
}
/*
We cannot specify auto position and set either the coordinates
on master or slave. If we try to do so, an error message is
printed out.
*/
if (lex_mi->log_file_name != nullptr || lex_mi->pos != 0 ||
lex_mi->relay_log_name != nullptr || lex_mi->relay_log_pos != 0) {
if (lex_mi->auto_position == LEX_MASTER_INFO::LEX_MI_ENABLE ||
(lex_mi->auto_position != LEX_MASTER_INFO::LEX_MI_DISABLE &&
mi->is_auto_position())) {
error = ER_BAD_SLAVE_AUTO_POSITION;
my_error(ER_BAD_SLAVE_AUTO_POSITION, MYF(0));
goto err;
}
}
/* CHANGE MASTER TO MASTER_AUTO_POSITION = 1 requires GTID_MODE != OFF */
if (lex_mi->auto_position == LEX_MASTER_INFO::LEX_MI_ENABLE &&
/*
We hold channel_map lock for the duration of the CHANGE MASTER.
This is important since it prevents that a concurrent
connection changes to GTID_MODE=OFF between this check and the
point where AUTO_POSITION is stored in the table and in mi.
*/
get_gtid_mode(GTID_MODE_LOCK_CHANNEL_MAP) == GTID_MODE_OFF) {
error = ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF;
my_error(ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF, MYF(0));
goto err;
}
/* Check if at least one receive option is given on change master */
have_receive_option = have_change_master_receive_option(lex_mi);
/* Check if at least one execute option is given on change master */
have_execute_option =
have_change_master_execute_option(lex_mi, &need_relay_log_purge);
if (need_relay_log_purge && /* If we should purge the logs for this channel */
preserve_logs && /* And we were asked to keep them */
mi->rli->inited) /* And the channel was initialized properly */
{
need_relay_log_purge = false;
}
/* With receiver thread running, we dont allow changing receive options. */
if (have_receive_option && (thread_mask & SLAVE_IO)) {
error = ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP;
my_error(ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP, MYF(0), mi->get_channel());
goto err;
}
/* With an execute thread running, we don't allow changing execute options. */
if (have_execute_option && (thread_mask & SLAVE_SQL)) {
error = ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP;
my_error(ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP, MYF(0), mi->get_channel());
goto err;
}
/*
We need to check if there is an empty master_host. Otherwise
change master succeeds, a master.info file is created containing
empty master_host string and when issuing: start slave; an error
is thrown stating that the server is not configured as slave.
(See BUG#28796).
*/
if (lex_mi->host && !*lex_mi->host) {
error = ER_WRONG_ARGUMENTS;
my_error(ER_WRONG_ARGUMENTS, MYF(0), "MASTER_HOST");
goto err;
}
THD_STAGE_INFO(thd, stage_changing_master);
int thread_mask_stopped_threads;
/*
Before load_mi_and_rli_from_repositories() call, get a bit mask to indicate
stopped threads in thread_mask_stopped_threads. Since the third argguement
is 1, thread_mask when the function returns stands for stopped threads.
*/
init_thread_mask(&thread_mask_stopped_threads, mi, 1);
if (load_mi_and_rli_from_repositories(mi, false, thread_mask_stopped_threads,
need_relay_log_purge)) {
error = ER_MASTER_INFO;
my_error(ER_MASTER_INFO, MYF(0));
goto err;
}
if (have_execute_option && (error = change_execute_options(lex_mi, mi)))
goto err;
if ((thread_mask & SLAVE_SQL) == 0) // If execute threads are stopped
{
if (mi->rli->mts_recovery_group_cnt) {
/*
Change-Master can't be done if there is a mts group gap.
That requires mts-recovery which START SLAVE provides.
*/
DBUG_ASSERT(mi->rli->recovery_parallel_workers);
error = ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS;
my_error(ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS, MYF(0));
goto err;
} else {
/*
Lack of mts group gaps makes Workers info stale regardless of
need_relay_log_purge computation. We set the mts_remove_worker_info
flag here and call reset_workers() later to delete the worker info
in mysql.slave_worker_info table.
*/
if (mi->rli->recovery_parallel_workers) mts_remove_worker_info = true;
}
}
/*
When give a warning?
CHANGE MASTER command is used in three ways:
a) To change a connection configuration but remain connected to
the same master.
b) To change positions in binary or relay log(eg: master_log_pos).
c) To change the master you are replicating from.
We give a warning in cases b and c.
*/
if ((lex_mi->host || lex_mi->port || lex_mi->log_file_name || lex_mi->pos ||
lex_mi->relay_log_name || lex_mi->relay_log_pos) &&
(mi->rli->atomic_channel_open_temp_tables > 0))
push_warning(thd, Sql_condition::SL_WARNING,
ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO,
ER_THD(thd, ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO));
/*
auto_position is the only option that affects both receive
and execute sections of replication. So, this code is kept
outside both if (have_receive_option) and if (have_execute_option)
Here, we check if the auto_position option was used and set the flag
if the slave should connect to the master and look for GTIDs.
*/
if (lex_mi->auto_position != LEX_MASTER_INFO::LEX_MI_UNCHANGED)
mi->set_auto_position(
(lex_mi->auto_position == LEX_MASTER_INFO::LEX_MI_ENABLE));
if (have_receive_option) {
strmake(saved_host, mi->host, HOSTNAME_LENGTH);
strmake(saved_bind_addr, mi->bind_addr, HOSTNAME_LENGTH);
saved_port = mi->port;
strmake(saved_log_name, mi->get_master_log_name(), FN_REFLEN - 1);
saved_log_pos = mi->get_master_log_pos();
if ((error = change_receive_options(thd, lex_mi, mi))) {
goto err;
}
}
/*
If user didn't specify neither host nor port nor any log name nor any log
pos, i.e. he specified only user/password/master_connect_retry,
master_delay, he probably wants replication to resume from where it had
left, i.e. from the coordinates of the **SQL** thread (imagine the case
where the I/O is ahead of the SQL; restarting from the coordinates of the
I/O would lose some events which is probably unwanted when you are just
doing minor changes like changing master_connect_retry). Note: coordinates
of the SQL thread must be read before the block which resets them.
*/
if (need_relay_log_purge) {
/*
A side-effect is that if only the I/O thread was started, this thread may
restart from ''/4 after the CHANGE MASTER. That's a minor problem (it is a
much more unlikely situation than the one we are fixing here).
*/
if (!lex_mi->host && !lex_mi->port && !lex_mi->log_file_name &&
!lex_mi->pos) {
/*
Sometimes mi->rli->master_log_pos == 0 (it happens when the SQL thread
is not initialized), so we use a max(). What happens to
mi->rli->master_log_pos during the initialization stages of replication
is not 100% clear, so we guard against problems using max().
*/
mi->set_master_log_pos(max<ulonglong>(
BIN_LOG_HEADER_SIZE, mi->rli->get_group_master_log_pos()));
mi->set_master_log_name(mi->rli->get_group_master_log_name());
}
}
if (have_receive_option)
LogErr(SYSTEM_LEVEL, ER_SLAVE_CHANGE_MASTER_TO_EXECUTED,
mi->get_for_channel_str(true), saved_host, saved_port,
saved_log_name, (ulong)saved_log_pos, saved_bind_addr, mi->host,
mi->port, mi->get_master_log_name(), (ulong)mi->get_master_log_pos(),
mi->bind_addr);
/* If the receiver is stopped, flush master_info to disk. */
if ((thread_mask & SLAVE_IO) == 0 && flush_master_info(mi, true)) {
error = ER_RELAY_LOG_INIT;
my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush master info file");
goto err;
}
if ((thread_mask & SLAVE_SQL) == 0) /* Applier module is not executing */
{
if (need_relay_log_purge) {
/*
'if (need_relay_log_purge)' implicitly means that all slave threads are
stopped and there is no use of relay_log_file/relay_log_pos options.
We need not check these here again.
*/
/* purge_relay_log() returns pointer to an error message here. */
const char *errmsg = nullptr;
/*
purge_relay_log() assumes that we have run_lock and no slave threads
are running.
*/
THD_STAGE_INFO(thd, stage_purging_old_relay_logs);
if (mi->rli->purge_relay_logs(thd, &errmsg)) {
error = ER_RELAY_LOG_FAIL;
my_error(ER_RELAY_LOG_FAIL, MYF(0), errmsg);
goto err;
}
/*
Coordinates in rli were spoilt by purge_relay_logs(),
so restore them to good values. If we left them to ''/0, that would
work. But that would fail in the case of 2 successive CHANGE MASTER
(without a START SLAVE in between): because first one would set the
coords in mi to the good values of those in rli, then set those i>n rli
to ''/0, then second CHANGE MASTER would set the coords in mi to those
of rli, i.e. to ''/0: we have lost all copies of the original good
coordinates. That's why we always save good coords in rli.
*/
mi->rli->set_group_master_log_pos(mi->get_master_log_pos());
mi->rli->set_group_master_log_name(mi->get_master_log_name());
DBUG_PRINT("info", ("master_log_pos: %llu", mi->get_master_log_pos()));
} else {
const char *errmsg = nullptr;
if (mi->rli->is_group_relay_log_name_invalid(&errmsg)) {
error = ER_RELAY_LOG_INIT;
my_error(ER_RELAY_LOG_INIT, MYF(0), errmsg);
goto err;
}
}
char *var_group_master_log_name =
const_cast<char *>(mi->rli->get_group_master_log_name());
if (!var_group_master_log_name[0]) // uninitialized case
mi->rli->set_group_master_log_pos(0);
mi->rli->abort_pos_wait++; /* for MASTER_POS_WAIT() to abort */
/* Clear the errors, for a clean start */
mi->rli->clear_error();
if (mi->rli->workers_array_initialized) {
for (size_t i = 0; i < mi->rli->get_worker_count(); i++) {
mi->rli->get_worker(i)->clear_error();
}
}
/*
If we don't write new coordinates to disk now, then old will remain in
relay-log.info until START SLAVE is issued; but if mysqld is shutdown
before START SLAVE, then old will remain in relay-log.info, and will be
the in-memory value at restart (thus causing errors, as the old relay log
does not exist anymore).
Notice that the rli table is available exclusively as slave is not
running.
*/
if (mi->rli->flush_info(true)) {
error = ER_RELAY_LOG_INIT;
my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush relay info file.");
goto err;
}
} /* end 'if (thread_mask & SLAVE_SQL == 0)' */
if (mts_remove_worker_info)
if (Rpl_info_factory::reset_workers(mi->rli)) {
error = ER_MTS_RESET_WORKERS;
my_error(ER_MTS_RESET_WORKERS, MYF(0));
goto err;
}
err:
unlock_slave_threads(mi);
mi->channel_unlock();
return error;
}
/**
This function is first called when the Master_info object
corresponding to a channel in a multisourced slave does not
exist. But before a new channel is created, certain
conditions have to be met. The below function apriorily
checks if all such conditions are met. If all the
conditions are met then it creates a channel i.e
mi<->rli
@param[in,out] mi When new {mi,rli} are created,
the reference is stored in *mi
@param[in] channel The channel on which the change
master was introduced.
*/
int add_new_channel(Master_info **mi, const char *channel) {
DBUG_TRACE;
int error = 0;
Ident_name_check ident_check_status;
/*
Refuse to create a new channel if the repositories does not support this.
*/
if (opt_mi_repository_id == INFO_REPOSITORY_FILE ||
opt_rli_repository_id == INFO_REPOSITORY_FILE) {
LogErr(ERROR_LEVEL,
ER_RPL_SLAVE_NEW_MASTER_INFO_NEEDS_REPOS_TYPE_OTHER_THAN_FILE);
error = ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY;
my_error(ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY, MYF(0));
goto err;
}
/*
Return if max num of replication channels exceeded already.
*/
if (!channel_map.is_valid_channel_count()) {
error = ER_SLAVE_MAX_CHANNELS_EXCEEDED;
my_error(ER_SLAVE_MAX_CHANNELS_EXCEEDED, MYF(0));
goto err;
}
/*
Now check the sanity of the channel name. It's length etc. The channel
identifier is similar to table names. So, use check_table_function.
*/
if (channel) {
ident_check_status = check_table_name(channel, strlen(channel));
} else
ident_check_status = Ident_name_check::WRONG;
if (ident_check_status != Ident_name_check::OK) {
error = ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG;
my_error(ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG, MYF(0));
goto err;
}
if (!((*mi) = Rpl_info_factory::create_mi_and_rli_objects(
opt_mi_repository_id, opt_rli_repository_id, channel, false,
&channel_map))) {
error = ER_MASTER_INFO;
my_error(ER_MASTER_INFO, MYF(0));
goto err;
}
err:
return error;
}
/**
Method used to check if the user is trying to update any other option for
the change master apart from the MASTER_USER and MASTER_PASSWORD.
In case user tries to update any other parameter apart from these two,
this method will return error.
@param lex_mi structure that holds all change master options given on
the change master command.
@retval true - The CHANGE MASTER is updating a unsupported parameter for the
recovery channel.
@retval false - Everything is fine. The CHANGE MASTER can execute with the
given option(s) for the recovery channel.
*/
static bool is_invalid_change_master_for_group_replication_recovery(
const LEX_MASTER_INFO *lex_mi) {
DBUG_TRACE;
bool have_extra_option_received = false;
/* Check if *at least one* receive/execute option is given on change master
* command*/
if (lex_mi->host || lex_mi->log_file_name || lex_mi->pos ||
lex_mi->bind_addr || lex_mi->port || lex_mi->connect_retry ||
lex_mi->server_id ||
lex_mi->auto_position != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->retry_count_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl_key || lex_mi->ssl_cert || lex_mi->ssl_ca ||
lex_mi->ssl_capath || lex_mi->tls_version || lex_mi->ssl_cipher ||
lex_mi->ssl_crl || lex_mi->ssl_crlpath ||
lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE ||
lex_mi->relay_log_name || lex_mi->relay_log_pos ||
lex_mi->sql_delay != -1 || lex_mi->public_key_path ||
lex_mi->get_public_key != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->zstd_compression_level || lex_mi->compression_algorithm)
have_extra_option_received = true;
return have_extra_option_received;
}
/**
Method used to check if the user is trying to update any other option for
the change master apart from the PRIVILEGE_CHECKS_USER.
In case user tries to update any other parameter apart from this one, this
method will return error.
@param lex_mi structure that holds all change master options given on
the change master command.
@retval true - The CHANGE MASTER is updating a unsupported parameter for the
recovery channel.
@retval false - Everything is fine. The CHANGE MASTER can execute with the
given option(s) for the recovery channel.
*/
static bool is_invalid_change_master_for_group_replication_applier(
const LEX_MASTER_INFO *lex_mi) {
DBUG_TRACE;
bool have_extra_option_received = false;
/* Check if *at least one* receive/execute option is given on change master
* command*/
if (lex_mi->host || lex_mi->user || lex_mi->password ||
lex_mi->log_file_name || lex_mi->pos || lex_mi->bind_addr ||
lex_mi->port || lex_mi->connect_retry || lex_mi->server_id ||
lex_mi->auto_position != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->retry_count_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->ssl_key || lex_mi->ssl_cert || lex_mi->ssl_ca ||
lex_mi->ssl_capath || lex_mi->tls_version || lex_mi->ssl_cipher ||
lex_mi->ssl_crl || lex_mi->ssl_crlpath ||
lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE ||
lex_mi->relay_log_name || lex_mi->relay_log_pos ||
lex_mi->sql_delay != -1 || lex_mi->public_key_path ||
lex_mi->get_public_key != LEX_MASTER_INFO::LEX_MI_UNCHANGED ||
lex_mi->zstd_compression_level || lex_mi->compression_algorithm)
have_extra_option_received = true;
return have_extra_option_received;
}
/**
Entry point for the CHANGE MASTER command. Function
decides to create a new channel or create an existing one.
@param[in] thd the client thread that issued the command.
@return
@retval true fail
@retval false success.
*/
bool change_master_cmd(THD *thd) {
DBUG_TRACE;
Master_info *mi = nullptr;
LEX *lex = thd->lex;
bool res = false;
channel_map.wrlock();
/* The slave must have been initialized to allow CHANGE MASTER statements */
if (!is_slave_configured()) {
my_error(ER_SLAVE_CONFIGURATION, MYF(0));
res = true;
goto err;
}
// If the chosen name is for group_replication_applier channel we allow the
// channel creation based on the check as to which field is being updated.
if (channel_map.is_group_replication_channel_name(lex->mi.channel, true)) {
LEX_MASTER_INFO *lex_mi = &thd->lex->mi;
if (is_invalid_change_master_for_group_replication_applier(lex_mi)) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"CHANGE MASTER with the given parameters", lex->mi.channel);
res = true;
goto err;
}
}
// If the channel being used is group_replication_recovery we allow the
// channel creation based on the check as to which field is being updated.
if (channel_map.is_group_replication_channel_name(lex->mi.channel) &&
!channel_map.is_group_replication_channel_name(lex->mi.channel, true)) {
LEX_MASTER_INFO *lex_mi = &thd->lex->mi;
if (is_invalid_change_master_for_group_replication_recovery(lex_mi)) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"CHANGE MASTER with the given parameters", lex->mi.channel);
res = true;
goto err;
}
}
/*
Error out if number of replication channels are > 1 if FOR CHANNEL
clause is not provided in the CHANGE MASTER command.
*/
if (!lex->mi.for_channel && channel_map.get_num_instances() > 1) {
my_error(ER_SLAVE_MULTIPLE_CHANNELS_CMD, MYF(0));
res = true;
goto err;
}
/* Get the Master_info of the channel */
mi = channel_map.get_mi(lex->mi.channel);
/* create a new channel if doesn't exist */
if (!mi && strcmp(lex->mi.channel, channel_map.get_default_channel())) {
/* The mi will be returned holding mi->channel_lock for writing */
if (add_new_channel(&mi, lex->mi.channel)) goto err;
}
if (mi) {
bool configure_filters = !Master_info::is_configured(mi);
if (!(res = change_master(thd, mi, &thd->lex->mi))) {
/*
If the channel was just created or not configured before this
"CHANGE MASTER", we need to configure rpl_filter for it.
*/
if (configure_filters) {
if ((res = Rpl_info_factory::configure_channel_replication_filters(
mi->rli, lex->mi.channel)))
goto err;
}
/*
Issuing deprecation warnings after the change (we make
sure that we don't issue warning if there is an error).
*/
issue_deprecation_warnings_for_channel(thd);
my_ok(thd);
}
} else {
/*
Even default channel does not exist. So issue a previous
backward compatible error message (till 5.6).
@TODO: This error message shall be improved.
*/
my_error(ER_SLAVE_CONFIGURATION, MYF(0));
}
err:
channel_map.unlock();
return res;
}
/**
Check if there is any slave SQL config conflict.
@param[in] rli The slave's rli object.
@return 0 is returned if there is no conflict, otherwise 1 is returned.
*/
static int check_slave_sql_config_conflict(const Relay_log_info *rli) {
int channel_mts_submode, slave_parallel_workers;
if (rli) {
channel_mts_submode = rli->channel_mts_submode;
slave_parallel_workers = rli->opt_slave_parallel_workers;
} else {
/*
When the slave is first initialized, we collect the values from the
command line options
*/
channel_mts_submode = mts_parallel_option;
slave_parallel_workers = opt_mts_slave_parallel_workers;
}
if (opt_slave_preserve_commit_order && slave_parallel_workers > 0) {
if (channel_mts_submode == MTS_PARALLEL_TYPE_DB_NAME) {
my_error(ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER, MYF(0),
"when slave_parallel_type is DATABASE");
return ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER;
}
if ((!opt_bin_log || !opt_log_slave_updates) &&
channel_mts_submode == MTS_PARALLEL_TYPE_LOGICAL_CLOCK) {
my_error(ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER, MYF(0),
"unless both log_bin and log_slave_updates are enabled");
return ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER;
}
}
if (rli) {
const char *channel = const_cast<Relay_log_info *>(rli)->get_channel();
if (slave_parallel_workers > 0 &&
(channel_mts_submode != MTS_PARALLEL_TYPE_LOGICAL_CLOCK ||
(channel_mts_submode == MTS_PARALLEL_TYPE_LOGICAL_CLOCK &&
!opt_slave_preserve_commit_order)) &&
channel_map.is_group_replication_channel_name(channel, true)) {
my_error(ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED, MYF(0),
"START SLAVE SQL_THREAD when SLAVE_PARALLEL_WORKERS > 0 "
"and SLAVE_PARALLEL_TYPE != LOGICAL_CLOCK "
"or SLAVE_PRESERVE_COMMIT_ORDER != ON",
channel);
return ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED;
}
}
return 0;
}
/**
@} (end of group Replication)
*/