MDEV-13039 innodb_fast_shutdown=0 may fail to purge all undo log

When a slow shutdown is performed soon after spawning some work for
background threads that can create or commit transactions, it is possible
that new transactions are started or committed after the purge has finished.
This is violating the specification of innodb_fast_shutdown=0, namely that
the purge must be completed. (None of the history of the recent transactions
would be purged.)

Also, it is possible that the purge threads would exit in slow shutdown
while there exist active transactions, such as recovered incomplete
transactions that are being rolled back. Thus, the slow shutdown could
fail to purge some undo log that becomes purgeable after the transaction
commit or rollback.

srv_undo_sources: A flag that indicates if undo log can be generated
or the persistent, whether by background threads or by user SQL.
Even when this flag is clear, active transactions that already exist
in the system may be committed or rolled back.

innodb_shutdown(): Renamed from innobase_shutdown_for_mysql().
Do not return an error code; the operation never fails.
Clear the srv_undo_sources flag, and also ensure that the background
DROP TABLE queue is empty.

srv_purge_should_exit(): Do not allow the purge to exit if
srv_undo_sources are active or the background DROP TABLE queue is not
empty, or in slow shutdown, if any active transactions exist
(and are being rolled back).

srv_purge_coordinator_thread(): Remove some previous workarounds
for this bug.

innobase_start_or_create_for_mysql(): Set buf_page_cleaner_is_active
and srv_dict_stats_thread_active directly. Set srv_undo_sources before
starting the purge subsystem, to prevent immediate shutdown of the purge.
Create dict_stats_thread and fts_optimize_thread immediately
after setting srv_undo_sources, so that shutdown can use this flag to
determine if these subsystems were started.

dict_stats_shutdown(): Shut down dict_stats_thread. Backported from 10.2.

srv_shutdown_table_bg_threads(): Remove (unused).
This commit is contained in:
Marko Mäkelä 2017-06-08 15:43:06 +03:00
parent a9117c9008
commit 417434f12d
22 changed files with 339 additions and 402 deletions

View file

@ -0,0 +1,48 @@
create table t1 (a int not null, d varchar(15) not null, b
varchar(198) not null, c char(156),
fulltext ftsic(c)) engine=InnoDB
row_format=redundant;
insert into t1 values(123, 'abcdef', 'jghikl', 'mnop');
insert into t1 values(456, 'abcdef', 'jghikl', 'mnop');
insert into t1 values(789, 'abcdef', 'jghikl', 'mnop');
insert into t1 values(134, 'kasdfsdsadf', 'adfjlasdkfjasd', 'adfsadflkasdasdfljasdf');
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
SET GLOBAL innodb_file_per_table=OFF;
create table t2 (a int not null, d varchar(15) not null, b
varchar(198) not null, c char(156), fulltext ftsic(c)) engine=InnoDB
row_format=redundant;
insert into t2 select * from t1;
create table t3 (a int not null, d varchar(15) not null, b varchar(198),
c varchar(150), index k1(c(99), b(56)), index k2(b(5), c(10))) engine=InnoDB
row_format=redundant;
insert into t3 values(444, 'dddd', 'bbbbb', 'aaaaa');
insert into t3 values(555, 'eeee', 'ccccc', 'aaaaa');
SET GLOBAL innodb_fast_shutdown=0;
SELECT COUNT(*) FROM t1;
COUNT(*)
4096
SELECT COUNT(*) FROM t2;
COUNT(*)
4096
SELECT COUNT(*) FROM t3;
COUNT(*)
2
TRUNCATE TABLE t1;
ERROR HY000: Table 't1' is read only
TRUNCATE TABLE t2;
ERROR HY000: Table 't2' is read only
TRUNCATE TABLE t3;
ERROR HY000: Table 't3' is read only
TRUNCATE TABLE t1;
TRUNCATE TABLE t2;
TRUNCATE TABLE t3;
DROP TABLE t1,t2,t3;

View file

@ -0,0 +1,65 @@
--source include/innodb_page_size.inc
# Embedded mode doesn't allow restarting
--source include/not_embedded.inc
create table t1 (a int not null, d varchar(15) not null, b
varchar(198) not null, c char(156),
fulltext ftsic(c)) engine=InnoDB
row_format=redundant;
insert into t1 values(123, 'abcdef', 'jghikl', 'mnop');
insert into t1 values(456, 'abcdef', 'jghikl', 'mnop');
insert into t1 values(789, 'abcdef', 'jghikl', 'mnop');
insert into t1 values(134, 'kasdfsdsadf', 'adfjlasdkfjasd', 'adfsadflkasdasdfljasdf');
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
SET GLOBAL innodb_file_per_table=OFF;
create table t2 (a int not null, d varchar(15) not null, b
varchar(198) not null, c char(156), fulltext ftsic(c)) engine=InnoDB
row_format=redundant;
insert into t2 select * from t1;
create table t3 (a int not null, d varchar(15) not null, b varchar(198),
c varchar(150), index k1(c(99), b(56)), index k2(b(5), c(10))) engine=InnoDB
row_format=redundant;
insert into t3 values(444, 'dddd', 'bbbbb', 'aaaaa');
insert into t3 values(555, 'eeee', 'ccccc', 'aaaaa');
# read-only restart requires the change buffer to be empty; therefore we
# do a slow shutdown.
SET GLOBAL innodb_fast_shutdown=0;
--let $restart_parameters = --innodb-read-only
--source include/restart_mysqld.inc
SELECT COUNT(*) FROM t1;
SELECT COUNT(*) FROM t2;
SELECT COUNT(*) FROM t3;
--error ER_OPEN_AS_READONLY
TRUNCATE TABLE t1;
--error ER_OPEN_AS_READONLY
TRUNCATE TABLE t2;
--error ER_OPEN_AS_READONLY
TRUNCATE TABLE t3;
--let $restart_parameters =
--source include/restart_mysqld.inc
TRUNCATE TABLE t1;
TRUNCATE TABLE t2;
TRUNCATE TABLE t3;
# TODO: Shutdown, corrupt the SYS_TABLES.TYPE of the tables, restart
DROP TABLE t1,t2,t3;

View file

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -58,7 +58,7 @@ is set to TRUE by the page_cleaner thread when it is spawned and is set
back to FALSE at shutdown by the page_cleaner as well. Therefore no
need to protect it by a mutex. It is only ever read by the thread
doing the shutdown */
UNIV_INTERN ibool buf_page_cleaner_is_active = FALSE;
UNIV_INTERN bool buf_page_cleaner_is_active;
/** LRU flush batch is further divided into this chunk size to
reduce the wait time for the threads waiting for a clean block */
@ -2422,8 +2422,6 @@ DECLARE_THREAD(buf_flush_page_cleaner_thread)(
os_thread_pf(os_thread_get_curr_id()));
#endif /* UNIV_DEBUG_THREAD_CREATION */
buf_page_cleaner_is_active = TRUE;
while (srv_shutdown_state == SRV_SHUTDOWN_NONE) {
page_cleaner_sleep_if_needed(next_loop_time);
@ -2517,7 +2515,7 @@ DECLARE_THREAD(buf_flush_page_cleaner_thread)(
/* We have lived our life. Time to die. */
thread_exit:
buf_page_cleaner_is_active = FALSE;
buf_page_cleaner_is_active = false;
my_thread_end();
/* We count the number of threads in os_thread_exit(). A created

View file

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 2012, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -38,12 +38,18 @@ Created Apr 25, 2012 Vasil Dimov
/** Minimum time interval between stats recalc for a given table */
#define MIN_RECALC_INTERVAL 10 /* seconds */
#define SHUTTING_DOWN() (srv_shutdown_state != SRV_SHUTDOWN_NONE)
/** Event to wake up dict_stats_thread on dict_stats_recalc_pool_add()
or shutdown. Not protected by any mutex. */
UNIV_INTERN os_event_t dict_stats_event;
/** Variable to initiate shutdown the dict stats thread. Note we don't
use 'srv_shutdown_state' because we want to shutdown dict stats thread
before purge thread. */
static bool dict_stats_start_shutdown;
/** Event to wait for shutdown of the dict stats thread */
static os_event_t dict_stats_shutdown_event;
/** This mutex protects the "recalc_pool" variable. */
static ib_mutex_t recalc_pool_mutex;
#ifdef HAVE_PSI_INTERFACE
@ -217,11 +223,11 @@ Must be called before dict_stats_thread() is started. */
UNIV_INTERN
void
dict_stats_thread_init()
/*====================*/
{
ut_a(!srv_read_only_mode);
dict_stats_event = os_event_create();
dict_stats_shutdown_event = os_event_create();
/* The recalc_pool_mutex is acquired from:
1) the background stats gathering thread before any other latch
@ -260,6 +266,9 @@ dict_stats_thread_deinit()
os_event_free(dict_stats_event);
dict_stats_event = NULL;
os_event_free(dict_stats_shutdown_event);
dict_stats_shutdown_event = NULL;
dict_stats_start_shutdown = false;
}
/*****************************************************************//**
@ -349,9 +358,7 @@ DECLARE_THREAD(dict_stats_thread)(
my_thread_init();
ut_a(!srv_read_only_mode);
srv_dict_stats_thread_active = TRUE;
while (!SHUTTING_DOWN()) {
while (!dict_stats_start_shutdown) {
/* Wake up periodically even if not signaled. This is
because we may lose an event - if the below call to
@ -361,7 +368,7 @@ DECLARE_THREAD(dict_stats_thread)(
os_event_wait_time(
dict_stats_event, MIN_RECALC_INTERVAL * 1000000);
if (SHUTTING_DOWN()) {
if (dict_stats_start_shutdown) {
break;
}
@ -370,8 +377,9 @@ DECLARE_THREAD(dict_stats_thread)(
os_event_reset(dict_stats_event);
}
srv_dict_stats_thread_active = FALSE;
srv_dict_stats_thread_active = false;
os_event_set(dict_stats_shutdown_event);
my_thread_end();
/* We count the number of threads in os_thread_exit(). A created
thread should always use that to exit instead of return(). */
@ -379,3 +387,12 @@ DECLARE_THREAD(dict_stats_thread)(
OS_THREAD_DUMMY_RETURN;
}
/** Shut down the dict_stats_thread. */
void
dict_stats_shutdown()
{
dict_stats_start_shutdown = true;
os_event_set(dict_stats_event);
os_event_wait(dict_stats_shutdown_event);
}

View file

@ -1142,14 +1142,11 @@ innobase_drop_database(
the path is used as the database name:
for example, in 'mysql/data/test' the
database name is 'test' */
/*******************************************************************//**
Closes an InnoDB database. */
/** Shut down the InnoDB storage engine.
@return 0 */
static
int
innobase_end(
/*=========*/
handlerton* hton, /* in: Innodb handlerton */
ha_panic_function type);
innobase_end(handlerton*, ha_panic_function);
/*****************************************************************//**
Creates an InnoDB transaction struct for the thd if it does not yet have one.
@ -3651,21 +3648,13 @@ error:
DBUG_RETURN(TRUE);
}
/*******************************************************************//**
Closes an InnoDB database.
@return TRUE if error */
/** Shut down the InnoDB storage engine.
@return 0 */
static
int
innobase_end(
/*=========*/
handlerton* hton, /*!< in/out: InnoDB handlerton */
ha_panic_function type MY_ATTRIBUTE((unused)))
/*!< in: ha_panic() parameter */
innobase_end(handlerton*, ha_panic_function)
{
int err= 0;
DBUG_ENTER("innobase_end");
DBUG_ASSERT(hton == innodb_hton_ptr);
if (innodb_inited) {
@ -3682,9 +3671,7 @@ innobase_end(
innodb_inited = 0;
hash_table_free(innobase_open_tables);
innobase_open_tables = NULL;
if (innobase_shutdown_for_mysql() != DB_SUCCESS) {
err = 1;
}
innodb_shutdown();
srv_free_paths_and_sizes();
my_free(internal_innobase_data_file_path);
mysql_mutex_destroy(&innobase_share_mutex);
@ -3693,7 +3680,7 @@ innobase_end(
mysql_mutex_destroy(&pending_checkpoint_mutex);
}
DBUG_RETURN(err);
DBUG_RETURN(0);
}
/****************************************************************//**

View file

@ -34,7 +34,7 @@ Created 11/5/1995 Heikki Tuuri
#include "buf0types.h"
/** Flag indicating if the page_cleaner is in active state. */
extern ibool buf_page_cleaner_is_active;
extern bool buf_page_cleaner_is_active;
/********************************************************************//**
Remove a block from the flush list of modified blocks. */

View file

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 2012, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -122,6 +122,10 @@ DECLARE_THREAD(dict_stats_thread)(
void* arg); /*!< in: a dummy parameter
required by os_thread_create */
/** Shut down the dict_stats_thread. */
void
dict_stats_shutdown();
# ifndef UNIV_NONINL
# include "dict0stats_bg.ic"
# endif

View file

@ -398,7 +398,7 @@ extern ibool srv_error_monitor_active;
extern ibool srv_buf_dump_thread_active;
/* TRUE during the lifetime of the stats thread */
extern ibool srv_dict_stats_thread_active;
extern bool srv_dict_stats_thread_active;
extern ulong srv_n_spin_wait_rounds;
extern ulong srv_n_free_tickets_to_enter;

View file

@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -75,22 +76,12 @@ are not found and the user wants.
@return DB_SUCCESS or error code */
UNIV_INTERN
dberr_t
innobase_start_or_create_for_mysql(void);
/*====================================*/
/****************************************************************//**
Shuts down the Innobase database.
@return DB_SUCCESS or error code */
UNIV_INTERN
dberr_t
innobase_shutdown_for_mysql(void);
innobase_start_or_create_for_mysql();
/********************************************************************
Signal all per-table background threads to shutdown, and wait for them to do
so. */
/** Shut down InnoDB. */
UNIV_INTERN
void
srv_shutdown_table_bg_threads(void);
/*=============================*/
innodb_shutdown();
/*************************************************************//**
Copy the file path component of the physical file to parameter. It will
@ -158,6 +149,9 @@ enum srv_shutdown_state {
SRV_SHUTDOWN_EXIT_THREADS/*!< Exit all threads */
};
/** Whether any undo log records can be generated */
extern bool srv_undo_sources;
/** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to
SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */
extern enum srv_shutdown_state srv_shutdown_state;

View file

@ -84,7 +84,7 @@ UNIV_INTERN ibool srv_error_monitor_active = FALSE;
UNIV_INTERN ibool srv_buf_dump_thread_active = FALSE;
UNIV_INTERN ibool srv_dict_stats_thread_active = FALSE;
UNIV_INTERN bool srv_dict_stats_thread_active;
UNIV_INTERN const char* srv_main_thread_op_info = "";
@ -2424,31 +2424,29 @@ suspend_thread:
goto loop;
}
/*********************************************************************//**
Check if purge should stop.
@return true if it should shutdown. */
/** Check if purge should stop.
@param[in] n_purged pages purged in the last batch
@return whether purge should exit */
static
bool
srv_purge_should_exit(
/*==============*/
ulint n_purged) /*!< in: pages purged in last batch */
srv_purge_should_exit(ulint n_purged)
{
switch (srv_shutdown_state) {
case SRV_SHUTDOWN_NONE:
/* Normal operation. */
break;
ut_ad(srv_shutdown_state == SRV_SHUTDOWN_NONE
|| srv_shutdown_state == SRV_SHUTDOWN_CLEANUP);
case SRV_SHUTDOWN_CLEANUP:
case SRV_SHUTDOWN_EXIT_THREADS:
/* Exit unless slow shutdown requested or all done. */
return(srv_fast_shutdown != 0 || n_purged == 0);
case SRV_SHUTDOWN_LAST_PHASE:
case SRV_SHUTDOWN_FLUSH_PHASE:
ut_error;
if (srv_undo_sources) {
return(false);
}
return(false);
if (srv_fast_shutdown) {
return(true);
}
/* Slow shutdown was requested. */
if (n_purged) {
/* The previous round still did some work. */
return(false);
}
/* Exit if there are no active transactions to roll back. */
return(trx_sys_any_active_transactions() == 0);
}
/*********************************************************************//**
@ -2709,7 +2707,7 @@ srv_purge_coordinator_suspend(
}
rw_lock_x_unlock(&purge_sys->latch);
} while (stop);
} while (stop && srv_undo_sources);
srv_resume_thread(slot, 0, false);
}
@ -2760,6 +2758,7 @@ DECLARE_THREAD(srv_purge_coordinator_thread)(
purge didn't purge any records then wait for activity. */
if (srv_shutdown_state == SRV_SHUTDOWN_NONE
&& srv_undo_sources
&& (purge_sys->state == PURGE_STATE_STOP
|| n_total_purged == 0)) {
@ -2776,36 +2775,8 @@ DECLARE_THREAD(srv_purge_coordinator_thread)(
rseg_history_len = srv_do_purge(
srv_n_purge_threads, &n_total_purged);
} while (!srv_purge_should_exit(n_total_purged));
/* Ensure that we don't jump out of the loop unless the
exit condition is satisfied. */
ut_a(srv_purge_should_exit(n_total_purged));
ulint n_pages_purged = ULINT_MAX;
/* Ensure that all records are purged if it is not a fast shutdown.
This covers the case where a record can be added after we exit the
loop above. */
while (srv_fast_shutdown == 0 && n_pages_purged > 0) {
n_pages_purged = trx_purge(1, srv_purge_batch_size, false);
}
/* This trx_purge is called to remove any undo records (added by
background threads) after completion of the above loop. When
srv_fast_shutdown != 0, a large batch size can cause significant
delay in shutdown ,so reducing the batch size to magic number 20
(which was default in 5.5), which we hope will be sufficient to
remove all the undo records */
const uint temp_batch_size = 20;
n_pages_purged = trx_purge(1, srv_purge_batch_size <= temp_batch_size
? srv_purge_batch_size : temp_batch_size,
true);
ut_a(n_pages_purged == 0 || srv_fast_shutdown != 0);
/* The task queue should always be empty, independent of fast
shutdown state. */
ut_a(srv_get_task_queue_length() == 0);

View file

@ -121,7 +121,10 @@ UNIV_INTERN ibool srv_is_being_started = FALSE;
/** TRUE if the server was successfully started */
UNIV_INTERN ibool srv_was_started = FALSE;
/** TRUE if innobase_start_or_create_for_mysql() has been called */
static ibool srv_start_has_been_called = FALSE;
static ibool srv_start_has_been_called;
/** Whether any undo log records can be generated */
UNIV_INTERN bool srv_undo_sources;
/** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to
SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */
@ -1565,8 +1568,7 @@ are not found and the user wants.
@return DB_SUCCESS or error code */
UNIV_INTERN
dberr_t
innobase_start_or_create_for_mysql(void)
/*====================================*/
innobase_start_or_create_for_mysql()
{
ibool create_new_db;
lsn_t min_flushed_lsn;
@ -2705,8 +2707,8 @@ files_checked:
}
}
srv_startup_is_before_trx_rollback_phase = FALSE;
recv_recovery_rollback_active();
srv_startup_is_before_trx_rollback_phase = FALSE;
/* It is possible that file_format tag has never
been set. In this case we initialize it to minimum
@ -2827,6 +2829,16 @@ files_checked:
srv_master_thread,
NULL, thread_ids + (1 + SRV_MAX_N_IO_THREADS));
thread_started[1 + SRV_MAX_N_IO_THREADS] = true;
srv_undo_sources = true;
/* Create the dict stats gathering thread */
srv_dict_stats_thread_active = true;
dict_stats_thread_handle = os_thread_create(
dict_stats_thread, NULL, NULL);
dict_stats_thread_started = true;
/* Create the thread that will optimize the FTS sub-system. */
fts_optimize_init();
}
if (!srv_read_only_mode
@ -2856,6 +2868,7 @@ files_checked:
}
if (!srv_read_only_mode) {
buf_page_cleaner_is_active = true;
buf_flush_page_cleaner_thread_handle = os_thread_create(buf_flush_page_cleaner_thread, NULL, NULL);
buf_flush_page_cleaner_thread_started = true;
}
@ -2885,13 +2898,6 @@ files_checked:
/* Create the buffer pool dump/load thread */
buf_dump_thread_handle = os_thread_create(buf_dump_thread, NULL, NULL);
buf_dump_thread_started = true;
/* Create the dict stats gathering thread */
dict_stats_thread_handle = os_thread_create(dict_stats_thread, NULL, NULL);
dict_stats_thread_started = true;
/* Create the thread that will optimize the FTS sub-system. */
fts_optimize_init();
}
srv_was_started = TRUE;
@ -2929,13 +2935,10 @@ srv_fts_close(void)
}
#endif
/****************************************************************//**
Shuts down the InnoDB database.
@return DB_SUCCESS or error code */
/** Shut down InnoDB. */
UNIV_INTERN
dberr_t
innobase_shutdown_for_mysql(void)
/*=============================*/
void
innodb_shutdown()
{
ulint i;
@ -2945,15 +2948,20 @@ innobase_shutdown_for_mysql(void)
"Shutting down an improperly started, "
"or created database!");
}
return(DB_SUCCESS);
}
if (!srv_read_only_mode) {
if (srv_undo_sources) {
ut_ad(!srv_read_only_mode);
/* Shutdown the FTS optimize sub system. */
fts_optimize_start_shutdown();
fts_optimize_end();
dict_stats_shutdown();
while (row_get_background_drop_list_len_low()) {
srv_wake_master_thread();
os_thread_yield();
}
srv_undo_sources = false;
}
/* 1. Flush the buffer pool to disk, write the current lsn to
@ -3156,89 +3164,9 @@ innobase_shutdown_for_mysql(void)
srv_was_started = FALSE;
srv_start_has_been_called = FALSE;
return(DB_SUCCESS);
}
#endif /* !UNIV_HOTBACKUP */
/********************************************************************
Signal all per-table background threads to shutdown, and wait for them to do
so. */
UNIV_INTERN
void
srv_shutdown_table_bg_threads(void)
/*===============================*/
{
dict_table_t* table;
dict_table_t* first;
dict_table_t* last = NULL;
mutex_enter(&dict_sys->mutex);
/* Signal all threads that they should stop. */
table = UT_LIST_GET_FIRST(dict_sys->table_LRU);
first = table;
while (table) {
dict_table_t* next;
fts_t* fts = table->fts;
if (fts != NULL) {
fts_start_shutdown(table, fts);
}
next = UT_LIST_GET_NEXT(table_LRU, table);
if (!next) {
last = table;
}
table = next;
}
/* We must release dict_sys->mutex here; if we hold on to it in the
loop below, we will deadlock if any of the background threads try to
acquire it (for example, the FTS thread by calling que_eval_sql).
Releasing it here and going through dict_sys->table_LRU without
holding it is safe because:
a) MySQL only starts the shutdown procedure after all client
threads have been disconnected and no new ones are accepted, so no
new tables are added or old ones dropped.
b) Despite its name, the list is not LRU, and the order stays
fixed.
To safeguard against the above assumptions ever changing, we store
the first and last items in the list above, and then check that
they've stayed the same below. */
mutex_exit(&dict_sys->mutex);
/* Wait for the threads of each table to stop. This is not inside
the above loop, because by signaling all the threads first we can
overlap their shutting down delays. */
table = UT_LIST_GET_FIRST(dict_sys->table_LRU);
ut_a(first == table);
while (table) {
dict_table_t* next;
fts_t* fts = table->fts;
if (fts != NULL) {
fts_shutdown(table, fts);
}
next = UT_LIST_GET_NEXT(table_LRU, table);
if (table == last) {
ut_a(!next);
}
table = next;
}
}
/*****************************************************************//**
Get the meta-data filename from the table name. */
UNIV_INTERN

View file

@ -243,6 +243,19 @@ trx_purge_add_update_undo_to_history(
hist_size + undo->size, MLOG_4BYTES, mtr);
}
/* Before any transaction-generating background threads or the
purge have been started, recv_recovery_rollback_active() can
start transactions in row_merge_drop_temp_indexes() and
fts_drop_orphaned_tables(), and roll back recovered transactions.
After the purge thread has been given permission to exit,
in fast shutdown, we may roll back transactions (trx->undo_no==0)
in THD::cleanup() invoked from unlink_thd(). */
ut_ad(srv_undo_sources
|| ((srv_startup_is_before_trx_rollback_phase
|| trx_rollback_or_clean_is_active)
&& purge_sys->state == PURGE_STATE_INIT)
|| (trx->undo_no == 0 && srv_fast_shutdown));
/* Add the log as the first in the history list */
flst_add_first(rseg_header + TRX_RSEG_HISTORY,
undo_header + TRX_UNDO_HISTORY_NODE, mtr);

View file

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -58,7 +58,7 @@ is set to TRUE by the page_cleaner thread when it is spawned and is set
back to FALSE at shutdown by the page_cleaner as well. Therefore no
need to protect it by a mutex. It is only ever read by the thread
doing the shutdown */
UNIV_INTERN ibool buf_page_cleaner_is_active = FALSE;
UNIV_INTERN bool buf_page_cleaner_is_active;
/** Flag indicating if the lru_manager is in active state. */
UNIV_INTERN bool buf_lru_manager_is_active = false;
@ -2718,8 +2718,6 @@ DECLARE_THREAD(buf_flush_page_cleaner_thread)(
os_thread_pf(os_thread_get_curr_id()));
#endif /* UNIV_DEBUG_THREAD_CREATION */
buf_page_cleaner_is_active = TRUE;
while (srv_shutdown_state == SRV_SHUTDOWN_NONE) {
ulint page_cleaner_sleep_time;
@ -2829,7 +2827,7 @@ DECLARE_THREAD(buf_flush_page_cleaner_thread)(
/* We have lived our life. Time to die. */
thread_exit:
buf_page_cleaner_is_active = FALSE;
buf_page_cleaner_is_active = false;
my_thread_end();
/* We count the number of threads in os_thread_exit(). A created

View file

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 2012, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -38,12 +38,18 @@ Created Apr 25, 2012 Vasil Dimov
/** Minimum time interval between stats recalc for a given table */
#define MIN_RECALC_INTERVAL 10 /* seconds */
#define SHUTTING_DOWN() (srv_shutdown_state != SRV_SHUTDOWN_NONE)
/** Event to wake up dict_stats_thread on dict_stats_recalc_pool_add()
or shutdown. Not protected by any mutex. */
UNIV_INTERN os_event_t dict_stats_event;
/** Variable to initiate shutdown the dict stats thread. Note we don't
use 'srv_shutdown_state' because we want to shutdown dict stats thread
before purge thread. */
static bool dict_stats_start_shutdown;
/** Event to wait for shutdown of the dict stats thread */
static os_event_t dict_stats_shutdown_event;
/** This mutex protects the "recalc_pool" variable. */
static ib_mutex_t recalc_pool_mutex;
#ifdef HAVE_PSI_INTERFACE
@ -207,11 +213,11 @@ Must be called before dict_stats_thread() is started. */
UNIV_INTERN
void
dict_stats_thread_init()
/*====================*/
{
ut_a(!srv_read_only_mode);
dict_stats_event = os_event_create();
dict_stats_shutdown_event = os_event_create();
/* The recalc_pool_mutex is acquired from:
1) the background stats gathering thread before any other latch
@ -250,6 +256,9 @@ dict_stats_thread_deinit()
os_event_free(dict_stats_event);
dict_stats_event = NULL;
os_event_free(dict_stats_shutdown_event);
dict_stats_shutdown_event = NULL;
dict_stats_start_shutdown = false;
}
/*****************************************************************//**
@ -339,9 +348,7 @@ DECLARE_THREAD(dict_stats_thread)(
my_thread_init();
ut_a(!srv_read_only_mode);
srv_dict_stats_thread_active = TRUE;
while (!SHUTTING_DOWN()) {
while (!dict_stats_start_shutdown) {
/* Wake up periodically even if not signaled. This is
because we may lose an event - if the below call to
@ -351,7 +358,7 @@ DECLARE_THREAD(dict_stats_thread)(
os_event_wait_time(
dict_stats_event, MIN_RECALC_INTERVAL * 1000000);
if (SHUTTING_DOWN()) {
if (dict_stats_start_shutdown) {
break;
}
@ -360,8 +367,9 @@ DECLARE_THREAD(dict_stats_thread)(
os_event_reset(dict_stats_event);
}
srv_dict_stats_thread_active = FALSE;
srv_dict_stats_thread_active = false;
os_event_set(dict_stats_shutdown_event);
my_thread_end();
/* We count the number of threads in os_thread_exit(). A created
thread should always use that to exit instead of return(). */
@ -369,3 +377,12 @@ DECLARE_THREAD(dict_stats_thread)(
OS_THREAD_DUMMY_RETURN;
}
/** Shut down the dict_stats_thread. */
void
dict_stats_shutdown()
{
dict_stats_start_shutdown = true;
os_event_set(dict_stats_event);
os_event_wait(dict_stats_shutdown_event);
}

View file

@ -1366,14 +1366,11 @@ innobase_drop_database(
the path is used as the database name:
for example, in 'mysql/data/test' the
database name is 'test' */
/*******************************************************************//**
Closes an InnoDB database. */
/** Shut down the InnoDB storage engine.
@return 0 */
static
int
innobase_end(
/*=========*/
handlerton* hton, /* in: Innodb handlerton */
ha_panic_function type);
innobase_end(handlerton*, ha_panic_function);
#if NOT_USED
/*****************************************************************//**
@ -4140,21 +4137,13 @@ error:
DBUG_RETURN(TRUE);
}
/*******************************************************************//**
Closes an InnoDB database.
@return TRUE if error */
/** Shut down the InnoDB storage engine.
@return 0 */
static
int
innobase_end(
/*=========*/
handlerton* hton, /*!< in/out: InnoDB handlerton */
ha_panic_function type MY_ATTRIBUTE((unused)))
/*!< in: ha_panic() parameter */
innobase_end(handlerton*, ha_panic_function)
{
int err= 0;
DBUG_ENTER("innobase_end");
DBUG_ASSERT(hton == innodb_hton_ptr);
if (innodb_inited) {
@ -4171,9 +4160,7 @@ innobase_end(
innodb_inited = 0;
hash_table_free(innobase_open_tables);
innobase_open_tables = NULL;
if (innobase_shutdown_for_mysql() != DB_SUCCESS) {
err = 1;
}
innodb_shutdown();
srv_free_paths_and_sizes();
my_free(internal_innobase_data_file_path);
mysql_mutex_destroy(&innobase_share_mutex);
@ -4182,7 +4169,7 @@ innobase_end(
mysql_mutex_destroy(&pending_checkpoint_mutex);
}
DBUG_RETURN(err);
DBUG_RETURN(0);
}
/****************************************************************//**

View file

@ -34,7 +34,7 @@ Created 11/5/1995 Heikki Tuuri
#include "buf0types.h"
/** Flag indicating if the page_cleaner is in active state. */
extern ibool buf_page_cleaner_is_active;
extern bool buf_page_cleaner_is_active;
/** Flag indicating if the lru_manager is in active state. */
extern bool buf_lru_manager_is_active;

View file

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 2012, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -122,6 +122,10 @@ DECLARE_THREAD(dict_stats_thread)(
void* arg); /*!< in: a dummy parameter
required by os_thread_create */
/** Shut down the dict_stats_thread. */
void
dict_stats_shutdown();
# ifndef UNIV_NONINL
# include "dict0stats_bg.ic"
# endif

View file

@ -499,7 +499,7 @@ extern ibool srv_error_monitor_active;
extern ibool srv_buf_dump_thread_active;
/* TRUE during the lifetime of the stats thread */
extern ibool srv_dict_stats_thread_active;
extern bool srv_dict_stats_thread_active;
extern ulong srv_n_spin_wait_rounds;
extern ulong srv_n_free_tickets_to_enter;

View file

@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -75,22 +76,12 @@ are not found and the user wants.
@return DB_SUCCESS or error code */
UNIV_INTERN
dberr_t
innobase_start_or_create_for_mysql(void);
/*====================================*/
/****************************************************************//**
Shuts down the Innobase database.
@return DB_SUCCESS or error code */
UNIV_INTERN
dberr_t
innobase_shutdown_for_mysql(void);
innobase_start_or_create_for_mysql();
/********************************************************************
Signal all per-table background threads to shutdown, and wait for them to do
so. */
/** Shut down InnoDB. */
UNIV_INTERN
void
srv_shutdown_table_bg_threads(void);
/*=============================*/
innodb_shutdown();
/*************************************************************//**
Copy the file path component of the physical file to parameter. It will
@ -158,6 +149,9 @@ enum srv_shutdown_state {
SRV_SHUTDOWN_EXIT_THREADS/*!< Exit all threads */
};
/** Whether any undo log records can be generated */
extern bool srv_undo_sources;
/** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to
SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */
extern enum srv_shutdown_state srv_shutdown_state;

View file

@ -92,7 +92,7 @@ UNIV_INTERN ibool srv_error_monitor_active = FALSE;
UNIV_INTERN ibool srv_buf_dump_thread_active = FALSE;
UNIV_INTERN ibool srv_dict_stats_thread_active = FALSE;
UNIV_INTERN bool srv_dict_stats_thread_active;
UNIV_INTERN const char* srv_main_thread_op_info = "";
@ -3097,31 +3097,29 @@ suspend_thread:
goto loop;
}
/*********************************************************************//**
Check if purge should stop.
@return true if it should shutdown. */
/** Check if purge should stop.
@param[in] n_purged pages purged in the last batch
@return whether purge should exit */
static
bool
srv_purge_should_exit(
/*==============*/
ulint n_purged) /*!< in: pages purged in last batch */
srv_purge_should_exit(ulint n_purged)
{
switch (srv_shutdown_state) {
case SRV_SHUTDOWN_NONE:
/* Normal operation. */
break;
ut_ad(srv_shutdown_state == SRV_SHUTDOWN_NONE
|| srv_shutdown_state == SRV_SHUTDOWN_CLEANUP);
case SRV_SHUTDOWN_CLEANUP:
case SRV_SHUTDOWN_EXIT_THREADS:
/* Exit unless slow shutdown requested or all done. */
return(srv_fast_shutdown != 0 || n_purged == 0);
case SRV_SHUTDOWN_LAST_PHASE:
case SRV_SHUTDOWN_FLUSH_PHASE:
ut_error;
if (srv_undo_sources) {
return(false);
}
return(false);
if (srv_fast_shutdown) {
return(true);
}
/* Slow shutdown was requested. */
if (n_purged) {
/* The previous round still did some work. */
return(false);
}
/* Exit if there are no active transactions to roll back. */
return(trx_sys_any_active_transactions() == 0);
}
/*********************************************************************//**
@ -3396,7 +3394,7 @@ srv_purge_coordinator_suspend(
}
rw_lock_x_unlock(&purge_sys->latch);
} while (stop);
} while (stop && srv_undo_sources);
srv_resume_thread(slot, 0, false);
}
@ -3450,6 +3448,7 @@ DECLARE_THREAD(srv_purge_coordinator_thread)(
purge didn't purge any records then wait for activity. */
if (srv_shutdown_state == SRV_SHUTDOWN_NONE
&& srv_undo_sources
&& (purge_sys->state == PURGE_STATE_STOP
|| n_total_purged == 0)) {
@ -3470,36 +3469,8 @@ DECLARE_THREAD(srv_purge_coordinator_thread)(
srv_n_purge_threads, &n_total_purged);
srv_inc_activity_count();
} while (!srv_purge_should_exit(n_total_purged));
/* Ensure that we don't jump out of the loop unless the
exit condition is satisfied. */
ut_a(srv_purge_should_exit(n_total_purged));
ulint n_pages_purged = ULINT_MAX;
/* Ensure that all records are purged if it is not a fast shutdown.
This covers the case where a record can be added after we exit the
loop above. */
while (srv_fast_shutdown == 0 && n_pages_purged > 0) {
n_pages_purged = trx_purge(1, srv_purge_batch_size, false);
}
/* This trx_purge is called to remove any undo records (added by
background threads) after completion of the above loop. When
srv_fast_shutdown != 0, a large batch size can cause significant
delay in shutdown ,so reducing the batch size to magic number 20
(which was default in 5.5), which we hope will be sufficient to
remove all the undo records */
const uint temp_batch_size = 20;
n_pages_purged = trx_purge(1, srv_purge_batch_size <= temp_batch_size
? srv_purge_batch_size : temp_batch_size,
true);
ut_a(n_pages_purged == 0 || srv_fast_shutdown != 0);
/* The task queue should always be empty, independent of fast
shutdown state. */
ut_a(srv_get_task_queue_length() == 0);

View file

@ -123,7 +123,10 @@ UNIV_INTERN ibool srv_is_being_started = FALSE;
/** TRUE if the server was successfully started */
UNIV_INTERN ibool srv_was_started = FALSE;
/** TRUE if innobase_start_or_create_for_mysql() has been called */
static ibool srv_start_has_been_called = FALSE;
static ibool srv_start_has_been_called;
/** Whether any undo log records can be generated */
UNIV_INTERN bool srv_undo_sources;
/** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to
SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */
@ -1623,8 +1626,7 @@ are not found and the user wants.
@return DB_SUCCESS or error code */
UNIV_INTERN
dberr_t
innobase_start_or_create_for_mysql(void)
/*====================================*/
innobase_start_or_create_for_mysql()
{
ibool create_new_db;
lsn_t min_flushed_lsn;
@ -2777,8 +2779,8 @@ files_checked:
}
}
srv_startup_is_before_trx_rollback_phase = FALSE;
recv_recovery_rollback_active();
srv_startup_is_before_trx_rollback_phase = FALSE;
/* It is possible that file_format tag has never
been set. In this case we initialize it to minimum
@ -2905,6 +2907,16 @@ files_checked:
srv_master_thread,
NULL, thread_ids + (1 + SRV_MAX_N_IO_THREADS));
thread_started[1 + SRV_MAX_N_IO_THREADS] = true;
srv_undo_sources = true;
/* Create the dict stats gathering thread */
srv_dict_stats_thread_active = true;
dict_stats_thread_handle = os_thread_create(
dict_stats_thread, NULL, NULL);
dict_stats_thread_started = true;
/* Create the thread that will optimize the FTS sub-system. */
fts_optimize_init();
}
if (!srv_read_only_mode
@ -2934,6 +2946,7 @@ files_checked:
}
if (!srv_read_only_mode) {
buf_page_cleaner_is_active = true;
buf_flush_page_cleaner_thread_handle = os_thread_create(buf_flush_page_cleaner_thread, NULL, NULL);
buf_flush_page_cleaner_thread_started = true;
}
@ -2973,13 +2986,6 @@ files_checked:
/* Create the buffer pool dump/load thread */
buf_dump_thread_handle = os_thread_create(buf_dump_thread, NULL, NULL);
buf_dump_thread_started = true;
/* Create the dict stats gathering thread */
dict_stats_thread_handle = os_thread_create(dict_stats_thread, NULL, NULL);
dict_stats_thread_started = true;
/* Create the thread that will optimize the FTS sub-system. */
fts_optimize_init();
}
srv_was_started = TRUE;
@ -3017,13 +3023,10 @@ srv_fts_close(void)
}
#endif
/****************************************************************//**
Shuts down the InnoDB database.
@return DB_SUCCESS or error code */
/** Shut down InnoDB. */
UNIV_INTERN
dberr_t
innobase_shutdown_for_mysql(void)
/*=============================*/
void
innodb_shutdown()
{
ulint i;
@ -3033,15 +3036,20 @@ innobase_shutdown_for_mysql(void)
"Shutting down an improperly started, "
"or created database!");
}
return(DB_SUCCESS);
}
if (!srv_read_only_mode) {
if (srv_undo_sources) {
ut_ad(!srv_read_only_mode);
/* Shutdown the FTS optimize sub system. */
fts_optimize_start_shutdown();
fts_optimize_end();
dict_stats_shutdown();
while (row_get_background_drop_list_len_low()) {
srv_wake_master_thread();
os_thread_yield();
}
srv_undo_sources = false;
}
/* 1. Flush the buffer pool to disk, write the current lsn to
@ -3245,89 +3253,9 @@ innobase_shutdown_for_mysql(void)
srv_was_started = FALSE;
srv_start_has_been_called = FALSE;
return(DB_SUCCESS);
}
#endif /* !UNIV_HOTBACKUP */
/********************************************************************
Signal all per-table background threads to shutdown, and wait for them to do
so. */
UNIV_INTERN
void
srv_shutdown_table_bg_threads(void)
/*===============================*/
{
dict_table_t* table;
dict_table_t* first;
dict_table_t* last = NULL;
mutex_enter(&dict_sys->mutex);
/* Signal all threads that they should stop. */
table = UT_LIST_GET_FIRST(dict_sys->table_LRU);
first = table;
while (table) {
dict_table_t* next;
fts_t* fts = table->fts;
if (fts != NULL) {
fts_start_shutdown(table, fts);
}
next = UT_LIST_GET_NEXT(table_LRU, table);
if (!next) {
last = table;
}
table = next;
}
/* We must release dict_sys->mutex here; if we hold on to it in the
loop below, we will deadlock if any of the background threads try to
acquire it (for example, the FTS thread by calling que_eval_sql).
Releasing it here and going through dict_sys->table_LRU without
holding it is safe because:
a) MySQL only starts the shutdown procedure after all client
threads have been disconnected and no new ones are accepted, so no
new tables are added or old ones dropped.
b) Despite its name, the list is not LRU, and the order stays
fixed.
To safeguard against the above assumptions ever changing, we store
the first and last items in the list above, and then check that
they've stayed the same below. */
mutex_exit(&dict_sys->mutex);
/* Wait for the threads of each table to stop. This is not inside
the above loop, because by signaling all the threads first we can
overlap their shutting down delays. */
table = UT_LIST_GET_FIRST(dict_sys->table_LRU);
ut_a(first == table);
while (table) {
dict_table_t* next;
fts_t* fts = table->fts;
if (fts != NULL) {
fts_shutdown(table, fts);
}
next = UT_LIST_GET_NEXT(table_LRU, table);
if (table == last) {
ut_a(!next);
}
table = next;
}
}
/*****************************************************************//**
Get the meta-data filename from the table name. */
UNIV_INTERN

View file

@ -247,6 +247,19 @@ trx_purge_add_update_undo_to_history(
hist_size + undo->size, MLOG_4BYTES, mtr);
}
/* Before any transaction-generating background threads or the
purge have been started, recv_recovery_rollback_active() can
start transactions in row_merge_drop_temp_indexes() and
fts_drop_orphaned_tables(), and roll back recovered transactions.
After the purge thread has been given permission to exit,
in fast shutdown, we may roll back transactions (trx->undo_no==0)
in THD::cleanup() invoked from unlink_thd(). */
ut_ad(srv_undo_sources
|| ((srv_startup_is_before_trx_rollback_phase
|| trx_rollback_or_clean_is_active)
&& purge_sys->state == PURGE_STATE_INIT)
|| (trx->undo_no == 0 && srv_fast_shutdown));
/* Add the log as the first in the history list */
flst_add_first(rseg_header + TRX_RSEG_HISTORY,
undo_header + TRX_UNDO_HISTORY_NODE, mtr);