Skip to content

Commit 2f00b73

Browse files
committed
MDEV-11985 Make innodb_read_only shutdown more robust
If InnoDB is started in innodb_read_only mode such that recovered incomplete transactions exist at startup (but the redo logs are clean), an assertion will fail at shutdown, because there would exist some non-prepared transactions. logs_empty_and_mark_files_at_shutdown(): Do not wait for incomplete transactions to finish if innodb_read_only or innodb_force_recovery>=3. Wait for purge to finish in only one place. trx_sys_close(): Relax the assertion that would fail first. trx_free_prepared(): Also free recovered TRX_STATE_ACTIVE transactions if innodb_read_only or innodb_force_recovery>=3. Also, revert my earlier fix to MySQL 5.7 because this fix is more generic: Bug#20874411 INNODB SHUTDOWN HANGS IF INNODB_FORCE_RECOVERY>=3 SKIPPED ANY ROLLBACK trx_undo_fake_prepared(): Remove. trx_sys_any_active_transactions(): Revert the changes.
1 parent a440d6e commit 2f00b73

File tree

6 files changed

+118
-79
lines changed

6 files changed

+118
-79
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
connect con1, localhost, root;
2+
CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB;
3+
BEGIN;
4+
INSERT INTO t VALUES(1),(2);
5+
DELETE FROM t WHERE a=2;
6+
connection default;
7+
# Normal MariaDB shutdown would roll back the above transaction.
8+
# We want the transaction to remain open, so we will kill the server
9+
# after ensuring that any non-transactional files are clean.
10+
FLUSH TABLES;
11+
# Ensure that the above incomplete transaction becomes durable.
12+
SET GLOBAL innodb_flush_log_at_trx_commit=1;
13+
BEGIN;
14+
INSERT INTO t VALUES(0);
15+
ROLLBACK;
16+
# Kill and restart: --innodb-force-recovery=3
17+
disconnect con1;
18+
SELECT * FROM t;
19+
a
20+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
21+
SELECT * FROM t;
22+
a
23+
1
24+
# Starting with MariaDB 10.2, innodb_read_only implies READ UNCOMMITTED.
25+
# In earlier versions, this would return the last committed version
26+
# (empty table)!
27+
SELECT * FROM t;
28+
a
29+
1
30+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
31+
SELECT * FROM t;
32+
a
33+
1
34+
SELECT * FROM t;
35+
a
36+
DROP TABLE t;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--source include/have_innodb.inc
2+
# need to restart server
3+
--source include/not_embedded.inc
4+
5+
--connect(con1, localhost, root)
6+
CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB;
7+
BEGIN;
8+
# Generate insert_undo log.
9+
INSERT INTO t VALUES(1),(2);
10+
# Generate update_undo log.
11+
DELETE FROM t WHERE a=2;
12+
--connection default
13+
--echo # Normal MariaDB shutdown would roll back the above transaction.
14+
--echo # We want the transaction to remain open, so we will kill the server
15+
--echo # after ensuring that any non-transactional files are clean.
16+
FLUSH TABLES;
17+
--echo # Ensure that the above incomplete transaction becomes durable.
18+
SET GLOBAL innodb_flush_log_at_trx_commit=1;
19+
BEGIN;
20+
INSERT INTO t VALUES(0);
21+
ROLLBACK;
22+
--let $restart_parameters= --innodb-force-recovery=3
23+
--source include/kill_and_restart_mysqld.inc
24+
--disconnect con1
25+
SELECT * FROM t;
26+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
27+
SELECT * FROM t;
28+
--let $restart_parameters= --innodb-read-only
29+
--source include/restart_mysqld.inc
30+
--echo # Starting with MariaDB 10.2, innodb_read_only implies READ UNCOMMITTED.
31+
--echo # In earlier versions, this would return the last committed version
32+
--echo # (empty table)!
33+
SELECT * FROM t;
34+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
35+
SELECT * FROM t;
36+
--let $restart_parameters=
37+
--source include/restart_mysqld.inc
38+
SELECT * FROM t;
39+
DROP TABLE t;

storage/innobase/log/log0log.cc

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,7 +2134,8 @@ logs_empty_and_mark_files_at_shutdown(void)
21342134
shutdown, because the InnoDB layer may have committed or
21352135
prepared transactions and we don't want to lose them. */
21362136

