Commit a4c50983 authored by Alfranio Correia's avatar Alfranio Correia

BUG#45292 orphan binary log created when starting server twice

This patch fixes three bugs as follows. First, aborting the server while purging
binary logs might generate orphan files due to how the purge operation was
implemented:

          (purge routine - sql/log.cc - MYSQL_BIN_LOG::purge_logs)

      1 - register the files to be removed in a temporary buffer.
      2 - update the log-bin.index.
      3 - flush the log-bin.index.
      4 - erase the files whose names where register in the temporary buffer
      in step 1.

Thus a failure while  executing step 4 would generate an orphan file. Second,
a similar issue might happen while creating a new binary as follows:

          (create routine - sql/log.cc - MYSQL_BIN_LOG::open)

      1 - open the new log-bin.
      2 - update the log-bin.index.

Thus a failure while executing step 1 would generate an orphan file.

To fix these issues, we record the files to be purged or created before really
removing or adding them. So if a failure happens such records can be used to
automatically remove dangling files. The new steps might be outlined as follows:

          (purge routine - sql/log.cc - MYSQL_BIN_LOG::purge_logs)

      1 - register the files to be removed in the log-bin.~rec~ placed in
      the data directory.
      2 - update the log-bin.index.
      3 - flush the log-bin.index.
      4 - delete the log-bin.~rec~.

          (create routine - sql/log.cc - MYSQL_BIN_LOG::open)

      1 - register the file to be created in the log-bin.~rec~
      placed in the data directory.
      2 - open the new log-bin.
      3 - update the log-bin.index.
      4 - delete the log-bin.~rec~.

          (recovery routine - sql/log.cc - MYSQL_BIN_LOG::open_index_file)

      1 - open the log-bin.index.
      2 - open the log-bin.~rec~.
      3 - for each file in log-bin.~rec~.
        3.1 Check if the file is in the log-bin.index and if so ignore it.
        3.2 Otherwise, delete it.

