Skip to content

Commit

Permalink
PXC-4433: Create file with current MySQL state
Browse files Browse the repository at this point in the history
https://perconadev.atlassian.net/browse/PXC-4433

server.state file containing current server state is being created
in data directory if the server is started with
create_server_state_file=ON option.
create_server_state_file default value is OFF.
  • Loading branch information
kamil-holubicki committed Sep 12, 2024
1 parent 77bee08 commit 19c3bd9
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 3 deletions.
1 change: 1 addition & 0 deletions sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ SET(SQL_SHARED_SOURCES
partition_info.cc
partitioning/partition_handler.cc
persisted_variable.cc
server_status_file.cc
protocol_classic.cc
psi_memory_key.cc
psi_memory_resource.cc
Expand Down
2 changes: 2 additions & 0 deletions sql/dd/impl/bootstrap/bootstrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
#include "sql/mdl.h"
#include "sql/mysqld.h"
#include "sql/sd_notify.h" // sysd::notify
#include "sql/server_status_file.h"
#include "sql/sql_zip_dict.h"
#include "sql/thd_raii.h"
#include "storage/perfschema/pfs_dd_version.h" // PFS_DD_VERSION
Expand Down Expand Up @@ -1408,6 +1409,7 @@ bool initialize_dd_properties(THD *thd) {
if (bootstrap::DD_bootstrap_ctx::instance().is_dd_upgrade()) {
LogErr(SYSTEM_LEVEL, ER_DD_UPGRADE, actual_dd_version, dd::DD_VERSION);
sysd::notify("STATUS=Data Dictionary upgrade in progress\n");
Server_status_file::set_status(Server_status_file::Status::UPGRADING);
}
if (bootstrap::DD_bootstrap_ctx::instance().is_server_upgrade()) {
// This condition is hit only if upgrade has been skipped before
Expand Down
8 changes: 6 additions & 2 deletions sql/dd/impl/upgrade/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@
#include "sql/dd/types/tablespace.h"
#include "sql/dd_sp.h" // prepare_sp_chistics_from_dd_routine
#include "sql/sd_notify.h" // sysd::notify
#include "sql/sp.h" // Stored_routine_creation_ctx
#include "sql/sp_head.h" // sp_head
#include "sql/server_status_file.h"
#include "sql/sp.h" // Stored_routine_creation_ctx
#include "sql/sp_head.h" // sp_head
#include "sql/sql_base.h"
#include "sql/sql_prepare.h"
#include "sql/strfunc.h"
Expand Down Expand Up @@ -977,11 +978,14 @@ bool upgrade_system_schemas(THD *thd) {
LogErr(SYSTEM_LEVEL, ER_SERVER_DOWNGRADE_STATUS, server_version,
MYSQL_VERSION_ID, "started");
sysd::notify("STATUS=Server downgrade in progress\n");
Server_status_file::set_status(Server_status_file::Status::DOWNGRADING);

/* purecov: end */
} else {
LogErr(SYSTEM_LEVEL, ER_SERVER_UPGRADE_STATUS, server_version,
MYSQL_VERSION_ID, "started");
sysd::notify("STATUS=Server upgrade in progress\n");
Server_status_file::set_status(Server_status_file::Status::UPGRADING);
}

bootstrap_error_handler.set_log_error(false);
Expand Down
2 changes: 2 additions & 0 deletions sql/dd/upgrade_57/upgrade.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
#include "sql/log.h" // sql_print_warning
#include "sql/mysqld.h" // key_file_sdi
#include "sql/sd_notify.h" // sysd::notify
#include "sql/server_status_file.h"
#include "sql/sql_class.h" // THD
#include "sql/sql_list.h"
#include "sql/sql_plugin.h"
Expand Down Expand Up @@ -904,6 +905,7 @@ bool do_pre_checks_and_initialize_dd(THD *thd) {
*/
LogErr(SYSTEM_LEVEL, ER_DD_UPGRADE_START);
sysd::notify("STATUS=Data Dictionary upgrade from MySQL 5.7 in progress\n");
Server_status_file::set_status(Server_status_file::Status::UPGRADING);
}

/*
Expand Down
14 changes: 13 additions & 1 deletion sql/mysqld.cc
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,7 @@ MySQL clients support the protocol:
#include "sql/range_optimizer/range_optimizer.h" // range_optimizer_init
#include "sql/replication.h" // thd_enter_cond
#include "sql/resourcegroups/resource_group_mgr.h" // init, post_init
#include "sql/server_status_file.h"
#include "sql/sql_profile.h"
#ifdef _WIN32
#include "sql/restart_monitor_win.h"
Expand Down Expand Up @@ -1448,6 +1449,8 @@ bool avoid_temporal_upgrade;

bool persisted_globals_load = true;

bool create_server_state_file = false;

bool opt_keyring_operations = true;

bool opt_table_encryption_privilege_check = false;
Expand Down Expand Up @@ -2773,6 +2776,7 @@ static void unireg_abort(int exit_code) {
#endif /* WITH_WSREP */
DBUG_TRACE;

Server_status_file::set_status(Server_status_file::Status::STOPPING);
if (errno) {
sysd::notify("ERRNO=", errno, "\n");
}
Expand Down Expand Up @@ -2899,6 +2903,8 @@ static void mysqld_exit(int exit_code) {
close_service_status_pipe_in_mysqld();
#endif // _WIN32

Server_status_file::set_status(Server_status_file::Status::STOPPED);

exit(exit_code); /* purecov: inspected */
}

Expand Down Expand Up @@ -8424,6 +8430,9 @@ int mysqld_main(int argc, char **argv)
strmake(mysql_real_data_home, get_relative_path(MYSQL_DATADIR),
sizeof(mysql_real_data_home) - 1);

Server_status_file::init(&argc, &argv);
Server_status_file::set_status(Server_status_file::Status::INITIALIZING);

/* Must be initialized early for comparison of options name */
system_charset_info = &my_charset_utf8mb3_general_ci;

Expand Down Expand Up @@ -9468,9 +9477,12 @@ int mysqld_main(int argc, char **argv)
}

mysqld_socket_acceptor->check_and_spawn_admin_connection_handler_thread();
mysqld_socket_acceptor->connection_event_loop();
Server_status_file::set_status(Server_status_file::Status::READY);
mysqld_socket_acceptor->connection_event_loop(); // KH: end of limbo state
#endif /* _WIN32 */
server_operational_state = SERVER_SHUTTING_DOWN;
Server_status_file::set_status(Server_status_file::Status::STOPPING);

sysd::notify("STOPPING=1\nSTATUS=Server shutdown in progress\n");

DBUG_PRINT("info", ("No longer listening for incoming connections"));
Expand Down
1 change: 1 addition & 0 deletions sql/mysqld.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ extern ulonglong utility_user_privileges;
extern char *utility_user_dynamic_privileges;

extern bool persisted_globals_load;
extern bool create_server_state_file;
extern bool opt_keyring_operations;
extern bool opt_table_encryption_privilege_check;
extern char *opt_keyring_migration_user;
Expand Down
191 changes: 191 additions & 0 deletions sql/server_status_file.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#include "server_status_file.h"

#include <assert.h>
#include <sys/file.h>
#include <chrono>
#include <filesystem>
#include <map>
#include <string>
#include <thread>

#include "my_alloc.h"
#include "my_getopt.h"
#include "my_io.h"

#include "mysqld_error.h"

#include <mysql/components/services/log_builtins.h>
#include <sql/server_component/log_builtins_internal.h>
#include "sql/mysqld.h"
#include "sql/raii/sentry.h" // raii::Sentry

namespace fs = std::filesystem;
using namespace std::chrono_literals;

namespace Server_status_file {

static constexpr auto path_separator =
std::filesystem::path::preferred_separator;
static const std::string m_server_state_filename("server.state");
static bool m_create_server_state_file = false;
static std::string m_server_state_file_path;

static std::map<Status, std::string> status2string{
{Status::INITIALIZING, "INITIALIZING"},
{Status::DOWNGRADING, "DOWNGRADING"},
{Status::UPGRADING, "UPGRADING"},
{Status::READY, "READY"},
{Status::STOPPING, "STOPPING"},
{Status::STOPPED, "STOPPED"},
{Status::UNKNOWN, "UNKNOWN"}};

void log(const char *msg) {
/* It is not nice to call from internals, but if we are just
deinitializing the server, log may be disabled already. */

if (log_builtins_started()) {
LogErr(WARNING_LEVEL, ER_LOG_PRINTF_MSG, msg);
}
}

static int delete_status_file() {
return unlink(m_server_state_file_path.c_str());
}

/* Try to acquire desired lock on file within a specified time.
Returns 0 in case of success, -1 otherwise. */
static int flockt(int fd, int operation, std::chrono::milliseconds timeout) {
static const std::chrono::milliseconds loop_wait_time = 500ms;
std::chrono::milliseconds wait_total = 0ms;

while (flock(fd, operation | LOCK_NB) == -1) {
if (errno == EWOULDBLOCK) {
if (wait_total >= timeout) {
// failed to lock
return -1;
}
std::this_thread::sleep_for(loop_wait_time);
wait_total += loop_wait_time;
} else {
return -1;
}
}
return 0;
}

static const std::string &get_status_string(Status status) {
if (status2string.find(status) != status2string.end()) {
return status2string[status];
}
return status2string[Status::UNKNOWN];
}

/* Store string representation of status to the server.status file.
If the file does not exist, it is created.
If the file exists, try to acquire exclusive lock with 5s timeout.
Regardless of the lock being acquired or not store the status in file. */
void set_status(Status status) {
if (!m_create_server_state_file) {
return;
}

int fd = open(m_server_state_file_path.c_str(), O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
log("Cannot open server.status file");
return;
}
// close() will release the file lock if acquired
raii::Sentry file_close{[fd] { close(fd); }};

/* Let's try acquire exclusive lock on server.status file.
We will give up after a short time. It is to avoid the case when someone
acquires a shared lock on this file without releasing it. That would block
the server forever.
We will update the status file anyway. In worst case the reader will
receive garbage if it reads in the middle of the write, but it is up to
reader to not lock for a long time. */
int res = flockt(fd, LOCK_EX, 5000ms);
if (res) {
log("Failed to acquire exclusive lock on server.status file. Reporting "
"status anyway.");
}

// truncate only after acquiring the lock
res = ftruncate(fd, 0);
if (res) {
log("Failed to truncate server.status file. Trying to delete.");
if (delete_status_file()) {
log("Failed to delete server.status file");
return;
}
}

const std::string &status_str = get_status_string(status);
size_t s = write(fd, status_str.c_str(), status_str.length());
if (s != status_str.length()) {
log("Error writing data to server.status file");
}

// file is closed by file_close Sentry
}

int init(int *argc, char ***argv) {
int temp_argc = *argc;
MEM_ROOT alloc{PSI_NOT_INSTRUMENTED, 512};
char *ptr, **res, *datadir = nullptr;
char local_datadir_buffer[FN_REFLEN] = {0};

/* mysql_real_data_home_ptr is initialized in init_common_variables().
We want to store the status file much earlier. Similar code exists in
Persisted_variables_cache::init() */
my_option persist_options[] = {
{"create_server_state_file", 0, "", &m_create_server_state_file,
&m_create_server_state_file, nullptr, GET_BOOL, OPT_ARG, 1, 0, 0,
nullptr, 0, nullptr},
{"datadir", 0, "", &datadir, nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0,
nullptr, 0, nullptr},
{nullptr, 0, nullptr, nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0,
0, nullptr, 0, nullptr}};

/* create temporary args list and pass it to handle_options */
if (!(ptr =
(char *)alloc.Alloc(sizeof(alloc) + (*argc + 1) * sizeof(char *))))
return 1;
memset(ptr, 0, (sizeof(char *) * (*argc + 1)));
res = (char **)(ptr);
memcpy((uchar *)res, (char *)(*argv), (*argc) * sizeof(char *));

my_getopt_skip_unknown = true;
if (my_handle_options(&temp_argc, &res, persist_options, nullptr, nullptr,
true)) {
alloc.Clear();
return 1;
}
my_getopt_skip_unknown = false;
alloc.Clear();

if (!datadir) {
// mysql_real_data_home must be initialized at this point
assert(mysql_real_data_home[0]);
/*
mysql_home_ptr should also be initialized at this point.
See calculate_mysql_home_from_my_progname() for details
*/
assert(mysql_home_ptr && mysql_home_ptr[0]);
convert_dirname(local_datadir_buffer, mysql_real_data_home, NullS);
(void)my_load_path(local_datadir_buffer, local_datadir_buffer,
mysql_home_ptr);
datadir = local_datadir_buffer;
}

m_server_state_file_path =
std::string(datadir) + path_separator + m_server_state_filename;

// Ignore the error. Server starts and it is possible there is no
// server.status file
delete_status_file();

return 0;
}

} // namespace Server_status_file
19 changes: 19 additions & 0 deletions sql/server_status_file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef SERVER_STATUS_FILE_INCLUDED
#define SERVER_STATUS_FILE_INCLUDED

namespace Server_status_file {
enum Status {
INITIALIZING,
DOWNGRADING,
UPGRADING,
READY,
STOPPING,
STOPPED,
UNKNOWN
};

int init(int *argc, char ***argv);
void set_status(Status status);
} // namespace Server_status_file

#endif /* SERVER_STATUS_FILE_INCLUDED */
12 changes: 12 additions & 0 deletions sql/signal_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "my_time.h"
#include "mysql_version.h"
#include "sql/mysqld.h"
#include "sql/server_status_file.h"
#include "sql/sql_class.h"
#include "sql/sql_const.h"

Expand Down Expand Up @@ -227,9 +228,11 @@ void print_fatal_signal(int sig) {
extern "C" void handle_fatal_signal(int sig) {
if (s_handler_being_processed) {
my_safe_printf_stderr("Fatal " SIGNAL_FMT " while backtracing\n", sig);
Server_status_file::set_status(Server_status_file::Status::STOPPED);
_exit(MYSQLD_FAILURE_EXIT); /* Quit without running destructors */
}

Server_status_file::set_status(Server_status_file::Status::STOPPING);
s_handler_being_processed = true;

#ifdef WITH_WSREP
Expand All @@ -246,6 +249,10 @@ extern "C" void handle_fatal_signal(int sig) {

buffered_error_log.write_to_disk();

// This is the last chance to report in the file.
// Below coredump will exit the process
Server_status_file::set_status(Server_status_file::Status::STOPPED);

if ((test_flags & TEST_CORE_ON_SIGNAL) != 0) {
#if HAVE_LIBCOREDUMPER
if (opt_libcoredumper) {
Expand Down Expand Up @@ -290,6 +297,9 @@ void my_server_abort() {
static std::atomic_bool abort_processing{false};
/* Broadcast that this thread wants to print the signal info. */
aborts_pending++;

Server_status_file::set_status(Server_status_file::Status::STOPPING);

/*
Wait for the exclusive right to print the signal info. This assures the
output is not interleaved.
Expand Down Expand Up @@ -319,6 +329,8 @@ void my_server_abort() {
while (abort_processing.exchange(true)) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

Server_status_file::set_status(Server_status_file::Status::STOPPED);
abort();
}
/*
Expand Down
Loading

0 comments on commit 19c3bd9

Please sign in to comment.