2137-
if (ulint total_trx = srv_was_started
2137+
if (ulint total_trx = srv_was_started && !srv_read_only_mode
2138+
&& srv_force_recovery < SRV_FORCE_NO_TRX_UNDO
21382139
? trx_sys_any_active_transactions() : 0) {
21392140

21402141
if (srv_print_verbose_log && count > 600) {
@@ -2144,13 +2145,6 @@ logs_empty_and_mark_files_at_shutdown(void)
21442145
count = 0;
21452146
}
21462147

2147-
/* Wake up purge threads to die - they have MYSQL_THD's and
2148-
thus might keep open transactions. In particular, this is
2149-
needed in embedded server and when one uses UNINSTALL PLUGIN.
2150-
In the normal server shutdown purge threads should've been
2151-
already notified by the thd_destructor_proxy thread. */
2152-
srv_purge_wakeup();
2153-
21542148
goto loop;
21552149
}
21562150

@@ -2196,15 +2190,13 @@ logs_empty_and_mark_files_at_shutdown(void)
21962190
thread_name = "fil_crypt_thread";
21972191
goto wait_suspend_loop;
21982192
case SRV_PURGE:
2193+
case SRV_WORKER:
21992194
srv_purge_wakeup();
22002195
thread_name = "purge thread";
22012196
goto wait_suspend_loop;
22022197
case SRV_MASTER:
22032198
thread_name = "master thread";
22042199
goto wait_suspend_loop;
2205-
case SRV_WORKER:
2206-
thread_name = "worker threads";
2207-
goto wait_suspend_loop;
22082200
}
22092201

