From 713fd543be1a09d1776ec367d109d64460937076 Mon Sep 17 00:00:00 2001 From: Yura Sorokin Date: Tue, 4 Jun 2024 02:15:54 +0200 Subject: [PATCH] PS-9244 fix: binlog_server fails to resume copying binlog file after server restart (#50) https://perconadev.atlassian.net/browse/PS-9244 Binlog Server Utility now properly handles 'STOP' events which may be written to the binlog when MySQL server shuts down. Added specializations of the 'binsrv::event::generic_body_impl<>' and 'binsrv::event::generic_post_header_impl<>' for 'code_type::stop' (both implemented via 'redirect_type' to 'empty_body' / 'empty_post_header'). 'STOP' is now a known event type for the 'binsrv::event::event' class and its inner body / post-header variants. Updated 'binsrv::reader_context'- it is now based on the following state machine: (ROTATE(artificial) FORMAT_DESCRIPTION * (ROTATE|STOP))* 'process_binlog_event()' in the main application now calls 'close_binlog()' not only on real (non-artificial) 'ROTATE' events but on 'STOP' events as well. Introduced new 'binlog_streaming.pull_mode' MTR test case which checks if the Binlog Server Utility properly handles reconnects and resuming operation in background ('pull') mode. Common functionality from the 'binlog_streaming.binsrv' and 'binlog_streaming.pull_mode' extracted into the following include files: * 'identify_storage_backend.inc' - for identifying whether a local filesystem ('file') or a AWS S3 ('s3') storage backend should be used. * 'set_up_binsrv_environment.inc' - for creating storage directory, configuring log file and generating JSON configuration. * 'tear_down_binsrv_environment.inc' - for cleaning up data directory and removing log file along with JSON configuration file. Temporarily disabled running MTR under "Clang 17 ASan" in GitHub Workflows because of the "-stdlib=libc++ -fsanitize=address" alloc-dealloc-mismatch issue (https://github.com/llvm/llvm-project/issues/59432). Hopefully, the problem will be fixed in clang-18. --- .github/workflows/cmake.yml | 14 +- CMakeLists.txt | 6 + .../include/diff_with_storage_object.inc | 20 +-- .../include/identify_storage_backend.inc | 23 +++ .../include/set_up_binsrv_environment.inc | 94 +++++++++++ .../include/tear_down_binsrv_environment.inc | 18 ++ mtr/binlog_streaming/r/binsrv.result | 7 +- mtr/binlog_streaming/r/pull_mode.result | 55 ++++++ mtr/binlog_streaming/t/binsrv.test | 148 ++--------------- mtr/binlog_streaming/t/pull_mode.test | 156 ++++++++++++++++++ src/app.cpp | 21 +-- src/binsrv/event/empty_body.hpp | 2 +- src/binsrv/event/empty_body_fwd.hpp | 2 +- src/binsrv/event/event.hpp | 2 + src/binsrv/event/reader_context.cpp | 13 +- src/binsrv/event/stop_body_impl.hpp | 32 ++++ src/binsrv/event/stop_body_impl_fwd.hpp | 28 ++++ src/binsrv/event/stop_post_header_impl.hpp | 32 ++++ .../event/stop_post_header_impl_fwd.hpp | 28 ++++ 19 files changed, 531 insertions(+), 170 deletions(-) create mode 100644 mtr/binlog_streaming/include/identify_storage_backend.inc create mode 100644 mtr/binlog_streaming/include/set_up_binsrv_environment.inc create mode 100644 mtr/binlog_streaming/include/tear_down_binsrv_environment.inc create mode 100644 mtr/binlog_streaming/r/pull_mode.result create mode 100644 mtr/binlog_streaming/t/pull_mode.test create mode 100644 src/binsrv/event/stop_body_impl.hpp create mode 100644 src/binsrv/event/stop_body_impl_fwd.hpp create mode 100644 src/binsrv/event/stop_post_header_impl.hpp create mode 100644 src/binsrv/event/stop_post_header_impl_fwd.hpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 6ecf7f2..377fd5c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -96,7 +96,7 @@ jobs: aws_cmake_flags: "-DCMAKE_CXX_FLAGS_INIT=-stdlib=libc++", boost_cmake_flags: "-DCMAKE_CXX_FLAGS_INIT=-stdlib=libc++", label: "Debug-clang17", - run_clang_tidy: true, + run_clang_tidy: true } - { name: "Clang 17 RelWithDebInfo", @@ -107,7 +107,7 @@ jobs: aws_cmake_flags: "-DCMAKE_CXX_FLAGS_INIT=-stdlib=libc++", boost_cmake_flags: "-DCMAKE_CXX_FLAGS_INIT=-stdlib=libc++", label: "RelWithDebInfo-clang17", - run_clang_tidy: true, + run_clang_tidy: true } - { name: "Clang 17 ASan", @@ -118,9 +118,13 @@ jobs: aws_cmake_flags: "-DCMAKE_CXX_FLAGS_INIT=-stdlib=libc++ -DENABLE_ADDRESS_SANITIZER=ON", boost_cmake_flags: "-DCMAKE_CXX_FLAGS_INIT=\"-stdlib=libc++ -fsanitize=address\"", sanitizer_cmake_flags: "-DWITH_ASAN=ON", - label: "ASan-clang17", - run_mtr: true, - mtr_options: "--sanitize" + label: "ASan-clang17" + # TODO: re-enable running MTR under "Clang 17 ASan" + # run_mtr: true, + # mtr_options: "--sanitize" + # when "-stdlib=libc++ -fsanitize=address" alloc-dealloc-mismatch issue is fixed + # (https://github.com/llvm/llvm-project/issues/59432) + # or CI is upgraded to Clang 18 } steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 7571045..0eb5266 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,12 @@ set(source_files src/binsrv/event/rotate_post_header_impl.hpp src/binsrv/event/rotate_post_header_impl.cpp + src/binsrv/event/stop_body_impl_fwd.hpp + src/binsrv/event/stop_body_impl.hpp + + src/binsrv/event/stop_post_header_impl_fwd.hpp + src/binsrv/event/stop_post_header_impl.hpp + src/binsrv/event/unknown_body_fwd.hpp src/binsrv/event/unknown_body.hpp src/binsrv/event/unknown_body.cpp diff --git a/mtr/binlog_streaming/include/diff_with_storage_object.inc b/mtr/binlog_streaming/include/diff_with_storage_object.inc index 0b06825..bee576d 100644 --- a/mtr/binlog_streaming/include/diff_with_storage_object.inc +++ b/mtr/binlog_streaming/include/diff_with_storage_object.inc @@ -2,18 +2,18 @@ # Compares a file on a local filesystem with an object from the backend storage. # # Usage: -# --let $storage_backend = file -# --let $local_file = $MYSQL_TMP_DIR/first -# --let $storage_object = $MYSQL_TMP_DIR/second -# --source diff_with_storage_object.inc +# --let $storage_backend = file +# --let $local_file = $MYSQL_TMP_DIR/first +# --let $storage_object = $MYSQL_TMP_DIR/second +# --source diff_with_storage_object.inc # # or -# --let $aws_cli = AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... aws -# --let $storage_backend = s3 -# --let $local_file = $MYSQL_TMP_DIR/first -# --let $aws_s3_bucket = my-bucket -# --let $storage_object = /vault/second -# --source diff_with_storage_object.inc +# --let $aws_cli = AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... aws +# --let $storage_backend = s3 +# --let $local_file = $MYSQL_TMP_DIR/first +# --let $aws_s3_bucket = my-bucket +# --let $storage_object = /vault/second +# --source diff_with_storage_object.inc # # $storage_backend - determines stortage backend type (either 'file' or 'fs') # $aws_cli - path to AWS command line interface (cli) tools with AWS_ACCESS_KEY_ID / diff --git a/mtr/binlog_streaming/include/identify_storage_backend.inc b/mtr/binlog_streaming/include/identify_storage_backend.inc new file mode 100644 index 0000000..18a4aeb --- /dev/null +++ b/mtr/binlog_streaming/include/identify_storage_backend.inc @@ -0,0 +1,23 @@ +# The following environment variables must be defined to use AWS S3 as a +# storage backend: +# - $MTR_BINSRV_AWS_ACCESS_KEY_ID +# - $MTR_BINSRV_AWS_SECRET_ACCESS_KEY +# - $MTR_BINSRV_AWS_S3_BUCKET +# - $MTR_BINSRV_AWS_S3_REGION (optional) + +--let $storage_backend = file +if ($MTR_BINSRV_AWS_ACCESS_KEY_ID != '') +{ + if ($MTR_BINSRV_AWS_SECRET_ACCESS_KEY != '') + { + if ($MTR_BINSRV_AWS_S3_BUCKET != '') + { + --let $storage_backend = s3 + --let $aws_cli = AWS_ACCESS_KEY_ID=$MTR_BINSRV_AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$MTR_BINSRV_AWS_SECRET_ACCESS_KEY aws + if ($MTR_BINSRV_AWS_S3_REGION != '') + { + --let $aws_cli = $aws_cli --region $MTR_BINSRV_AWS_S3_REGION + } + } + } +} diff --git a/mtr/binlog_streaming/include/set_up_binsrv_environment.inc b/mtr/binlog_streaming/include/set_up_binsrv_environment.inc new file mode 100644 index 0000000..2e1db50 --- /dev/null +++ b/mtr/binlog_streaming/include/set_up_binsrv_environment.inc @@ -0,0 +1,94 @@ +# +# Creates a data directory and a JSON configuration file for the Binlog Server Utility +# +# Usage: +# --let $binsrv_connect_timeout = 20 +# --let $binsrv_read_timeout = 60 +# --let $binsrv_idle_time = 10 +# --source set_up_binsrv_environment.inc + +--echo +--echo *** Generating a configuration file in JSON format for the Binlog +--echo *** Server utility. + +# temporarily disabling MySQL general query log so that AWS credentials +# will not appear in plain in recorded SQL queries +--disable_query_log +SET @old_sql_log_off = @@sql_log_off; +SET sql_log_off = ON; + +if ($storage_backend == file) +{ + --let $binsrv_storage_path = $MYSQL_TMP_DIR/storage + eval SET @storage_uri = CONCAT('file://', '$binsrv_storage_path'); +} +if ($storage_backend == s3) +{ + --let $qualified_bucket = $MTR_BINSRV_AWS_S3_BUCKET + if ($MTR_BINSRV_AWS_S3_REGION) + { + --let $qualified_bucket = $qualified_bucket.$MTR_BINSRV_AWS_S3_REGION + } + --let $binsrv_storage_path = `SELECT CONCAT('/mtr-', UUID())` + eval SET @storage_uri = CONCAT('s3://', '$MTR_BINSRV_AWS_ACCESS_KEY_ID', ':', '$MTR_BINSRV_AWS_SECRET_ACCESS_KEY', '@', '$qualified_bucket', '$binsrv_storage_path'); + --let $aws_s3_bucket = $MTR_BINSRV_AWS_S3_BUCKET +} + +--let $binsrv_log_path = $MYSQL_TMP_DIR/binsrv_utility.log +eval SET @log_path = '$binsrv_log_path'; + +SET @delimiter_pos = INSTR(USER(), '@'); +SET @connection_user = SUBSTRING(USER(), 1, @delimiter_pos - 1); +SET @connection_host = SUBSTRING(USER(), @delimiter_pos + 1); +SET @connection_host = IF(@connection_host = 'localhost', '127.0.0.1', @connection_host); + +eval SET @binsrv_config_json = JSON_OBJECT( + 'logger', JSON_OBJECT( + 'level', 'trace', + 'file', @log_path + ), + 'connection', JSON_OBJECT( + 'host', @connection_host, + 'port', @@global.port, + 'user', @connection_user, + 'password', '', + 'connect_timeout', $binsrv_connect_timeout, + 'read_timeout', $binsrv_read_timeout, + 'write_timeout', 60 + ), + 'replication', JSON_OBJECT( + 'server_id', @@server_id + 1, + 'idle_time', $binsrv_idle_time + ), + 'storage', JSON_OBJECT( + 'uri', @storage_uri + ) +); + +--let $binsrv_config_file_path = $MYSQL_TMP_DIR/binsrv_config.json +--let $write_var = `SELECT @binsrv_config_json` +--let $write_to_file = $binsrv_config_file_path +--source include/write_var_to_file.inc + +SET sql_log_off = @old_sql_log_off; +--enable_query_log + +--echo +--echo *** Determining binlog file directory from the server. +--disable_query_log +SET @path_separator = '/'; +--source include/check_windows.inc +if ($have_windows) { + SET @path_separator = '\\'; +} +--let $binlog_base_dir = `SELECT LEFT(@@global.log_bin_basename, CHAR_LENGTH(@@global.log_bin_basename) - CHAR_LENGTH(SUBSTRING_INDEX(@@global.log_bin_basename, @path_separator, -1)))` +--enable_query_log + + +--echo +--echo *** Creating a temporary directory for storing +--echo *** binlog files downloaded via the Binlog Server utility. +if ($storage_backend == file) +{ + --mkdir $binsrv_storage_path +} diff --git a/mtr/binlog_streaming/include/tear_down_binsrv_environment.inc b/mtr/binlog_streaming/include/tear_down_binsrv_environment.inc new file mode 100644 index 0000000..2d48b8e --- /dev/null +++ b/mtr/binlog_streaming/include/tear_down_binsrv_environment.inc @@ -0,0 +1,18 @@ +--echo +--echo *** Removing the Binlog Server utility storage directory. +if ($storage_backend == file) +{ + --force-rmdir $binsrv_storage_path +} +if ($storage_backend == s3) +{ + --exec $aws_cli s3 rm s3://$aws_s3_bucket$binsrv_storage_path/ --recursive > /dev/null +} + +--echo +--echo *** Removing the Binlog Server utility log file. +--remove_file $binsrv_log_path + +--echo +--echo *** Removing the Binlog Server utility configuration file. +--remove_file $binsrv_config_file_path diff --git a/mtr/binlog_streaming/r/binsrv.result b/mtr/binlog_streaming/r/binsrv.result index 378bd88..0fe7363 100644 --- a/mtr/binlog_streaming/r/binsrv.result +++ b/mtr/binlog_streaming/r/binsrv.result @@ -1,11 +1,8 @@ -*** Flushing binary logs at the very beginning of the test. -FLUSH BINARY LOGS; +*** Resetting replication at the very beginning of the test. +RESET MASTER; *** Determining the first fresh binary log name. -*** Purging all binary logs before the first fresh one. -PURGE BINARY LOGS TO ''; - *** Creating a simple table and filling it with some data. CREATE TABLE t1(id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id)) ENGINE=InnoDB; INSERT INTO t1 VALUES(DEFAULT); diff --git a/mtr/binlog_streaming/r/pull_mode.result b/mtr/binlog_streaming/r/pull_mode.result new file mode 100644 index 0000000..ef41662 --- /dev/null +++ b/mtr/binlog_streaming/r/pull_mode.result @@ -0,0 +1,55 @@ +*** Resetting replication at the very beginning of the test. +RESET MASTER; + +*** Generating a configuration file in JSON format for the Binlog +*** Server utility. + +*** Determining binlog file directory from the server. + +*** Creating a temporary directory for storing +*** binlog files downloaded via the Binlog Server utility. + +*** Starting Binlog Server Utility in background in pull mode +include/read_file_to_var.inc + +*** Determining the first fresh binary log name. + +*** Creating a simple table and filling it with some data. +CREATE TABLE t1(id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id)) ENGINE=InnoDB; +INSERT INTO t1 VALUES(DEFAULT); + +*** Restarting the server with a pause to test for the STOP event in +*** the binary log and reconnection logic. +# restart + +*** Determining the second binary log name. + +*** Filling the table with some more data and dropping the table. +INSERT INTO t1 VALUES(DEFAULT); +DROP TABLE t1; + +*** FLUSHING the binlog one more time to make sure that the second one +*** is no longer open. +FLUSH BINARY LOGS; + +*** Determining the third binary log name. + +*** Waiting till Binlog Server Utility starts processing the third +*** binary log. + +*** Sending SIGTERM signal to the Binlog Server Utility and waiting for +*** the process to terminate + +*** Checking that the Binlog Server utility detected an empty storage +include/assert_grep.inc [Binlog storage must be initialized on an empty directory] + +*** Comparing server and downloaded versions of the first binlog file + +*** Comparing server and downloaded versions of the second binlog file + +*** Removing the Binlog Server utility storage directory. + +*** Removing the Binlog Server utility log file. + +*** Removing the Binlog Server utility configuration file. +KILL CONNECTION ; diff --git a/mtr/binlog_streaming/t/binsrv.test b/mtr/binlog_streaming/t/binsrv.test index c4a5172..327611a 100644 --- a/mtr/binlog_streaming/t/binsrv.test +++ b/mtr/binlog_streaming/t/binsrv.test @@ -1,11 +1,3 @@ -# The following environment variables must be defined to use AWS S3 as a -# storage backend: -# - $MTR_BINSRV_AWS_ACCESS_KEY_ID -# - $MTR_BINSRV_AWS_SECRET_ACCESS_KEY -# - $MTR_BINSRV_AWS_S3_BUCKET -# - $MTR_BINSRV_AWS_S3_REGION (optional) - - # make sure that $BINSRV environment variable is set to the absolute path # of the Binlog Server utility before running this test if (!$BINSRV) { @@ -14,18 +6,12 @@ if (!$BINSRV) { # in case of --repeat=N, we need to start from a fresh binary log to make # this test deterministic ---echo *** Flushing binary logs at the very beginning of the test. -FLUSH BINARY LOGS; +--echo *** Resetting replication at the very beginning of the test. +RESET MASTER; --echo --echo *** Determining the first fresh binary log name. --let $first_binlog = query_get_value(SHOW MASTER STATUS, File, 1) ---replace_result $first_binlog - ---echo ---echo *** Purging all binary logs before the first fresh one. ---replace_result $first_binlog -eval PURGE BINARY LOGS TO '$first_binlog'; --echo --echo *** Creating a simple table and filling it with some data. @@ -44,108 +30,14 @@ FLUSH BINARY LOGS; --echo *** Filling the table with some more data. INSERT INTO t1 VALUES(DEFAULT); ---let $storage_backend = file -if ($MTR_BINSRV_AWS_ACCESS_KEY_ID != '') -{ - if ($MTR_BINSRV_AWS_SECRET_ACCESS_KEY != '') - { - if ($MTR_BINSRV_AWS_S3_BUCKET != '') - { - --let $storage_backend = s3 - --let $aws_cli = AWS_ACCESS_KEY_ID=$MTR_BINSRV_AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$MTR_BINSRV_AWS_SECRET_ACCESS_KEY aws - if ($MTR_BINSRV_AWS_S3_REGION != '') - { - --let $aws_cli = $aws_cli --region $MTR_BINSRV_AWS_S3_REGION - } - } - } -} - ---echo ---echo *** Generating a configuration file in JSON format for the Binlog ---echo *** Server utility. - -# temporarily disabling MySQL general query log so that AWS credentials -# will not appear in plain in recorded SQL queries ---disable_query_log -SET @old_sql_log_off = @@sql_log_off; -SET sql_log_off = ON; - -if ($storage_backend == file) -{ - --let $binsrv_storage_path = $MYSQL_TMP_DIR/storage - eval SET @storage_uri = CONCAT('file://', '$binsrv_storage_path'); -} -if ($storage_backend == s3) -{ - --let $qualified_bucket = $MTR_BINSRV_AWS_S3_BUCKET - if ($MTR_BINSRV_AWS_S3_REGION) - { - --let $qualified_bucket = $qualified_bucket.$MTR_BINSRV_AWS_S3_REGION - } - --let $binsrv_storage_path = `SELECT CONCAT('/mtr-', UUID())` - eval SET @storage_uri = CONCAT('s3://', '$MTR_BINSRV_AWS_ACCESS_KEY_ID', ':', '$MTR_BINSRV_AWS_SECRET_ACCESS_KEY', '@', '$qualified_bucket', '$binsrv_storage_path'); - --let $aws_s3_bucket = $MTR_BINSRV_AWS_S3_BUCKET -} - ---let $binsrv_log_path = $MYSQL_TMP_DIR/binsrv_utility.log -eval SET @log_path = '$binsrv_log_path'; - -SET @delimiter_pos = INSTR(USER(), '@'); -SET @connection_user = SUBSTRING(USER(), 1, @delimiter_pos - 1); -SET @connection_host = SUBSTRING(USER(), @delimiter_pos + 1); -SET @connection_host = IF(@connection_host = 'localhost', '127.0.0.1', @connection_host); - -eval SET @binsrv_config_json = JSON_OBJECT( - 'logger', JSON_OBJECT( - 'level', 'trace', - 'file', @log_path - ), - 'connection', JSON_OBJECT( - 'host', @connection_host, - 'port', @@global.port, - 'user', @connection_user, - 'password', '', - 'connect_timeout', 20, - 'read_timeout', 60, - 'write_timeout', 60 - ), - 'replication', JSON_OBJECT( - 'server_id', @@server_id + 1, - 'idle_time', 10 - ), - 'storage', JSON_OBJECT( - 'uri', @storage_uri - ) -); +# identifying backend storage type ('file' or 's3') +--source ../include/identify_storage_backend.inc ---let $binsrv_config_file_path = $MYSQL_TMP_DIR/binsrv_config.json ---let $write_var = `SELECT @binsrv_config_json` ---let $write_to_file = $binsrv_config_file_path ---source include/write_var_to_file.inc - -SET sql_log_off = @old_sql_log_off; ---enable_query_log - ---echo ---echo *** Determining binlog file directory from the server. ---disable_query_log -SET @path_separator = '/'; ---source include/check_windows.inc -if ($have_windows) { - SET @path_separator = '\\'; -} ---let $binlog_base_dir = `SELECT LEFT(@@global.log_bin_basename, CHAR_LENGTH(@@global.log_bin_basename) - CHAR_LENGTH(SUBSTRING_INDEX(@@global.log_bin_basename, @path_separator, -1)))` ---enable_query_log - - ---echo ---echo *** Creating a temporary directory for storing ---echo *** binlog files downloaded via the Binlog Server utility. -if ($storage_backend == file) -{ - --mkdir $binsrv_storage_path -} +# creating data directory, configuration file, etc. +--let $binsrv_connect_timeout = 20 +--let $binsrv_read_timeout = 60 +--let $binsrv_idle_time = 10 +--source ../include/set_up_binsrv_environment.inc --echo --echo *** Executing the Binlog Server utility to download all binlog data @@ -161,7 +53,7 @@ if ($storage_backend == file) --let $assert_select = binlog storage initialized on an empty directory --source include/assert_grep.inc -# At this point we have 2 binlog files $first_binlog (already closed/rotedted +# At this point we have 2 binlog files $first_binlog (already closed/rotated # by the server) and $second_binlog (currently open). # The former can be compared as is. @@ -252,21 +144,5 @@ FLUSH BINARY LOGS; --let $storage_object = $binsrv_storage_path/$second_binlog --source ../include/diff_with_storage_object.inc ---echo ---echo *** Removing the Binlog Server utility storage directory. -if ($storage_backend == file) -{ - --force-rmdir $binsrv_storage_path -} -if ($storage_backend == s3) -{ - --exec $aws_cli s3 rm s3://$aws_s3_bucket$binsrv_storage_path/ --recursive > /dev/null -} - ---echo ---echo *** Removing the Binlog Server utility log file. ---remove_file $binsrv_log_path - ---echo ---echo *** Removing the Binlog Server utility configuration file. ---remove_file $binsrv_config_file_path +# cleaning up +--source ../include/tear_down_binsrv_environment.inc diff --git a/mtr/binlog_streaming/t/pull_mode.test b/mtr/binlog_streaming/t/pull_mode.test new file mode 100644 index 0000000..39bf11a --- /dev/null +++ b/mtr/binlog_streaming/t/pull_mode.test @@ -0,0 +1,156 @@ +# make sure that $BINSRV environment variable is set to the absolute path +# of the Binlog Server utility before running this test +if (!$BINSRV) { + --skip \$BINSRV environment variable must be set +} + +--source include/count_sessions.inc + +# in case of --repeat=N, we need to start from a fresh binary log to make +# this test deterministic +--echo *** Resetting replication at the very beginning of the test. +RESET MASTER; + +# identifying backend storage type ('file' or 's3') +--source ../include/identify_storage_backend.inc + +# creating data directory, configuration file, etc. +--let $binsrv_connect_timeout = 3 +--let $binsrv_read_timeout = 3 +--let $binsrv_idle_time = 1 +--source ../include/set_up_binsrv_environment.inc + +--echo +--echo *** Starting Binlog Server Utility in background in pull mode +--let $binsrv_pid_file = $MYSQL_TMP_DIR/binsrv_utility.pid +--let $binsrv_spawn_cmd_line = $BINSRV pull $binsrv_config_file_path > /dev/null & echo \$! > $binsrv_pid_file + +--let EXPORTED_BINSRV_SPAWN_CMD_LINE = $binsrv_spawn_cmd_line +--perl + use strict; + use warnings; + my $cmd = $ENV{'EXPORTED_BINSRV_SPAWN_CMD_LINE'}; + system("$cmd"); +EOF + +--let $read_from_file = $binsrv_pid_file +--source include/read_file_to_var.inc +--let $binsrv_pid = $result + +--echo +--echo *** Determining the first fresh binary log name. +--let $first_binlog = query_get_value(SHOW MASTER STATUS, File, 1) + +--echo +--echo *** Creating a simple table and filling it with some data. +CREATE TABLE t1(id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id)) ENGINE=InnoDB; +INSERT INTO t1 VALUES(DEFAULT); + +--echo +--echo *** Restarting the server with a pause to test for the STOP event in +--echo *** the binary log and reconnection logic. +--source include/shutdown_mysqld.inc +# Sleeping here deliberately so that the Binlog Server Utility would encounter +# read timeout and would try to reconnect several times. +--sleep 10 +--source include/start_mysqld.inc + +--echo +--echo *** Determining the second binary log name. +--let $second_binlog = query_get_value(SHOW MASTER STATUS, File, 1) + +--echo +--echo *** Filling the table with some more data and dropping the table. +INSERT INTO t1 VALUES(DEFAULT); +DROP TABLE t1; + +--echo +--echo *** FLUSHING the binlog one more time to make sure that the second one +--echo *** is no longer open. +FLUSH BINARY LOGS; + +--echo +--echo *** Determining the third binary log name. +--let $third_binlog = query_get_value(SHOW MASTER STATUS, File, 1) + +--echo +--echo *** Waiting till Binlog Server Utility starts processing the third +--echo *** binary log. +# We grep the Binlog Server Utility log file in a loop until we encounter the +# third binary log file name. +--let $max_number_of_attempts = 60 +--let $iteration = 0 +while($iteration < $max_number_of_attempts) +{ + --error 0, 1 + --exec grep --silent $third_binlog $binsrv_log_path + --let $grep_status = $__error + if ($grep_status == 0) + { + --let $iteration = $max_number_of_attempts + } + if ($grep_status != 0) + { + --sleep 1 + --inc $iteration + } +} +if ($grep_status != 0) +{ + --die The Binlog Server Utility did not start processing the third binary log. +} + +--echo +--echo *** Sending SIGTERM signal to the Binlog Server Utility and waiting for +--echo *** the process to terminate +--replace_result $binsrv_pid +--exec kill -s TERM $binsrv_pid +--let EXPORTED_BINSRV_PID = $binsrv_pid + +--perl + use strict; + use warnings; + use Errno; + my $pid = $ENV{'EXPORTED_BINSRV_PID'}; + my $not_present = (!kill(0, $pid) && $! == Errno::ESRCH); + while (!$not_present) { + sleep(1); + $not_present = (!kill(0, $pid) && $! == Errno::ESRCH); + } +EOF + +--remove_file $binsrv_pid_file + +--echo +--echo *** Checking that the Binlog Server utility detected an empty storage +--let $assert_text = Binlog storage must be initialized on an empty directory +--let $assert_file = $binsrv_log_path +--let $assert_count = 1 +--let $assert_select = binlog storage initialized on an empty directory +--source include/assert_grep.inc + +--echo +--echo *** Comparing server and downloaded versions of the first binlog file +--let $local_file = $binlog_base_dir/$first_binlog +--let $storage_object = $binsrv_storage_path/$first_binlog +--source ../include/diff_with_storage_object.inc + +--echo +--echo *** Comparing server and downloaded versions of the second binlog file +--let $local_file = $binlog_base_dir/$second_binlog +--let $storage_object = $binsrv_storage_path/$second_binlog +--source ../include/diff_with_storage_object.inc + +# cleaning up +--source ../include/tear_down_binsrv_environment.inc + +# As the Binlog Server Utility interrupts the connection upon timeout, here we +# need to close it on the MySQL server side as well in order to make sure that +# MTR 'check-test' before and after the test produces the same output. +--let $binlog_dump_connection_id = `SELECT ID FROM performance_schema.processlist WHERE COMMAND = 'Binlog Dump'` +--replace_result $binlog_dump_connection_id +eval KILL CONNECTION $binlog_dump_connection_id; + +# Also, we use 'count_sessions' include files to make sure that 'Binlog Dump' +# connection is indeed closed. +--source include/wait_until_count_sessions.inc diff --git a/src/app.cpp b/src/app.cpp index 89e5d83..a192923 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -228,7 +228,8 @@ void process_binlog_event(const binsrv::event::event ¤t_event, if (!is_artificial && !is_pseudo) { storage.write_event(portion); } - if (code == binsrv::event::code_type::rotate && !is_artificial) { + if ((code == binsrv::event::code_type::rotate && !is_artificial) || + code == binsrv::event::code_type::stop) { storage.close_binlog(); } } @@ -457,15 +458,15 @@ int main(int argc, char *argv[]) { "logging level set to \""s + std::string{log_level_label} + '"'); - // setting custom SIGINT and SIGTERM signals amd making sure that - // any other dependency library hasn't already changed them - [[maybe_unused]] auto const previous_sigterm_handler{ - std::signal(SIGTERM, &custom_signal_handler)}; - assert(previous_sigterm_handler == SIG_DFL); - - [[maybe_unused]] auto const previous_sigint_handler{ - std::signal(SIGINT, &custom_signal_handler)}; - assert(previous_sigint_handler == SIG_DFL); + // setting custom SIGINT and SIGTERM signal handlers + if (std::signal(SIGTERM, &custom_signal_handler) == SIG_ERR) { + util::exception_location().raise( + "cannot set custom signal handler for SIGTERM"); + } + if (std::signal(SIGINT, &custom_signal_handler) == SIG_ERR) { + util::exception_location().raise( + "cannot set custom signal handler for SIGINT"); + } logger->log(binsrv::log_severity::info, "set custom handlers for SIGINT and SIGTERM signals"); diff --git a/src/binsrv/event/empty_body.hpp b/src/binsrv/event/empty_body.hpp index 6548dbc..9f0ddb0 100644 --- a/src/binsrv/event/empty_body.hpp +++ b/src/binsrv/event/empty_body.hpp @@ -16,7 +16,7 @@ #ifndef BINSRV_EVENT_EMPTY_BODY_HPP #define BINSRV_EVENT_EMPTY_BODY_HPP -#include "binsrv/event/empty_post_header_fwd.hpp" // IWYU pragma: export +#include "binsrv/event/empty_body_fwd.hpp" // IWYU pragma: export #include "util/byte_span_fwd.hpp" diff --git a/src/binsrv/event/empty_body_fwd.hpp b/src/binsrv/event/empty_body_fwd.hpp index cbde5a9..817c38e 100644 --- a/src/binsrv/event/empty_body_fwd.hpp +++ b/src/binsrv/event/empty_body_fwd.hpp @@ -22,7 +22,7 @@ namespace binsrv::event { class empty_body; -std::ostream &operator<<(std::ostream &output, const enpty_body &obj); +std::ostream &operator<<(std::ostream &output, const empty_body &obj); } // namespace binsrv::event diff --git a/src/binsrv/event/event.hpp b/src/binsrv/event/event.hpp index 9862bb6..5e69899 100644 --- a/src/binsrv/event/event.hpp +++ b/src/binsrv/event/event.hpp @@ -40,6 +40,8 @@ #include "binsrv/event/reader_context_fwd.hpp" #include "binsrv/event/rotate_body_impl.hpp" // IWYU pragma: export #include "binsrv/event/rotate_post_header_impl.hpp" // IWYU pragma: export +#include "binsrv/event/stop_body_impl.hpp" // IWYU pragma: export +#include "binsrv/event/stop_post_header_impl.hpp" // IWYU pragma: export #include "binsrv/event/unknown_body.hpp" // IWYU pragma: export #include "binsrv/event/unknown_post_header.hpp" // IWYU pragma: export diff --git a/src/binsrv/event/reader_context.cpp b/src/binsrv/event/reader_context.cpp index 439e2ec..7ce089d 100644 --- a/src/binsrv/event/reader_context.cpp +++ b/src/binsrv/event/reader_context.cpp @@ -93,14 +93,23 @@ void reader_context::process_event(const event ¤t_event) { "header"); } - // normal (non-artificial) event is expected to be the last event in + // normal (non-artificial) ROTATE event is expected to be the last event in // binlog - so we reset the current position here position_ = 0U; } + if (code == code_type::stop) { + // alternatively, the last event in binlog may be not only non-artificial + // ROTATE (in case when admin executed FLUSH BINARY LOGS) but STOP (when + // the server was shut down) + // in this latter case, we also reset the position in order to indicate + // the end of the current cycle and expect new ROTATE(artificial) and + // FORMAT_DESCRIPTION + position_ = 0U; + } // TODO: add some kind of state machine where the expected sequence of events // is the following - - // (ROTATE(artificial) FORMAT_DESCRIPTION * ROTATE)* + // (ROTATE(artificial) FORMAT_DESCRIPTION * (ROTATE|STOP))* if (code == code_type::format_description) { const auto &post_header{ current_event.get_post_header()}; diff --git a/src/binsrv/event/stop_body_impl.hpp b/src/binsrv/event/stop_body_impl.hpp new file mode 100644 index 0000000..74bf94e --- /dev/null +++ b/src/binsrv/event/stop_body_impl.hpp @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// 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 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 + +#ifndef BINSRV_EVENT_STOP_BODY_IMPL_HPP +#define BINSRV_EVENT_STOP_BODY_IMPL_HPP + +#include "binsrv/event/stop_body_impl_fwd.hpp" // IWYU pragma: export + +#include "binsrv/event/empty_body.hpp" + +namespace binsrv::event { + +template <> class [[nodiscard]] generic_body_impl { +public: + using redirect_type = empty_body; +}; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_STOP_BODY_IMPL_HPP diff --git a/src/binsrv/event/stop_body_impl_fwd.hpp b/src/binsrv/event/stop_body_impl_fwd.hpp new file mode 100644 index 0000000..d9eba97 --- /dev/null +++ b/src/binsrv/event/stop_body_impl_fwd.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// 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 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 + +#ifndef BINSRV_EVENT_STOP_BODY_IMPL_FWD_HPP +#define BINSRV_EVENT_STOP_BODY_IMPL_FWD_HPP + +#include "binsrv/event/code_type.hpp" +#include "binsrv/event/generic_body_fwd.hpp" + +namespace binsrv::event { + +template <> class generic_body_impl; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_STOP_BODY_IMPL_FWD_HPP diff --git a/src/binsrv/event/stop_post_header_impl.hpp b/src/binsrv/event/stop_post_header_impl.hpp new file mode 100644 index 0000000..5b628ee --- /dev/null +++ b/src/binsrv/event/stop_post_header_impl.hpp @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// 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 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 + +#ifndef BINSRV_EVENT_STOP_POST_HEADER_IMPL_HPP +#define BINSRV_EVENT_STOP_POST_HEADER_IMPL_HPP + +#include "binsrv/event/stop_post_header_impl_fwd.hpp" // IWYU pragma: export + +#include "binsrv/event/empty_post_header.hpp" + +namespace binsrv::event { + +template <> class [[nodiscard]] generic_post_header_impl { +public: + using redirect_type = empty_post_header; +}; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_STOP_POST_HEADER_IMPL_HPP diff --git a/src/binsrv/event/stop_post_header_impl_fwd.hpp b/src/binsrv/event/stop_post_header_impl_fwd.hpp new file mode 100644 index 0000000..085082a --- /dev/null +++ b/src/binsrv/event/stop_post_header_impl_fwd.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// 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 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 + +#ifndef BINSRV_EVENT_STOP_POST_HEADER_IMPL_FWD_HPP +#define BINSRV_EVENT_STOP_POST_HEADER_IMPL_FWD_HPP + +#include "binsrv/event/code_type.hpp" +#include "binsrv/event/generic_post_header_fwd.hpp" + +namespace binsrv::event { + +template <> class generic_post_header_impl; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_STOP_POST_HEADER_IMPL_FWD_HPP