Skip to content

Commit 2b97ff5

Browse files
author
Slawomir Maludzinski
committed
BUG#33658850 - Binlog recovery not reporting all error states
Problem ------- The binary log recovery process doesn't report errors for all failure cases that may happen. More specifically, in `MYSQL_BIN_LOG::open_binlog`: 1. If ev==nullptr return error. Start ha_recover(). It's possible that the server crashed after creating the file and before writing anything to it. 2. If type_code!=FORMAT_DESCRIPTION_LOG_EVENT it returns error. It reports that the binlog is corrupted and return error. Start ha_recover(); 3. If LOG_EVENT_BINLOG_IN_USE_F is cleared, it does the right thing, return an error. Start ha_recover(). 4. If is_binlog_malformed() report error and don't truncate the binlog nor clear the LOG_EVENT_IN_USE_F. 5. If storage engine recovery fails truncate the binlog to a valid position but not clear LOG_EVENT_IN_USE_F. 6. If valid_pos==0, it skips truncating the binary log and clearing LOG_EVENT_IN_USE_F. That situation happens when binlog begins with a half-transaction but is handled in case 1. and 2. Analysis / Root-cause analysis ------------------------------ Suggested Fix: Suggest code flow like: ev = ... if (!ev) { LogErr(...) return -1; } if (ev is not a FD event) { LogErr(...) return -1; } if (LOG_EVENT_IN_USE_F is not set) { LogErr(...) return 0; } Do the recovery. if (is_binlog_malformed) return -1; if (has_engine_recovery_failed) { Do the truncation. return -1; } if (valid_pos > 0) { Do the truncation. } Clear LOG_EVENT_IN_USE_F. return 0; Solution -------- Change binlog.cc int MYSQL_BIN_LOG::open_binlog(const char *opt_name) implementation. Change-Id: Iea89e083cef8d8fdc62273888911d91c6e07f0ad
1 parent b34e761 commit 2b97ff5

File tree

7 files changed

+495
-104
lines changed

7 files changed

+495
-104
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#
2+
# copy file segment
3+
#
4+
# $in_filename - name of the source binary log
5+
# $out_filename - name of the output binary log
6+
# $file_start_pos - file position start
7+
# $file_stop_pos - file position stop
8+
9+
if (!$in_filename) {
10+
--die Please assign a file name to $in_filename!!
11+
}
12+
13+
if (!$out_filename) {
14+
--die Please assign a file name to $out_filename!!
15+
}
16+
17+
if ($file_start_pos == '') {
18+
--die Please assign position of segment start offset to $file_start_pos!!
19+
}
20+
21+
if ($file_stop_pos == '') {
22+
--die Please assign position of segment stop offset to $file_stop_pos!!
23+
}
24+
25+
--let IN_FILENAME_ENV = $in_filename
26+
--let OUT_FILENAME_ENV = $out_filename
27+
--let FILE_START_POS_ENV = $file_start_pos
28+
--let FILE_STOP_POS_ENV = $file_stop_pos
29+
30+
--perl
31+
my $in_filename = $ENV{'IN_FILENAME_ENV'};
32+
my $out_filename = $ENV{'OUT_FILENAME_ENV'};
33+
my $file_start_pos = $ENV{'FILE_START_POS_ENV'};
34+
my $file_stop_pos = $ENV{'FILE_STOP_POS_ENV'};
35+
36+
open(IN, '<:raw', $in_filename) || die("Could not open <$in_filename: $!");
37+
open(OUT, '>>:raw', $out_filename) || die("Could not open >>$out_filename: $!");
38+
39+
my ($len_in, $len_out, $data);
40+
41+
# skip $file_start_pos bytes
42+
sysseek(IN, $file_start_pos, SEEK_SET);
43+
44+
# length to be read
45+
my $length = $file_stop_pos - $file_start_pos;
46+
47+
# copy binary segment
48+
while ($length > 0) {
49+
$len_in = sysread(IN, my $data, $length);
50+
if (!defined($len_in)) {
51+
die("Could not sysread data: $!");
52+
}
53+
if ($len_in == 0) {
54+
die("Read zero bytes");
55+
}
56+
57+
$length = $length - $len_in;
58+
59+
my $offset = 0;
60+
while ($len_in > 0) {
61+
62+
$len_out = syswrite(OUT, $data, $len_in, $offset);
63+
if (!defined($len_out)) {
64+
die("Could not syswrite data: $!");
65+
}
66+
$offset += $len_out;
67+
$len_in -= $len_out;
68+
}
69+
}
70+
EOF
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
RESET MASTER;
2+
include/save_error_log_position.inc
3+
Prepare binary log with header only
4+
Prepare binary log without Format_desc event
5+
Prepare binary log without Format_desc event
6+
Backup binlog file
7+
8+
# 1. Test Error in Log_event::read_log_event() #
9+
################################################
10+
# restart
11+
RESET MASTER;
12+
include/assert_error_log.inc [server: 1, pattern: Error in Log_event::read_log_event().*]
13+
14+
# 2. Truncated binary log
15+
##############################################################################
16+
# restart
17+
RESET MASTER;
18+
include/assert_error_log.inc [server: 1, pattern: Error in Log_event::read_log_event().*
19+
Error reading GTIDs from binary log.*
20+
]
21+
22+
# 3. Test Found invalid event sequence while recovering from binary log file #
23+
##############################################################################
24+
# restart
25+
RESET MASTER;
26+
include/assert_error_log.inc [server: 1, pattern: Found invalid event sequence while recovering from binary log file.*]
27+
# restart
28+
29+
# 4. Test check !(ev->common_header->flags & LOG_EVENT_BINLOG_IN_USE_F) #
30+
#########################################################################
31+
# Adding debug point 'binlog_crash_between_close_and_open' to @@GLOBAL.debug
32+
FLUSH BINARY LOGS;
33+
ERROR HY000: Lost connection to MySQL server during query
34+
# restart
35+
# Check for the "Failed to open the crashed binlog file when source server is recovering it\."
36+
Pattern "\[Note\] \[[^]]*\] \[[^]]*\] Failed to open the crashed binlog file when source server is recovering it\." found
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# === Purpose ===
2+
#
3+
# Check errors returned by MYSQL_BIN_LOG::open_binlog
4+
#
5+
# ==== Requirements ====
6+
#
7+
# R1. When the binary log file is opened during recovery,
8+
# the following error conditions and boundary conditions should be handled:
9+
# * If the file is truncated just after the magic header,
10+
# it should report an error and start ha_recover().
11+
# * If the file is missing a format_description_log_event,
12+
# recovery should report an error and start ha_recover().
13+
# * If the Format_description_log_event exists but
14+
# does not have LOG_EVENT_BINLOG_IN_USE_F set,
15+
# it should report a warning and start ha_recover().
16+
#
17+
# === Implementation ====
18+
#
19+
# 1. Start server so that binary log is created
20+
# 2. Prepare binary log with header only
21+
# 3. Check error ER_READ_LOG_EVENT_FAILED is reported
22+
# "Error in Log_event::read_log_event()"
23+
# 4. Perpare binary log without Format_desc event
24+
# 5. Check error ER_BINLOG_CRASH_RECOVERY_MALFORMED_LOG is reported
25+
# "Found invalid event sequence while recovering from binary log file."
26+
# 6. Set debug points binlog_crash_between_close_and_open so that server is crashed
27+
# after binary log LOG_EVENT_BINLOG_IN_USE_F is set to false
28+
# 7. Check error ER_BINLOG_CANT_OPEN_CRASHED_BINLOG is reported
29+
# "Failed to open the crashed binlog file when source server is recovering it."
30+
#
31+
# === References ===
32+
#
33+
# BUG #33658850 Binlog recovery not reporting all error states
34+
#
35+
--source include/have_debug.inc
36+
--source include/have_binlog_format_mixed.inc
37+
38+
RESET MASTER;
39+
40+
--let $MYSQLD_DATADIR = `SELECT @@DATADIR`
41+
--let $binlog = query_get_value(SHOW MASTER STATUS, File, 1)
42+
--let $event_following_format_desc_start = query_get_value(SHOW BINLOG EVENTS IN '$binlog', Pos, 2)
43+
--let $event_following_format_desc_stop = query_get_value(SHOW BINLOG EVENTS IN '$binlog', End_log_pos, 2)
44+
45+
--file_exists $MYSQLD_DATADIR/$binlog
46+
47+
#
48+
# Use save_error_log_position.inc before the first call to assert_error_log.inc.
49+
# The error log may contain errors from earlier test runs.
50+
#
51+
--source include/save_error_log_position.inc
52+
53+
#
54+
# Shutdown server
55+
#
56+
--source include/shutdown_mysqld.inc
57+
58+
#
59+
# Prepare binary log with header only
60+
#
61+
--echo Prepare binary log with header only
62+
63+
--let $in_filename = $MYSQLD_DATADIR/$binlog
64+
--let $out_filename = $MYSQLD_DATADIR/$binlog.header
65+
--let $file_start_pos = 0
66+
--let $file_stop_pos = 4
67+
--source extra/binlog_tests/copy_file_segment.inc
68+
69+
#
70+
# Prepare binary log with truncated Format_desc event
71+
#
72+
--echo Prepare binary log without Format_desc event
73+
74+
--let $in_filename = $MYSQLD_DATADIR/$binlog
75+
--let $out_filename = $MYSQLD_DATADIR/$binlog.truncformat
76+
--let $file_start_pos = 0
77+
--let $file_stop_pos = $event_following_format_desc_start
78+
--dec $file_stop_pos
79+
--source extra/binlog_tests/copy_file_segment.inc
80+
81+
#
82+
# Prepare binary log without Format_desc event
83+
#
84+
--echo Prepare binary log without Format_desc event
85+
86+
--let $in_filename = $MYSQLD_DATADIR/$binlog
87+
--let $out_filename = $MYSQLD_DATADIR/$binlog.formatless
88+
--let $file_start_pos = 0
89+
--let $file_stop_pos = 4
90+
--source extra/binlog_tests/copy_file_segment.inc
91+
--let $file_start_pos = $event_following_format_desc_start
92+
--let $file_stop_pos = $event_following_format_desc_stop
93+
--source extra/binlog_tests/copy_file_segment.inc
94+
95+
#
96+
# Backup binlog file
97+
#
98+
--echo Backup binlog file
99+
100+
--copy_file $MYSQLD_DATADIR/$binlog $MYSQLD_DATADIR/$binlog.backup
101+
102+
--echo
103+
--echo # 1. Test Error in Log_event::read_log_event() #
104+
--echo ################################################
105+
106+
#
107+
# Swap header only event
108+
#
109+
--remove_file $MYSQLD_DATADIR/$binlog
110+
--copy_file $MYSQLD_DATADIR/$binlog.header $MYSQLD_DATADIR/$binlog
111+
112+
#
113+
# Start server
114+
#
115+
--source include/start_mysqld.inc
116+
117+
RESET MASTER;
118+
119+
#
120+
# Search for error message : 'arrived the end of the file'
121+
#
122+
--let $error_pattern = Error in Log_event::read_log_event().*
123+
--source include/assert_error_log.inc
124+
125+
#
126+
# Shutdown server
127+
#
128+
--source include/shutdown_mysqld.inc
129+
130+
--echo
131+
--echo # 2. Truncated binary log
132+
--echo ##############################################################################
133+
134+
#
135+
# Swap header only event
136+
#
137+
--remove_file $MYSQLD_DATADIR/$binlog
138+
--copy_file $MYSQLD_DATADIR/$binlog.truncformat $MYSQLD_DATADIR/$binlog
139+
140+
#
141+
# Start server
142+
#
143+
--source include/start_mysqld.inc
144+
145+
RESET MASTER;
146+
147+
#
148+
# Search for error message : 'binlog truncated in the middle of event; consider out of disk space'
149+
#
150+
let $error_pattern = Error in Log_event::read_log_event().*
151+
Error reading GTIDs from binary log.*
152+
;
153+
--source include/assert_error_log.inc
154+
155+
#
156+
# Shutdown server
157+
#
158+
--source include/shutdown_mysqld.inc
159+
160+
--echo
161+
--echo # 3. Test Found invalid event sequence while recovering from binary log file #
162+
--echo ##############################################################################
163+
164+
#
165+
# Swap format less binary log
166+
#
167+
--remove_file $MYSQLD_DATADIR/$binlog
168+
--copy_file $MYSQLD_DATADIR/$binlog.formatless $MYSQLD_DATADIR/$binlog
169+
170+
#
171+
# Start server
172+
#
173+
--source include/start_mysqld.inc
174+
175+
RESET MASTER;
176+
177+
#
178+
# Search for error message
179+
#
180+
--let $error_pattern = Found invalid event sequence while recovering from binary log file.*
181+
--source include/assert_error_log.inc
182+
183+
#
184+
# Shutdown server
185+
#
186+
--source include/shutdown_mysqld.inc
187+
188+
#
189+
# Restore binary log file
190+
#
191+
--remove_file $MYSQLD_DATADIR/$binlog
192+
--copy_file $MYSQLD_DATADIR/$binlog.backup $MYSQLD_DATADIR/$binlog
193+
194+
#
195+
# Now the server should start
196+
#
197+
--source include/start_mysqld.inc
198+
199+
--echo
200+
--echo # 4. Test check !(ev->common_header->flags & LOG_EVENT_BINLOG_IN_USE_F) #
201+
--echo #########################################################################
202+
203+
--let $debug_point = binlog_crash_between_close_and_open
204+
--source include/add_debug_point.inc
205+
206+
--error CR_SERVER_LOST
207+
FLUSH BINARY LOGS;
208+
209+
#
210+
# Start server
211+
#
212+
--remove_file $MYSQLTEST_VARDIR/log/mysqld.1.err
213+
--source include/start_mysqld.inc
214+
215+
#
216+
# Search for note message
217+
#
218+
--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err
219+
--let SEARCH_PATTERN = \[Note\] \[[^]]*\] \[[^]]*\] Failed to open the crashed binlog file when source server is recovering it\.
220+
--echo # Check for the "Failed to open the crashed binlog file when source server is recovering it\."
221+
--source include/search_pattern.inc
222+
223+
#
224+
# Cleanup
225+
#
226+
--remove_file $MYSQLD_DATADIR/$binlog.backup
227+
--remove_file $MYSQLD_DATADIR/$binlog.header
228+
--remove_file $MYSQLD_DATADIR/$binlog.truncformat
229+
--remove_file $MYSQLD_DATADIR/$binlog.formatless

0 commit comments

Comments
 (0)