The third issue can be described as follows. The purge operation was allowing
to remove a file in use thus leading to the loss of data and possible
inconsistencies between the master and slave. Roughly, the routine was only
taking into account the dump threads and so if a slave was not connect the
file might be delete even though it was in use.
parent 43d8e02f
call mtr.add_suppression('Attempting backtrace');
call mtr.add_suppression('MSYQL_BIN_LOG::purge_logs failed to process registered files that would be purged.');
call mtr.add_suppression('MSYQL_BIN_LOG::open failed to sync the index file');
call mtr.add_suppression('Turning logging off for the whole duration of the MySQL server process.');
call mtr.add_suppression('MSYQL_BIN_LOG::purge_logs failed to clean registers before purging logs.');
flush logs; flush logs;
flush logs; flush logs;
flush logs; flush logs;
...@@ -21,7 +26,6 @@ flush logs; ...@@ -21,7 +26,6 @@ flush logs;
*** must be a warning master-bin.000001 was not found *** *** must be a warning master-bin.000001 was not found ***
Warnings: Warnings:
Warning 1612 Being purged log master-bin.000001 was not found Warning 1612 Being purged log master-bin.000001 was not found
Warning 1612 Being purged log master-bin.000001 was not found
*** must show one record, of the active binlog, left in the index file after PURGE *** *** must show one record, of the active binlog, left in the index file after PURGE ***
show binary logs; show binary logs;
Log_name File_size Log_name File_size
...@@ -37,4 +41,111 @@ Level Code Message ...@@ -37,4 +41,111 @@ Level Code Message
Error 1377 a problem with deleting master-bin.000001; consider examining correspondence of your binlog index file to the actual binlog files Error 1377 a problem with deleting master-bin.000001; consider examining correspondence of your binlog index file to the actual binlog files
Error 1377 Fatal error during log purge Error 1377 Fatal error during log purge
reset master; reset master;
# crash_purge_before_update_index
flush logs;
SET SESSION debug="+d,crash_purge_before_update_index";
purge binary logs TO 'master-bin.000002';
ERROR HY000: Lost connection to MySQL server during query
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000001
master-bin.000002
master-bin.000003
# crash_purge_non_critical_after_update_index
flush logs;
SET SESSION debug="+d,crash_purge_non_critical_after_update_index";
purge binary logs TO 'master-bin.000004';
ERROR HY000: Lost connection to MySQL server during query
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000004
master-bin.000005
# crash_purge_critical_after_update_index
flush logs;
SET SESSION debug="+d,crash_purge_critical_after_update_index";
purge binary logs TO 'master-bin.000006';
ERROR HY000: Lost connection to MySQL server during query
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000006
master-bin.000007
# crash_create_non_critical_before_update_index
SET SESSION debug="+d,crash_create_non_critical_before_update_index";
flush logs;
ERROR HY000: Lost connection to MySQL server during query
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000006
master-bin.000007
master-bin.000008
# crash_create_critical_before_update_index
SET SESSION debug="+d,crash_create_critical_before_update_index";
flush logs;
ERROR HY000: Lost connection to MySQL server during query
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000006
master-bin.000007
master-bin.000008
master-bin.000009
# crash_create_after_update_index
SET SESSION debug="+d,crash_create_after_update_index";
flush logs;
ERROR HY000: Lost connection to MySQL server during query
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000006
master-bin.000007
master-bin.000008
master-bin.000009
master-bin.000010
master-bin.000011
#
# This should put the server in unsafe state and stop
# accepting any command. If we inject a fault at this
# point and continue the execution the server crashes.
# Besides the flush command does not report an error.
#
# fault_injection_registering_index
SET SESSION debug="+d,fault_injection_registering_index";
flush logs;
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000006
master-bin.000007
master-bin.000008
master-bin.000009
master-bin.000010
master-bin.000011
master-bin.000012
# fault_injection_updating_index
SET SESSION debug="+d,fault_injection_updating_index";
flush logs;
SET @index=LOAD_FILE('MYSQLTEST_VARDIR/mysqld.1/data//master-bin.index');
SELECT @index;
@index
master-bin.000006
master-bin.000007
master-bin.000008
master-bin.000009
master-bin.000010
master-bin.000011
master-bin.000012
master-bin.000013
SET SESSION debug="";
End of tests End of tests
...@@ -3,6 +3,15 @@ ...@@ -3,6 +3,15 @@
# #
source include/have_log_bin.inc; source include/have_log_bin.inc;
source include/not_embedded.inc; source include/not_embedded.inc;
call mtr.add_suppression('Attempting backtrace');
call mtr.add_suppression('MSYQL_BIN_LOG::purge_logs failed to process registered files that would be purged.');
call mtr.add_suppression('MSYQL_BIN_LOG::open failed to sync the index file');
call mtr.add_suppression('Turning logging off for the whole duration of the MySQL server process.');
call mtr.add_suppression('MSYQL_BIN_LOG::purge_logs failed to clean registers before purging logs.');
let $old=`select @@debug`;
let $MYSQLD_DATADIR= `select @@datadir`;
let $INDEX=$MYSQLD_DATADIR/master-bin.index;
# #
# testing purge binary logs TO # testing purge binary logs TO
...@@ -13,7 +22,6 @@ flush logs; ...@@ -13,7 +22,6 @@ flush logs;
flush logs; flush logs;
source include/show_binary_logs.inc; source include/show_binary_logs.inc;
let $MYSQLD_DATADIR= `select @@datadir`;
remove_file $MYSQLD_DATADIR/master-bin.000001; remove_file $MYSQLD_DATADIR/master-bin.000001;
# there must be a warning with file names # there must be a warning with file names
...@@ -66,4 +74,159 @@ rmdir $MYSQLD_DATADIR/master-bin.000001; ...@@ -66,4 +74,159 @@ rmdir $MYSQLD_DATADIR/master-bin.000001;
--disable_warnings --disable_warnings
reset master; reset master;
--enable_warnings --enable_warnings
--echo # crash_purge_before_update_index
flush logs;
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
SET SESSION debug="+d,crash_purge_before_update_index";
--error 2013
purge binary logs TO 'master-bin.000002';
--enable_reconnect
--source include/wait_until_connected_again.inc
file_exists $MYSQLD_DATADIR/master-bin.000001;
file_exists $MYSQLD_DATADIR/master-bin.000002;
file_exists $MYSQLD_DATADIR/master-bin.000003;
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo # crash_purge_non_critical_after_update_index
flush logs;
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
SET SESSION debug="+d,crash_purge_non_critical_after_update_index";
--error 2013
purge binary logs TO 'master-bin.000004';
--enable_reconnect
--source include/wait_until_connected_again.inc
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000001;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000002;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000003;
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo # crash_purge_critical_after_update_index
flush logs;
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
SET SESSION debug="+d,crash_purge_critical_after_update_index";
--error 2013
purge binary logs TO 'master-bin.000006';
--enable_reconnect
--source include/wait_until_connected_again.inc
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000004;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000005;
file_exists $MYSQLD_DATADIR/master-bin.000006;
file_exists $MYSQLD_DATADIR/master-bin.000007;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000008;
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo # crash_create_non_critical_before_update_index
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
SET SESSION debug="+d,crash_create_non_critical_before_update_index";
--error 2013
flush logs;
--enable_reconnect
--source include/wait_until_connected_again.inc
file_exists $MYSQLD_DATADIR/master-bin.000008;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000009;
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo # crash_create_critical_before_update_index
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
SET SESSION debug="+d,crash_create_critical_before_update_index";
--error 2013
flush logs;
--enable_reconnect
--source include/wait_until_connected_again.inc
file_exists $MYSQLD_DATADIR/master-bin.000009;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000010;
--error 1
file_exists $MYSQLD_DATADIR/master-bin.000011;
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo # crash_create_after_update_index
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
SET SESSION debug="+d,crash_create_after_update_index";
--error 2013
flush logs;
--enable_reconnect
--source include/wait_until_connected_again.inc
file_exists $MYSQLD_DATADIR/master-bin.000010;
file_exists $MYSQLD_DATADIR/master-bin.000011;
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo #
--echo # This should put the server in unsafe state and stop
--echo # accepting any command. If we inject a fault at this
--echo # point and continue the execution the server crashes.
--echo # Besides the flush command does not report an error.
--echo #
--echo # fault_injection_registering_index
SET SESSION debug="+d,fault_injection_registering_index";
flush logs;
--source include/restart_mysqld.inc
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
--echo # fault_injection_updating_index
SET SESSION debug="+d,fault_injection_updating_index";
flush logs;
--source include/restart_mysqld.inc
--chmod 0644 $INDEX
-- replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
-- eval SET @index=LOAD_FILE('$index')
-- replace_regex /\.[\\\/]master/master/
SELECT @index;
eval SET SESSION debug="$old";
--echo End of tests --echo End of tests
...@@ -1897,6 +1897,22 @@ void MYSQL_LOG::init(enum_log_type log_type_arg, ...@@ -1897,6 +1897,22 @@ void MYSQL_LOG::init(enum_log_type log_type_arg,
} }
bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name,
const char *new_name,
enum_log_type log_type_arg,
enum cache_type io_cache_type_arg)
{
init(log_type_arg, io_cache_type_arg);
if (new_name && !strmov(log_file_name, new_name))
return TRUE;
else if (!new_name && generate_new_name(log_file_name, log_name))
return TRUE;
return FALSE;
}
/* /*
Open a (new) log file. Open a (new) log file.
...@@ -1929,17 +1945,14 @@ bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, ...@@ -1929,17 +1945,14 @@ bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg,
write_error= 0; write_error= 0;
init(log_type_arg, io_cache_type_arg);
if (!(name= my_strdup(log_name, MYF(MY_WME)))) if (!(name= my_strdup(log_name, MYF(MY_WME))))
{ {
name= (char *)log_name; // for the error message name= (char *)log_name; // for the error message
goto err; goto err;
} }
if (new_name) if (init_and_set_log_file_name(name, new_name,
strmov(log_file_name, new_name); log_type_arg, io_cache_type_arg))
else if (generate_new_name(log_file_name, name))
goto err; goto err;
if (io_cache_type == SEQ_READ_APPEND) if (io_cache_type == SEQ_READ_APPEND)
...@@ -2429,7 +2442,7 @@ MYSQL_BIN_LOG::MYSQL_BIN_LOG() ...@@ -2429,7 +2442,7 @@ MYSQL_BIN_LOG::MYSQL_BIN_LOG()
*/ */
index_file_name[0] = 0; index_file_name[0] = 0;
bzero((char*) &index_file, sizeof(index_file)); bzero((char*) &index_file, sizeof(index_file));
bzero((char*) &purge_temp, sizeof(purge_temp)); bzero((char*) &purge_index_file, sizeof(purge_index_file));
} }
/* this is called only once */ /* this is called only once */
...@@ -2473,7 +2486,7 @@ void MYSQL_BIN_LOG::init_pthread_objects() ...@@ -2473,7 +2486,7 @@ void MYSQL_BIN_LOG::init_pthread_objects()
bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg, bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
const char *log_name) const char *log_name, bool need_mutex)
{ {
File index_file_nr= -1; File index_file_nr= -1;
DBUG_ASSERT(!my_b_inited(&index_file)); DBUG_ASSERT(!my_b_inited(&index_file));
...@@ -2498,7 +2511,8 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg, ...@@ -2498,7 +2511,8 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
init_io_cache(&index_file, index_file_nr, init_io_cache(&index_file, index_file_nr,
IO_SIZE, WRITE_CACHE, IO_SIZE, WRITE_CACHE,
my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)), my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)),
0, MYF(MY_WME | MY_WAIT_IF_FULL))) 0, MYF(MY_WME | MY_WAIT_IF_FULL)) ||
DBUG_EVALUATE_IF("fault_injection_openning_index", 1, 0))
{ {
/* /*
TODO: all operations creating/deleting the index file or a log, should TODO: all operations creating/deleting the index file or a log, should
...@@ -2509,6 +2523,28 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg, ...@@ -2509,6 +2523,28 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
my_close(index_file_nr,MYF(0)); my_close(index_file_nr,MYF(0));
return TRUE; return TRUE;
} }
#ifdef HAVE_REPLICATION
/*
Sync the index by purging any binary log file that is not registered.
In other words, either purge binary log files that were removed from
the index but not purged from the file system due to a crash or purge
any binary log file that was created but not register in the index
due to a crash.
*/
if (set_purge_index_file_name(index_file_name_arg) ||
open_purge_index_file(FALSE) ||
purge_index_entry(NULL, NULL, need_mutex) ||
close_purge_index_file() ||
DBUG_EVALUATE_IF("fault_injection_recovering_index", 1, 0))
{
sql_print_error("MYSQL_BIN_LOG::open_index_file failed to sync the index "
"file.");
return TRUE;
}
#endif
return FALSE; return FALSE;
} }
...@@ -2533,17 +2569,44 @@ bool MYSQL_BIN_LOG::open(const char *log_name, ...@@ -2533,17 +2569,44 @@ bool MYSQL_BIN_LOG::open(const char *log_name,
enum cache_type io_cache_type_arg, enum cache_type io_cache_type_arg,
bool no_auto_events_arg, bool no_auto_events_arg,
ulong max_size_arg, ulong max_size_arg,
bool null_created_arg) bool null_created_arg,
bool need_mutex)
{ {
File file= -1; File file= -1;
DBUG_ENTER("MYSQL_BIN_LOG::open"); DBUG_ENTER("MYSQL_BIN_LOG::open");
DBUG_PRINT("enter",("log_type: %d",(int) log_type_arg)); DBUG_PRINT("enter",("log_type: %d",(int) log_type_arg));
write_error=0; if (init_and_set_log_file_name(log_name, new_name, log_type_arg,
io_cache_type_arg))
{
sql_print_error("MSYQL_BIN_LOG::open failed to generate new file name.");
DBUG_RETURN(1);
}
#ifdef HAVE_REPLICATION
if (open_purge_index_file(TRUE) ||
register_create_index_entry(log_file_name) ||
sync_purge_index_file() ||
DBUG_EVALUATE_IF("fault_injection_registering_index", 1, 0))
{
sql_print_error("MSYQL_BIN_LOG::open failed to sync the index file.");
DBUG_RETURN(1);
}
DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", abort(););
#endif
write_error= 0;
/* open the main log file */ /* open the main log file */
if (MYSQL_LOG::open(log_name, log_type_arg, new_name, io_cache_type_arg)) if (MYSQL_LOG::open(log_name, log_type_arg, new_name,
io_cache_type_arg))
{
#ifdef HAVE_REPLICATION
close_purge_index_file();
#endif
DBUG_RETURN(1); /* all warnings issued */ DBUG_RETURN(1); /* all warnings issued */
}
init(no_auto_events_arg, max_size_arg); init(no_auto_events_arg, max_size_arg);
...@@ -2569,9 +2632,6 @@ bool MYSQL_BIN_LOG::open(const char *log_name, ...@@ -2569,9 +2632,6 @@ bool MYSQL_BIN_LOG::open(const char *log_name,
write_file_name_to_index_file= 1; write_file_name_to_index_file= 1;
} }
DBUG_ASSERT(my_b_inited(&index_file) != 0);
reinit_io_cache(&index_file, WRITE_CACHE,
my_b_filelength(&index_file), 0, 0);
if (need_start_event && !no_auto_events) if (need_start_event && !no_auto_events)
{ {
/* /*
...@@ -2629,23 +2689,44 @@ bool MYSQL_BIN_LOG::open(const char *log_name, ...@@ -2629,23 +2689,44 @@ bool MYSQL_BIN_LOG::open(const char *log_name,
if (write_file_name_to_index_file) if (write_file_name_to_index_file)
{ {
#ifdef HAVE_REPLICATION
DBUG_EXECUTE_IF("crash_create_critical_before_update_index", abort(););
#endif
DBUG_ASSERT(my_b_inited(&index_file) != 0);
reinit_io_cache(&index_file, WRITE_CACHE,
my_b_filelength(&index_file), 0, 0);
/* /*
As this is a new log file, we write the file name to the index As this is a new log file, we write the file name to the index
file. As every time we write to the index file, we sync it. file. As every time we write to the index file, we sync it.
*/ */
if (my_b_write(&index_file, (uchar*) log_file_name, if (DBUG_EVALUATE_IF("fault_injection_updating_index", 1, 0) ||
strlen(log_file_name)) || my_b_write(&index_file, (uchar*) log_file_name,
my_b_write(&index_file, (uchar*) "\n", 1) || strlen(log_file_name)) ||
flush_io_cache(&index_file) || my_b_write(&index_file, (uchar*) "\n", 1) ||
flush_io_cache(&index_file) ||
my_sync(index_file.file, MYF(MY_WME))) my_sync(index_file.file, MYF(MY_WME)))
goto err; goto err;
#ifdef HAVE_REPLICATION
DBUG_EXECUTE_IF("crash_create_after_update_index", abort(););
#endif
} }
} }
log_state= LOG_OPENED; log_state= LOG_OPENED;
#ifdef HAVE_REPLICATION
close_purge_index_file();
#endif
DBUG_RETURN(0); DBUG_RETURN(0);
err: err:
#ifdef HAVE_REPLICATION
if (is_inited_purge_index_file())
purge_index_entry(NULL, NULL, need_mutex);
close_purge_index_file();
#endif
sql_print_error("Could not use %s for logging (error %d). \ sql_print_error("Could not use %s for logging (error %d). \
Turning logging off for the whole duration of the MySQL server process. \ Turning logging off for the whole duration of the MySQL server process. \
To turn it on again: fix the cause, \ To turn it on again: fix the cause, \
...@@ -2902,8 +2983,15 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) ...@@ -2902,8 +2983,15 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd)
name=0; // Protect against free name=0; // Protect against free
close(LOG_CLOSE_TO_BE_OPENED); close(LOG_CLOSE_TO_BE_OPENED);
/* First delete all old log files */ /*
First delete all old log files and then update the index file.
As we first delete the log files and do not use sort of logging,
a crash may lead to an inconsistent state where the index has
references to non-existent files.
We need to invert the steps and use the purge_index_file methods
in order to make the operation safe.
*/
if (find_log_pos(&linfo, NullS, 0)) if (find_log_pos(&linfo, NullS, 0))
{ {
error=1; error=1;
...@@ -2970,8 +3058,8 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) ...@@ -2970,8 +3058,8 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd)
} }
if (!thd->slave_thread) if (!thd->slave_thread)
need_start_event=1; need_start_event=1;
if (!open_index_file(index_file_name, 0)) if (!open_index_file(index_file_name, 0, FALSE))
open(save_name, log_type, 0, io_cache_type, no_auto_events, max_size, 0); open(save_name, log_type, 0, io_cache_type, no_auto_events, max_size, 0, FALSE);
my_free((uchar*) save_name, MYF(0)); my_free((uchar*) save_name, MYF(0));
err: err:
...@@ -3158,7 +3246,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, ...@@ -3158,7 +3246,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log,
bool need_update_threads, bool need_update_threads,
ulonglong *decrease_log_space) ulonglong *decrease_log_space)
{ {
int error; int error= 0;
bool exit_loop= 0; bool exit_loop= 0;
LOG_INFO log_info; LOG_INFO log_info;
THD *thd= current_thd; THD *thd= current_thd;
...@@ -3169,33 +3257,15 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, ...@@ -3169,33 +3257,15 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log,
pthread_mutex_lock(&LOCK_index); pthread_mutex_lock(&LOCK_index);
if ((error=find_log_pos(&log_info, to_log, 0 /*no mutex*/))) if ((error=find_log_pos(&log_info, to_log, 0 /*no mutex*/)))
{ {
sql_print_error("MYSQL_LOG::purge_logs was called with file %s not " sql_print_error("MYSQL_BIN_LOG::purge_logs was called with file %s not "
"listed in the index.", to_log); "listed in the index.", to_log);
goto err; goto err;
} }
/* if ((error= open_purge_index_file(TRUE)))
For crash recovery reasons the index needs to be updated before
any files are deleted. Move files to be deleted into a temp file
to be processed after the index is updated.
*/
if (!my_b_inited(&purge_temp))
{ {
if ((error=open_cached_file(&purge_temp, mysql_tmpdir, TEMP_PREFIX, sql_print_error("MYSQL_BIN_LOG::purge_logs failed to sync the index file.");
DISK_BUFFER_SIZE, MYF(MY_WME)))) goto err;
{
sql_print_error("MYSQL_LOG::purge_logs failed to open purge_temp");
goto err;
}
}
else
{
if ((error=reinit_io_cache(&purge_temp, WRITE_CACHE, 0, 0, 1)))
{
sql_print_error("MYSQL_LOG::purge_logs failed to reinit purge_temp "
"for write");
goto err;
}
} }
/* /*
...@@ -3205,51 +3275,177 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, ...@@ -3205,51 +3275,177 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log,
if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/))) if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
goto err; goto err;
while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) && while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) &&
!is_active(log_info.log_file_name) &&
!log_in_use(log_info.log_file_name)) !log_in_use(log_info.log_file_name))
{ {
if ((error=my_b_write(&purge_temp, (const uchar*)log_info.log_file_name, if ((error= register_purge_index_entry(log_info.log_file_name)))
strlen(log_info.log_file_name))) ||
(error=my_b_write(&purge_temp, (const uchar*)"\n", 1)))
{ {
sql_print_error("MYSQL_LOG::purge_logs failed to copy %s to purge_temp", sql_print_error("MYSQL_BIN_LOG::purge_logs failed to copy %s to register file.",
log_info.log_file_name); log_info.log_file_name);
goto err; goto err;
} }
if (find_next_log(&log_info, 0) || exit_loop) if (find_next_log(&log_info, 0) || exit_loop)
break; break;
} }
DBUG_EXECUTE_IF("crash_purge_before_update_index", abort(););
if ((error= sync_purge_index_file()))
{
sql_print_error("MSYQL_BIN_LOG::purge_logs failed to flush register file.");
goto err;
}
/* We know how many files to delete. Update index file. */ /* We know how many files to delete. Update index file. */
if ((error=update_log_index(&log_info, need_update_threads))) if ((error=update_log_index(&log_info, need_update_threads)))
{ {
sql_print_error("MSYQL_LOG::purge_logs failed to update the index file"); sql_print_error("MSYQL_BIN_LOG::purge_logs failed to update the index file");
goto err; goto err;
} }
DBUG_EXECUTE_IF("crash_after_update_index", abort();); DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", abort(););
err:
/* Read each entry from purge_index_file and delete the file. */
if (is_inited_purge_index_file() &&
(error= purge_index_entry(thd, decrease_log_space, FALSE)))
sql_print_error("MSYQL_BIN_LOG::purge_logs failed to process registered files"
" that would be purged.");
close_purge_index_file();
DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", abort(););
if (need_mutex)
pthread_mutex_unlock(&LOCK_index);
DBUG_RETURN(error);
}
/* Switch purge_temp for read. */ int MYSQL_BIN_LOG::set_purge_index_file_name(const char *base_file_name)
if ((error=reinit_io_cache(&purge_temp, READ_CACHE, 0, 0, 0))) {
int error= 0;
DBUG_ENTER("MYSQL_BIN_LOG::set_purge_index_file_name");
if (fn_format(purge_index_file_name, base_file_name, mysql_data_home,
".~rec~", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH |
MY_REPLACE_EXT)) == NULL)
{ {
sql_print_error("MSYQL_LOG::purge_logs failed to reinit purge_temp " error= 1;
sql_print_error("MYSQL_BIN_LOG::set_purge_index_file_name failed to set "
"file name.");
}
DBUG_RETURN(error);
}
int MYSQL_BIN_LOG::open_purge_index_file(bool destroy)
{
int error= 0;
File file= -1;
DBUG_ENTER("MYSQL_BIN_LOG::open_purge_index_file");
if (destroy)
close_purge_index_file();
if (!my_b_inited(&purge_index_file))
{
if ((file= my_open(purge_index_file_name, O_RDWR | O_CREAT | O_BINARY,
MYF(MY_WME | ME_WAITTANG))) < 0 ||
init_io_cache(&purge_index_file, file, IO_SIZE,
(destroy ? WRITE_CACHE : READ_CACHE),
0, 0, MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)))
{
error= 1;
sql_print_error("MYSQL_BIN_LOG::open_purge_index_file failed to open register "
" file.");
}
}
DBUG_RETURN(error);
}
int MYSQL_BIN_LOG::close_purge_index_file()
{
int error= 0;
DBUG_ENTER("MYSQL_BIN_LOG::close_purge_index_file");
if (my_b_inited(&purge_index_file))
{
end_io_cache(&purge_index_file);
error= my_close(purge_index_file.file, MYF(0));
}
my_delete(purge_index_file_name, MYF(0));
bzero((char*) &purge_index_file, sizeof(purge_index_file));
DBUG_RETURN(error);
}
bool MYSQL_BIN_LOG::is_inited_purge_index_file()
{
DBUG_ENTER("MYSQL_BIN_LOG::is_inited_purge_index_file");
DBUG_RETURN (my_b_inited(&purge_index_file));
}
int MYSQL_BIN_LOG::sync_purge_index_file()
{
int error= 0;
DBUG_ENTER("MYSQL_BIN_LOG::sync_purge_index_file");
if ((error= flush_io_cache(&purge_index_file)) ||
(error= my_sync(purge_index_file.file, MYF(MY_WME))))
DBUG_RETURN(error);
DBUG_RETURN(error);
}
int MYSQL_BIN_LOG::register_purge_index_entry(const char *entry)
{
int error= 0;
DBUG_ENTER("MYSQL_BIN_LOG::register_purge_index_entry");
if ((error=my_b_write(&purge_index_file, (const uchar*)entry, strlen(entry))) ||
(error=my_b_write(&purge_index_file, (const uchar*)"\n", 1)))
DBUG_RETURN (error);
DBUG_RETURN(error);
}
int MYSQL_BIN_LOG::register_create_index_entry(const char *entry)
{
DBUG_ENTER("MYSQL_BIN_LOG::register_create_index_entry");
DBUG_RETURN(register_purge_index_entry(entry));
}
int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *decrease_log_space,
bool need_mutex)
{
MY_STAT s;
int error= 0;
LOG_INFO log_info;
LOG_INFO check_log_info;
DBUG_ENTER("MYSQL_BIN_LOG:purge_index_entry");
DBUG_ASSERT(my_b_inited(&purge_index_file));
if ((error=reinit_io_cache(&purge_index_file, READ_CACHE, 0, 0, 0)))
{
sql_print_error("MSYQL_BIN_LOG::purge_index_entry failed to reinit register file "
"for read"); "for read");
goto err; goto err;
} }
/* Read each entry from purge_temp and delete the file. */
for (;;) for (;;)
{ {
uint length; uint length;
if ((length=my_b_gets(&purge_temp, log_info.log_file_name, if ((length=my_b_gets(&purge_index_file, log_info.log_file_name,
FN_REFLEN)) <= 1) FN_REFLEN)) <= 1)
{ {
if (purge_temp.error) if (purge_index_file.error)
{ {
error= purge_temp.error; error= purge_index_file.error;
sql_print_error("MSYQL_LOG::purge_logs error %d reading from " sql_print_error("MSYQL_BIN_LOG::purge_index_entry error %d reading from "
"purge_temp", error); "register file.", error);
goto err; goto err;
} }
...@@ -3260,9 +3456,6 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, ...@@ -3260,9 +3456,6 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log,
/* Get rid of the trailing '\n' */ /* Get rid of the trailing '\n' */
log_info.log_file_name[length-1]= 0; log_info.log_file_name[length-1]= 0;
ha_binlog_index_purge_file(current_thd, log_info.log_file_name);
MY_STAT s;
if (!my_stat(log_info.log_file_name, &s, MYF(0))) if (!my_stat(log_info.log_file_name, &s, MYF(0)))
{ {
if (my_errno == ENOENT) if (my_errno == ENOENT)
...@@ -3310,64 +3503,92 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, ...@@ -3310,64 +3503,92 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log,
} }
else else
{ {
DBUG_PRINT("info",("purging %s",log_info.log_file_name)); if ((error= find_log_pos(&check_log_info, log_info.log_file_name, need_mutex)))
if (!my_delete(log_info.log_file_name, MYF(0)))
{
if (decrease_log_space)
*decrease_log_space-= s.st_size;
}
else
{ {
if (my_errno == ENOENT) if (error != LOG_INFO_EOF)
{ {
if (thd) if (thd)
{ {
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_ERROR,
ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE), ER_BINLOG_PURGE_FATAL_ERR,
"a problem with deleting %s and "
"reading the binlog index file",
log_info.log_file_name); log_info.log_file_name);
} }
sql_print_information("Failed to delete file '%s'", else
log_info.log_file_name); {
my_errno= 0; sql_print_information("Failed to delete file '%s' and "
"read the binlog index file",
log_info.log_file_name);
}
goto err;
}
error= 0;
if (!need_mutex)
{
/*
This is to avoid triggering an error in NDB.
*/
ha_binlog_index_purge_file(current_thd, log_info.log_file_name);
}
DBUG_PRINT("info",("purging %s",log_info.log_file_name));
if (!my_delete(log_info.log_file_name, MYF(0)))
{
if (decrease_log_space)
*decrease_log_space-= s.st_size;
} }
else else
{ {
if (thd) if (my_errno == ENOENT)
{ {
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, if (thd)
ER_BINLOG_PURGE_FATAL_ERR, {
"a problem with deleting %s; " push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
"consider examining correspondence " ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
"of your binlog index file " log_info.log_file_name);
"to the actual binlog files", }
log_info.log_file_name); sql_print_information("Failed to delete file '%s'",
log_info.log_file_name);
my_errno= 0;
} }
else else
{ {
sql_print_information("Failed to delete file '%s'; " if (thd)
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_ERROR,
ER_BINLOG_PURGE_FATAL_ERR,
"a problem with deleting %s; "
"consider examining correspondence " "consider examining correspondence "
"of your binlog index file " "of your binlog index file "
"to the actual binlog files", "to the actual binlog files",
log_info.log_file_name); log_info.log_file_name);
} }
if (my_errno == EMFILE) else
{ {
DBUG_PRINT("info", sql_print_information("Failed to delete file '%s'; "
("my_errno: %d, set ret = LOG_INFO_EMFILE", my_errno)); "consider examining correspondence "
error= LOG_INFO_EMFILE; "of your binlog index file "
"to the actual binlog files",
log_info.log_file_name);
}
if (my_errno == EMFILE)
{
DBUG_PRINT("info",
("my_errno: %d, set ret = LOG_INFO_EMFILE", my_errno));
error= LOG_INFO_EMFILE;
goto err;
}
error= LOG_INFO_FATAL;
goto err; goto err;
} }
error= LOG_INFO_FATAL;
goto err;
} }
} }
} }
} }
err: err:
close_cached_file(&purge_temp);
if (need_mutex)
pthread_mutex_unlock(&LOCK_index);
DBUG_RETURN(error); DBUG_RETURN(error);
} }
...@@ -3407,7 +3628,8 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) ...@@ -3407,7 +3628,8 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time)
goto err; goto err;
while (strcmp(log_file_name, log_info.log_file_name) && while (strcmp(log_file_name, log_info.log_file_name) &&
!log_in_use(log_info.log_file_name)) !is_active(log_info.log_file_name) &&
!log_in_use(log_info.log_file_name))
{ {
if (!my_stat(log_info.log_file_name, &stat_area, MYF(0))) if (!my_stat(log_info.log_file_name, &stat_area, MYF(0)))
{ {
...@@ -3416,14 +3638,6 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) ...@@ -3416,14 +3638,6 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time)
/* /*
It's not fatal if we can't stat a log file that does not exist. It's not fatal if we can't stat a log file that does not exist.
*/ */
if (thd)
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
log_info.log_file_name);
}
sql_print_information("Failed to execute my_stat on file '%s'",
log_info.log_file_name);
my_errno= 0; my_errno= 0;
} }
else else
...@@ -3618,9 +3832,9 @@ void MYSQL_BIN_LOG::new_file_impl(bool need_lock) ...@@ -3618,9 +3832,9 @@ void MYSQL_BIN_LOG::new_file_impl(bool need_lock)
*/ */
/* reopen index binlog file, BUG#34582 */ /* reopen index binlog file, BUG#34582 */
if (!open_index_file(index_file_name, 0)) if (!open_index_file(index_file_name, 0, FALSE))
open(old_name, log_type, new_name_ptr, open(old_name, log_type, new_name_ptr,
io_cache_type, no_auto_events, max_size, 1); io_cache_type, no_auto_events, max_size, 1, FALSE);
my_free(old_name,MYF(0)); my_free(old_name,MYF(0));
end: end:
...@@ -5522,7 +5736,7 @@ int TC_LOG_BINLOG::open(const char *opt_name) ...@@ -5522,7 +5736,7 @@ int TC_LOG_BINLOG::open(const char *opt_name)
if (using_heuristic_recover()) if (using_heuristic_recover())
{ {
/* generate a new binlog to mask a corrupted one */ /* generate a new binlog to mask a corrupted one */
open(opt_name, LOG_BIN, 0, WRITE_CACHE, 0, max_binlog_size, 0); open(opt_name, LOG_BIN, 0, WRITE_CACHE, 0, max_binlog_size, 0, TRUE);
cleanup(); cleanup();
return 1; return 1;
} }
......
...@@ -172,6 +172,10 @@ public: ...@@ -172,6 +172,10 @@ public:
enum_log_type log_type, enum_log_type log_type,
const char *new_name, const char *new_name,
enum cache_type io_cache_type_arg); enum cache_type io_cache_type_arg);
bool init_and_set_log_file_name(const char *log_name,
const char *new_name,
enum_log_type log_type_arg,
enum cache_type io_cache_type_arg);
void init(enum_log_type log_type_arg, void init(enum_log_type log_type_arg,
enum cache_type io_cache_type_arg); enum cache_type io_cache_type_arg);
void close(uint exiting); void close(uint exiting);
...@@ -233,14 +237,15 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG ...@@ -233,14 +237,15 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
pthread_cond_t update_cond; pthread_cond_t update_cond;
ulonglong bytes_written; ulonglong bytes_written;
IO_CACHE index_file; IO_CACHE index_file;
char index_file_name[FN_REFLEN];
/* /*
purge_temp is a temp file used in purge_logs so that the index file purge_file is a temp file used in purge_logs so that the index file
can be updated before deleting files from disk, yielding better crash can be updated before deleting files from disk, yielding better crash
recovery. It is created on demand the first time purge_logs is called recovery. It is created on demand the first time purge_logs is called
and then reused for subsequent calls. It is cleaned up in cleanup(). and then reused for subsequent calls. It is cleaned up in cleanup().
*/ */
IO_CACHE purge_temp; IO_CACHE purge_index_file;
char index_file_name[FN_REFLEN]; char purge_index_file_name[FN_REFLEN];
/* /*
The max size before rotation (usable only if log_type == LOG_BIN: binary The max size before rotation (usable only if log_type == LOG_BIN: binary
logs and relay logs). logs and relay logs).
...@@ -349,9 +354,10 @@ public: ...@@ -349,9 +354,10 @@ public:
const char *new_name, const char *new_name,
enum cache_type io_cache_type_arg, enum cache_type io_cache_type_arg,
bool no_auto_events_arg, ulong max_size, bool no_auto_events_arg, ulong max_size,
bool null_created); bool null_created,
bool need_mutex);
bool open_index_file(const char *index_file_name_arg, bool open_index_file(const char *index_file_name_arg,
const char *log_name); const char *log_name, bool need_mutex);
/* Use this to start writing a new log file */ /* Use this to start writing a new log file */
void new_file(); void new_file();
...@@ -384,6 +390,16 @@ public: ...@@ -384,6 +390,16 @@ public:
ulonglong *decrease_log_space); ulonglong *decrease_log_space);
int purge_logs_before_date(time_t purge_time); int purge_logs_before_date(time_t purge_time);
int purge_first_log(Relay_log_info* rli, bool included); int purge_first_log(Relay_log_info* rli, bool included);
int set_purge_index_file_name(const char *base_file_name);
int open_purge_index_file(bool destroy);
bool is_inited_purge_index_file();
int close_purge_index_file();
int clean_purge_index_file();
int sync_purge_index_file();
int register_purge_index_entry(const char* entry);
int register_create_index_entry(const char* entry);
int purge_index_entry(THD *thd, ulonglong *decrease_log_space,
bool need_mutex);
bool reset_logs(THD* thd); bool reset_logs(THD* thd);
void close(uint exiting); void close(uint exiting);
......
...@@ -3932,7 +3932,7 @@ a file name for --log-bin-index option", opt_binlog_index_name); ...@@ -3932,7 +3932,7 @@ a file name for --log-bin-index option", opt_binlog_index_name);
my_free(opt_bin_logname, MYF(MY_ALLOW_ZERO_PTR)); my_free(opt_bin_logname, MYF(MY_ALLOW_ZERO_PTR));
opt_bin_logname=my_strdup(buf, MYF(0)); opt_bin_logname=my_strdup(buf, MYF(0));
} }
if (mysql_bin_log.open_index_file(opt_binlog_index_name, ln)) if (mysql_bin_log.open_index_file(opt_binlog_index_name, ln, TRUE))
{ {
unireg_abort(1); unireg_abort(1);
} }
...@@ -4096,7 +4096,7 @@ a file name for --log-bin-index option", opt_binlog_index_name); ...@@ -4096,7 +4096,7 @@ a file name for --log-bin-index option", opt_binlog_index_name);
} }
if (opt_bin_log && mysql_bin_log.open(opt_bin_logname, LOG_BIN, 0, if (opt_bin_log && mysql_bin_log.open(opt_bin_logname, LOG_BIN, 0,
WRITE_CACHE, 0, max_binlog_size, 0)) WRITE_CACHE, 0, max_binlog_size, 0, TRUE))
unireg_abort(1); unireg_abort(1);
#ifdef HAVE_REPLICATION #ifdef HAVE_REPLICATION
......
...@@ -182,10 +182,10 @@ a file name for --relay-log-index option", opt_relaylog_index_name); ...@@ -182,10 +182,10 @@ a file name for --relay-log-index option", opt_relaylog_index_name);
note, that if open() fails, we'll still have index file open note, that if open() fails, we'll still have index file open
but a destructor will take care of that but a destructor will take care of that
*/ */
if (rli->relay_log.open_index_file(opt_relaylog_index_name, ln) || if (rli->relay_log.open_index_file(opt_relaylog_index_name, ln, TRUE) ||
rli->relay_log.open(ln, LOG_BIN, 0, SEQ_READ_APPEND, 0, rli->relay_log.open(ln, LOG_BIN, 0, SEQ_READ_APPEND, 0,
(max_relay_log_size ? max_relay_log_size : (max_relay_log_size ? max_relay_log_size :
max_binlog_size), 1)) max_binlog_size), 1, TRUE))
{ {
pthread_mutex_unlock(&rli->data_lock); pthread_mutex_unlock(&rli->data_lock);
sql_print_error("Failed in open_log() called from init_relay_log_info()"); sql_print_error("Failed in open_log() called from init_relay_log_info()");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment