Skip to content

Commit 417434f

Browse files
committed
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).
1 parent a9117c9 commit 417434f

File tree

22 files changed

+339
-402
lines changed

22 files changed

+339
-402
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
create table t1 (a int not null, d varchar(15) not null, b
2+
varchar(198) not null, c char(156),
3+
fulltext ftsic(c)) engine=InnoDB
4+
row_format=redundant;
5+
insert into t1 values(123, 'abcdef', 'jghikl', 'mnop');
6+
insert into t1 values(456, 'abcdef', 'jghikl', 'mnop');
7+
insert into t1 values(789, 'abcdef', 'jghikl', 'mnop');
8+
insert into t1 values(134, 'kasdfsdsadf', 'adfjlasdkfjasd', 'adfsadflkasdasdfljasdf');
9+
insert into t1 select * from t1;
10+
insert into t1 select * from t1;
11+
insert into t1 select * from t1;
12+
insert into t1 select * from t1;
13+
insert into t1 select * from t1;
14+
insert into t1 select * from t1;
15+
insert into t1 select * from t1;
16+
insert into t1 select * from t1;
17+
insert into t1 select * from t1;
18+
insert into t1 select * from t1;
19+
SET GLOBAL innodb_file_per_table=OFF;
20+
create table t2 (a int not null, d varchar(15) not null, b
21+
varchar(198) not null, c char(156), fulltext ftsic(c)) engine=InnoDB
22+
row_format=redundant;
23+
insert into t2 select * from t1;
24+
create table t3 (a int not null, d varchar(15) not null, b varchar(198),
25+
c varchar(150), index k1(c(99), b(56)), index k2(b(5), c(10))) engine=InnoDB
26+
row_format=redundant;
27+
insert into t3 values(444, 'dddd', 'bbbbb', 'aaaaa');
28+
insert into t3 values(555, 'eeee', 'ccccc', 'aaaaa');
29+
SET GLOBAL innodb_fast_shutdown=0;
30+
SELECT COUNT(*) FROM t1;
31+
COUNT(*)
32+
4096
33+
SELECT COUNT(*) FROM t2;
34+
COUNT(*)
35+
4096
36+
SELECT COUNT(*) FROM t3;
37+
COUNT(*)
38+
2
39+
TRUNCATE TABLE t1;
40+
ERROR HY000: Table 't1' is read only
41+
TRUNCATE TABLE t2;
42+
ERROR HY000: Table 't2' is read only
43+
TRUNCATE TABLE t3;
44+
ERROR HY000: Table 't3' is read only
45+
TRUNCATE TABLE t1;
46+
TRUNCATE TABLE t2;
47+
TRUNCATE TABLE t3;
48+
DROP TABLE t1,t2,t3;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--source include/innodb_page_size.inc
2+
# Embedded mode doesn't allow restarting
3+
--source include/not_embedded.inc
4+
5+
create table t1 (a int not null, d varchar(15) not null, b
6+
varchar(198) not null, c char(156),
7+
fulltext ftsic(c)) engine=InnoDB
8+
row_format=redundant;
9+
10+
insert into t1 values(123, 'abcdef', 'jghikl', 'mnop');
11+
insert into t1 values(456, 'abcdef', 'jghikl', 'mnop');
12+
insert into t1 values(789, 'abcdef', 'jghikl', 'mnop');
13+
insert into t1 values(134, 'kasdfsdsadf', 'adfjlasdkfjasd', 'adfsadflkasdasdfljasdf');
14+
insert into t1 select * from t1;
15+
insert into t1 select * from t1;
16+
insert into t1 select * from t1;
17+
insert into t1 select * from t1;
18+
insert into t1 select * from t1;
19+
insert into t1 select * from t1;
20+
insert into t1 select * from t1;
21+
insert into t1 select * from t1;
22+
insert into t1 select * from t1;
23+
insert into t1 select * from t1;
24+
25+
SET GLOBAL innodb_file_per_table=OFF;
26+
create table t2 (a int not null, d varchar(15) not null, b
27+
varchar(198) not null, c char(156), fulltext ftsic(c)) engine=InnoDB
28+
row_format=redundant;
29+
30+
insert into t2 select * from t1;
31+
32+
create table t3 (a int not null, d varchar(15) not null, b varchar(198),
33+
c varchar(150), index k1(c(99), b(56)), index k2(b(5), c(10))) engine=InnoDB
34+
row_format=redundant;
35+
36+
insert into t3 values(444, 'dddd', 'bbbbb', 'aaaaa');
37+
insert into t3 values(555, 'eeee', 'ccccc', 'aaaaa');
38+
39+
# read-only restart requires the change buffer to be empty; therefore we
40+
# do a slow shutdown.
41+
SET GLOBAL innodb_fast_shutdown=0;
42+
--let $restart_parameters = --innodb-read-only
43+
--source include/restart_mysqld.inc
44+
45+
SELECT COUNT(*) FROM t1;
46+
SELECT COUNT(*) FROM t2;
47+
SELECT COUNT(*) FROM t3;
48+
49+
--error ER_OPEN_AS_READONLY
50+
TRUNCATE TABLE t1;
51+
--error ER_OPEN_AS_READONLY
52+
TRUNCATE TABLE t2;
53+
--error ER_OPEN_AS_READONLY
54+
TRUNCATE TABLE t3;
55+
56+
--let $restart_parameters =
57+
--source include/restart_mysqld.inc
58+
59+
TRUNCATE TABLE t1;
60+
TRUNCATE TABLE t2;
61+
TRUNCATE TABLE t3;
62+
63+
# TODO: Shutdown, corrupt the SYS_TABLES.TYPE of the tables, restart
64+
65+
DROP TABLE t1,t2,t3;