22102202
/* At this point only page_cleaner should be active. We wait

storage/innobase/trx/trx0sys.cc

Lines changed: 9 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,9 @@ trx_sys_close(void)
10961096
}
10971097

10981098
/* Only prepared transactions may be left in the system. Free them. */
1099-
ut_a(UT_LIST_GET_LEN(trx_sys->rw_trx_list) == trx_sys->n_prepared_trx);
1099+
ut_a(UT_LIST_GET_LEN(trx_sys->rw_trx_list) == trx_sys->n_prepared_trx
1100+
|| srv_read_only_mode
1101+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
11001102

11011103
for (trx_t* trx = UT_LIST_GET_FIRST(trx_sys->rw_trx_list);
11021104
trx != NULL;
@@ -1151,80 +1153,22 @@ trx_sys_close(void)
11511153
trx_sys = NULL;
11521154
}
11531155

1154-
/** @brief Convert an undo log to TRX_UNDO_PREPARED state on shutdown.
1155-
1156-
If any prepared ACTIVE transactions exist, and their rollback was
1157-
prevented by innodb_force_recovery, we convert these transactions to
1158-
XA PREPARE state in the main-memory data structures, so that shutdown
1159-
will proceed normally. These transactions will again recover as ACTIVE
1160-
on the next restart, and they will be rolled back unless
1161-
innodb_force_recovery prevents it again.
1162-
1163-
@param[in] trx transaction
1164-
@param[in,out] undo undo log to convert to TRX_UNDO_PREPARED */
1165-
static
1166-
void
1167-
trx_undo_fake_prepared(
1168-
const trx_t* trx,
1169-
trx_undo_t* undo)
1170-
{
1171-
ut_ad(srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
1172-
ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
1173-
ut_ad(trx->is_recovered);
1174-
1175-
if (undo != NULL) {
1176-
ut_ad(undo->state == TRX_UNDO_ACTIVE);
1177-
undo->state = TRX_UNDO_PREPARED;
1178-
}
1179-
}
1180-
11811156
/*********************************************************************
11821157
Check if there are any active (non-prepared) transactions.
11831158
@return total number of active transactions or 0 if none */
11841159
ulint
11851160
trx_sys_any_active_transactions(void)
11861161
/*=================================*/
11871162
{
1163+
ulint total_trx = 0;
1164+
11881165
trx_sys_mutex_enter();
11891166

1190-
ulint total_trx = UT_LIST_GET_LEN(trx_sys->mysql_trx_list);
1191-
1192-
if (total_trx == 0) {
1193-
total_trx = UT_LIST_GET_LEN(trx_sys->rw_trx_list);
1194-
ut_a(total_trx >= trx_sys->n_prepared_trx);
1195-
1196-
if (total_trx > trx_sys->n_prepared_trx
1197-
&& srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO) {
1198-
for (trx_t* trx = UT_LIST_GET_FIRST(
1199-
trx_sys->rw_trx_list);
1200-
trx != NULL;
1201-
trx = UT_LIST_GET_NEXT(trx_list, trx)) {
1202-
if (!trx_state_eq(trx, TRX_STATE_ACTIVE)
1203-
|| !trx->is_recovered) {
1204-
continue;
1205-
}
1206-
/* This was a recovered transaction
1207-
whose rollback was disabled by
1208-
the innodb_force_recovery setting.
1209-
Pretend that it is in XA PREPARE
1210-
state so that shutdown will work. */
1211-
trx_undo_fake_prepared(
1212-
trx, trx->rsegs.m_redo.insert_undo);
1213-
trx_undo_fake_prepared(
1214-
trx, trx->rsegs.m_redo.update_undo);
1215-
trx_undo_fake_prepared(
1216-
trx, trx->rsegs.m_noredo.insert_undo);
1217-
trx_undo_fake_prepared(
1218-
trx, trx->rsegs.m_noredo.update_undo);
1219-
trx->state = TRX_STATE_PREPARED;
1220-
trx_sys->n_prepared_trx++;
1221-
trx_sys->n_prepared_recovered_trx++;
1222-
}
1223-
}
1167+
total_trx = UT_LIST_GET_LEN(trx_sys->rw_trx_list)
1168+
+ UT_LIST_GET_LEN(trx_sys->mysql_trx_list);
12241169

1225-
ut_a(total_trx >= trx_sys->n_prepared_trx);
1226-
total_trx -= trx_sys->n_prepared_trx;
1227-
}
1170+
ut_a(total_trx >= trx_sys->n_prepared_trx);
1171+
total_trx -= trx_sys->n_prepared_trx;
12281172

12291173
trx_sys_mutex_exit();
12301174

storage/innobase/trx/trx0trx.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,11 @@ trx_free_prepared(
632632
/*==============*/
633633
trx_t* trx) /*!< in, own: trx object */
634634
{
635-
ut_a(trx_state_eq(trx, TRX_STATE_PREPARED));
635+
ut_a(trx_state_eq(trx, TRX_STATE_PREPARED)
636+
|| (trx_state_eq(trx, TRX_STATE_ACTIVE)
637+
&& trx->is_recovered
638+
&& (srv_read_only_mode
639+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO)));
636640
ut_a(trx->magic_n == TRX_MAGIC_N);
637641

638642
lock_trx_release_locks(trx);

storage/innobase/trx/trx0undo.cc

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,7 +2015,19 @@ trx_undo_free_prepared(
20152015
ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS);
20162016

20172017
if (trx->rsegs.m_redo.update_undo) {
2018-
ut_a(trx->rsegs.m_redo.update_undo->state == TRX_UNDO_PREPARED);
2018+
switch (trx->rsegs.m_redo.update_undo->state) {
2019+
case TRX_UNDO_PREPARED:
2020+
break;
2021+
case TRX_UNDO_ACTIVE:
2022+
/* lock_trx_release_locks() assigns
2023+
trx->is_recovered=false */
2024+
ut_a(srv_read_only_mode
2025+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
2026+
break;
2027+
default:
2028+
ut_error;
2029+
}
2030+
20192031
UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->update_undo_list,
20202032
trx->rsegs.m_redo.update_undo);
20212033
trx_undo_mem_free(trx->rsegs.m_redo.update_undo);
@@ -2024,7 +2036,19 @@ trx_undo_free_prepared(
20242036
}
20252037

20262038
if (trx->rsegs.m_redo.insert_undo) {
2027-
ut_a(trx->rsegs.m_redo.insert_undo->state == TRX_UNDO_PREPARED);
2039+
switch (trx->rsegs.m_redo.insert_undo->state) {
2040+
case TRX_UNDO_PREPARED:
2041+
break;
2042+
case TRX_UNDO_ACTIVE:
2043+
/* lock_trx_release_locks() assigns
2044+
trx->is_recovered=false */
2045+
ut_a(srv_read_only_mode
2046+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
2047+
break;
2048+
default:
2049+
ut_error;
2050+
}
2051+
20282052
UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->insert_undo_list,
20292053
trx->rsegs.m_redo.insert_undo);
20302054
trx_undo_mem_free(trx->rsegs.m_redo.insert_undo);

0 commit comments

Comments
 (0)