storage/innobase/buf/buf0flu.cc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved.
4-
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
4+
Copyright (c) 2017, MariaDB Corporation.
55
66
This program is free software; you can redistribute it and/or modify it under
77
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
5858
back to FALSE at shutdown by the page_cleaner as well. Therefore no
5959
need to protect it by a mutex. It is only ever read by the thread
6060
doing the shutdown */
61-
UNIV_INTERN ibool buf_page_cleaner_is_active = FALSE;
61+
UNIV_INTERN bool buf_page_cleaner_is_active;
6262

6363
/** LRU flush batch is further divided into this chunk size to
6464
reduce the wait time for the threads waiting for a clean block */
@@ -2422,8 +2422,6 @@ DECLARE_THREAD(buf_flush_page_cleaner_thread)(
24222422
os_thread_pf(os_thread_get_curr_id()));
24232423
#endif /* UNIV_DEBUG_THREAD_CREATION */
24242424

2425-
buf_page_cleaner_is_active = TRUE;
2426-
24272425
while (srv_shutdown_state == SRV_SHUTDOWN_NONE) {
24282426

24292427
page_cleaner_sleep_if_needed(next_loop_time);
@@ -2517,7 +2515,7 @@ DECLARE_THREAD(buf_flush_page_cleaner_thread)(
25172515
/* We have lived our life. Time to die. */
25182516

25192517
thread_exit:
2520-
buf_page_cleaner_is_active = FALSE;
2518+
buf_page_cleaner_is_active = false;
25212519

25222520
my_thread_end();
25232521
/* We count the number of threads in os_thread_exit(). A created

storage/innobase/dict/dict0stats_bg.cc

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 2012, 2017, Oracle and/or its affiliates. All Rights Reserved.
4-
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
4+
Copyright (c) 2017, MariaDB Corporation.
55
66
This program is free software; you can redistribute it and/or modify it under
77
the terms of the GNU General Public License as published by the Free Software
@@ -38,12 +38,18 @@ Created Apr 25, 2012 Vasil Dimov
3838
/** Minimum time interval between stats recalc for a given table */
3939
#define MIN_RECALC_INTERVAL 10 /* seconds */
4040

41-
#define SHUTTING_DOWN() (srv_shutdown_state != SRV_SHUTDOWN_NONE)
42-
4341
/** Event to wake up dict_stats_thread on dict_stats_recalc_pool_add()
4442
or shutdown. Not protected by any mutex. */
4543
UNIV_INTERN os_event_t dict_stats_event;
4644

45+
/** Variable to initiate shutdown the dict stats thread. Note we don't
46+
use 'srv_shutdown_state' because we want to shutdown dict stats thread
47+
before purge thread. */
48+
static bool dict_stats_start_shutdown;
49+
50+
/** Event to wait for shutdown of the dict stats thread */
51+
static os_event_t dict_stats_shutdown_event;
52+
4753
/** This mutex protects the "recalc_pool" variable. */
4854
static ib_mutex_t recalc_pool_mutex;
4955
#ifdef HAVE_PSI_INTERFACE
@@ -217,11 +223,11 @@ Must be called before dict_stats_thread() is started. */
217223
UNIV_INTERN
218224
void
219225
dict_stats_thread_init()
220-
/*====================*/
221226
{
222227
ut_a(!srv_read_only_mode);
223228

224229
dict_stats_event = os_event_create();
230+
dict_stats_shutdown_event = os_event_create();
225231

226232
/* The recalc_pool_mutex is acquired from:
227233
1) the background stats gathering thread before any other latch
@@ -260,6 +266,9 @@ dict_stats_thread_deinit()
260266

261267
os_event_free(dict_stats_event);
262268
dict_stats_event = NULL;
269+
os_event_free(dict_stats_shutdown_event);
270+
dict_stats_shutdown_event = NULL;
271+
dict_stats_start_shutdown = false;
263272
}
264273

265274
/*****************************************************************//**
@@ -349,9 +358,7 @@ DECLARE_THREAD(dict_stats_thread)(
349358
my_thread_init();
350359
ut_a(!srv_read_only_mode);
351360

352-
srv_dict_stats_thread_active = TRUE;
353-
354-
while (!SHUTTING_DOWN()) {
361+
while (!dict_stats_start_shutdown) {
355362

356363
/* Wake up periodically even if not signaled. This is
357364
because we may lose an event - if the below call to
@@ -361,7 +368,7 @@ DECLARE_THREAD(dict_stats_thread)(
361368
os_event_wait_time(
362369
dict_stats_event, MIN_RECALC_INTERVAL * 1000000);
363370

364-
if (SHUTTING_DOWN()) {
371+
if (dict_stats_start_shutdown) {
365372
break;
366373
}
367374

@@ -370,12 +377,22 @@ DECLARE_THREAD(dict_stats_thread)(
370377
os_event_reset(dict_stats_event);
371378
}
372379

373-
srv_dict_stats_thread_active = FALSE;
380+
srv_dict_stats_thread_active = false;
374381

382+
os_event_set(dict_stats_shutdown_event);
375383
my_thread_end();
376384
/* We count the number of threads in os_thread_exit(). A created
377385
thread should always use that to exit instead of return(). */
378386
os_thread_exit(NULL);
379387

380388
OS_THREAD_DUMMY_RETURN;
381389
}
390+
391+
/** Shut down the dict_stats_thread. */
392+
void
393+
dict_stats_shutdown()
394+
{
395+
dict_stats_start_shutdown = true;
396+
os_event_set(dict_stats_event);
397+
os_event_wait(dict_stats_shutdown_event);
398+
}

storage/innobase/handler/ha_innodb.cc

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,14 +1142,11 @@ innobase_drop_database(
11421142
the path is used as the database name:
11431143
for example, in 'mysql/data/test' the
11441144
database name is 'test' */
1145-
/*******************************************************************//**
1146-
Closes an InnoDB database. */
1145+
/** Shut down the InnoDB storage engine.
1146+
@return 0 */
11471147
static
11481148
int
1149-
innobase_end(
1150-
/*=========*/
1151-
handlerton* hton, /* in: Innodb handlerton */
1152-
ha_panic_function type);
1149+
innobase_end(handlerton*, ha_panic_function);
11531150

11541151
/*****************************************************************//**
11551152
Creates an InnoDB transaction struct for the thd if it does not yet have one.
@@ -3651,21 +3648,13 @@ innobase_init(
36513648
DBUG_RETURN(TRUE);
36523649
}
36533650

3654-
/*******************************************************************//**
3655-
Closes an InnoDB database.
3656-
@return TRUE if error */
3651+
/** Shut down the InnoDB storage engine.
3652+
@return 0 */
36573653
static
36583654
int
3659-
innobase_end(
3660-
/*=========*/
3661-
handlerton* hton, /*!< in/out: InnoDB handlerton */
3662-
ha_panic_function type MY_ATTRIBUTE((unused)))
3663-
/*!< in: ha_panic() parameter */
3655+
innobase_end(handlerton*, ha_panic_function)
36643656
{
3665-
int err= 0;
3666-
36673657
DBUG_ENTER("innobase_end");
3668-
DBUG_ASSERT(hton == innodb_hton_ptr);
36693658

36703659
if (innodb_inited) {
36713660

@@ -3682,9 +3671,7 @@ innobase_end(
36823671
innodb_inited = 0;
36833672
hash_table_free(innobase_open_tables);
36843673
innobase_open_tables = NULL;
3685-
if (innobase_shutdown_for_mysql() != DB_SUCCESS) {
3686-
err = 1;
3687-
}
3674+
innodb_shutdown();
36883675
srv_free_paths_and_sizes();
36893676
my_free(internal_innobase_data_file_path);
36903677
mysql_mutex_destroy(&innobase_share_mutex);
@@ -3693,7 +3680,7 @@ innobase_end(
36933680
mysql_mutex_destroy(&pending_checkpoint_mutex);
36943681
}
36953682

3696-
DBUG_RETURN(err);
3683+
DBUG_RETURN(0);
36973684
}
36983685

36993686
/****************************************************************//**

storage/innobase/include/buf0flu.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Created 11/5/1995 Heikki Tuuri
3434
#include "buf0types.h"
3535

3636
/** Flag indicating if the page_cleaner is in active state. */
37-
extern ibool buf_page_cleaner_is_active;
37+
extern bool buf_page_cleaner_is_active;
3838

3939
/********************************************************************//**
4040
Remove a block from the flush list of modified blocks. */

storage/innobase/include/dict0stats_bg.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 2012, 2016, Oracle and/or its affiliates. All Rights Reserved.
4-
Copyright (c) 2017, MariaDB Corporation. All Rights Reserved.
4+
Copyright (c) 2017, MariaDB Corporation.
55
66
This program is free software; you can redistribute it and/or modify it under
77
the terms of the GNU General Public License as published by the Free Software
@@ -122,6 +122,10 @@ DECLARE_THREAD(dict_stats_thread)(
122122
void* arg); /*!< in: a dummy parameter
123123
required by os_thread_create */
124124

125+
/** Shut down the dict_stats_thread. */
126+
void
127+
dict_stats_shutdown();
128+
125129
# ifndef UNIV_NONINL
126130
# include "dict0stats_bg.ic"
127131
# endif

storage/innobase/include/srv0srv.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ extern ibool srv_error_monitor_active;
398398
extern ibool srv_buf_dump_thread_active;
399399

400400
/* TRUE during the lifetime of the stats thread */
401-
extern ibool srv_dict_stats_thread_active;
401+
extern bool srv_dict_stats_thread_active;
402402

403403
extern ulong srv_n_spin_wait_rounds;
404404
extern ulong srv_n_free_tickets_to_enter;

storage/innobase/include/srv0start.h

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved.
4+
Copyright (c) 2017, MariaDB Corporation.
45
56
This program is free software; you can redistribute it and/or modify it under
67
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.
7576
@return DB_SUCCESS or error code */
7677
UNIV_INTERN
7778
dberr_t
78-
innobase_start_or_create_for_mysql(void);
79-
/*====================================*/
80-
/****************************************************************//**
81-
Shuts down the Innobase database.
82-
@return DB_SUCCESS or error code */
83-
UNIV_INTERN
84-
dberr_t
85-
innobase_shutdown_for_mysql(void);
79+
innobase_start_or_create_for_mysql();
8680

87-
/********************************************************************
88-
Signal all per-table background threads to shutdown, and wait for them to do
89-
so. */
81+
/** Shut down InnoDB. */
9082
UNIV_INTERN
9183
void
92-
srv_shutdown_table_bg_threads(void);
93-
/*=============================*/
84+
innodb_shutdown();
9485

9586
/*************************************************************//**
9687
Copy the file path component of the physical file to parameter. It will
@@ -158,6 +149,9 @@ enum srv_shutdown_state {
158149
SRV_SHUTDOWN_EXIT_THREADS/*!< Exit all threads */
159150
};
160151

152+
/** Whether any undo log records can be generated */
153+
extern bool srv_undo_sources;
154+
161155
/** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to
162156
SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */
163157
extern enum srv_shutdown_state srv_shutdown_state;

0 commit comments

Comments
 (0)