Skip to content

Commit 6559ba7

Browse files
committed
MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup while rolling back recovered incomplete transactions
trx_rollback_resurrected(): If shutdown was initiated, fake all remaining active transactions to XA PREPARE state, so that shutdown can proceed. Also, make the parameter "all" an output that will be assigned to FALSE in this case. trx_rollback_or_clean_recovered(): Remove the shutdown check (it was moved to trx_rollback_resurrected()). trx_undo_free_prepared(): Relax assertions.
1 parent 6224666 commit 6559ba7

File tree

6 files changed

+166
-42
lines changed

6 files changed

+166
-42
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#
2+
# MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup
3+
# while rolling back recovered incomplete transactions
4+
#
5+
CREATE TABLE t (a INT) ENGINE=InnoDB;
6+
BEGIN;
7+
COMMIT;
8+
CREATE TABLE t8 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
9+
BEGIN;
10+
INSERT INTO t8 (a) SELECT NULL FROM t;
11+
UPDATE t8 SET a=a+100, b=a;
12+
DELETE FROM t8;
13+
CREATE TABLE t7 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
14+
BEGIN;
15+
INSERT INTO t7 (a) SELECT NULL FROM t;
16+
UPDATE t7 SET a=a+100, b=a;
17+
DELETE FROM t7;
18+
CREATE TABLE t6 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
19+
BEGIN;
20+
INSERT INTO t6 (a) SELECT NULL FROM t;
21+
UPDATE t6 SET a=a+100, b=a;
22+
DELETE FROM t6;
23+
CREATE TABLE t5 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
24+
BEGIN;
25+
INSERT INTO t5 (a) SELECT NULL FROM t;
26+
UPDATE t5 SET a=a+100, b=a;
27+
DELETE FROM t5;
28+
CREATE TABLE t4 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
29+
BEGIN;
30+
INSERT INTO t4 (a) SELECT NULL FROM t;
31+
UPDATE t4 SET a=a+100, b=a;
32+
DELETE FROM t4;
33+
CREATE TABLE t3 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
34+
BEGIN;
35+
INSERT INTO t3 (a) SELECT NULL FROM t;
36+
UPDATE t3 SET a=a+100, b=a;
37+
DELETE FROM t3;
38+
CREATE TABLE t2 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
39+
BEGIN;
40+
INSERT INTO t2 (a) SELECT NULL FROM t;
41+
UPDATE t2 SET a=a+100, b=a;
42+
DELETE FROM t2;
43+
CREATE TABLE t1 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
44+
BEGIN;
45+
INSERT INTO t1 (a) SELECT NULL FROM t;
46+
UPDATE t1 SET a=a+100, b=a;
47+
DELETE FROM t1;
48+
SET GLOBAL innodb_flush_log_at_trx_commit=1;
49+
CREATE TABLE u(a SERIAL) ENGINE=INNODB;
50+
# Kill and restart
51+
DROP TABLE t,u;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--source include/have_innodb.inc
2+
--source include/not_embedded.inc
3+
4+
--echo #
5+
--echo # MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup
6+
--echo # while rolling back recovered incomplete transactions
7+
--echo #
8+
9+
CREATE TABLE t (a INT) ENGINE=InnoDB;
10+
let $size = 100;
11+
let $trx = 8;
12+
let $c = $size;
13+
BEGIN;
14+
--disable_query_log
15+
while ($c) {
16+
INSERT INTO t VALUES();
17+
dec $c;
18+
}
19+
--enable_query_log
20+
COMMIT;
21+
22+
let $c = $trx;
23+
while ($c)
24+
{
25+
connect (con$c,localhost,root,,);
26+
eval CREATE TABLE t$c (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB;
27+
BEGIN;
28+
eval INSERT INTO t$c (a) SELECT NULL FROM t;
29+
eval UPDATE t$c SET a=a+$size, b=a;
30+
eval DELETE FROM t$c;
31+
dec $c;
32+
}
33+
34+
--connection default
35+
SET GLOBAL innodb_flush_log_at_trx_commit=1;
36+
CREATE TABLE u(a SERIAL) ENGINE=INNODB;
37+
38+
--source include/kill_and_restart_mysqld.inc
39+
--source include/restart_mysqld.inc
40+
41+
--disable_query_log
42+
let $c = $trx;
43+
while ($c)
44+
{
45+
disconnect con$c;
46+
eval DROP TABLE t$c;
47+
dec $c;
48+
}
49+
--enable_query_log
50+
51+
DROP TABLE t,u;

storage/innobase/trx/trx0roll.cc

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ ibool
672672
trx_rollback_resurrected(
673673
/*=====================*/
674674
trx_t* trx,/*!< in: transaction to rollback or clean */
675-
ibool all)/*!< in: FALSE=roll back dictionary transactions;
675+
ibool*all)/*!< in/out: FALSE=roll back dictionary transactions;
676676
TRUE=roll back all non-PREPARED transactions */
677677
{
678678
ut_ad(mutex_own(&trx_sys->mutex));
@@ -683,16 +683,15 @@ trx_rollback_resurrected(
683683
to accidentally clean up a non-recovered transaction here. */
684684

685685
trx_mutex_enter(trx);
686-
boolis_recovered = trx->is_recovered;
687-
trx_state_tstate = trx->state;
688-
trx_mutex_exit(trx);
689-
690-
if (!is_recovered) {
686+
if (!trx->is_recovered) {
687+
func_exit:
688+
trx_mutex_exit(trx);
691689
return(FALSE);
692690
}
693691

694-
switch (state) {
692+
switch (trx->state) {
695693
case TRX_STATE_COMMITTED_IN_MEMORY:
694+
trx_mutex_exit(trx);
696695
mutex_exit(&trx_sys->mutex);
697696
fprintf(stderr,
698697
"InnoDB: Cleaning up trx with id " TRX_ID_FMT "\n",
@@ -701,21 +700,31 @@ trx_rollback_resurrected(
701700
trx_free_for_background(trx);
702701
return(TRUE);
703702
case TRX_STATE_ACTIVE:
704-
if (all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
703+
if (srv_shutdown_state != SRV_SHUTDOWN_NONE
704+
&& srv_fast_shutdown) {
705+
trx->state = TRX_STATE_PREPARED;
706+
trx_sys->n_prepared_trx++;
707+
trx_sys->n_prepared_recovered_trx++;
708+
*all = FALSE;
709+
goto func_exit;
710+
}
711+
trx_mutex_exit(trx);
712+
713+
if (*all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
705714
mutex_exit(&trx_sys->mutex);
706715
trx_rollback_active(trx);
707716
trx_free_for_background(trx);
708717
return(TRUE);
709718
}
710719
return(FALSE);
711720
case TRX_STATE_PREPARED:
712-
return(FALSE);
721+
goto func_exit;
713722
case TRX_STATE_NOT_STARTED:
714723
break;
715724
}
716725

717726
ut_error;
718-
return(FALSE);
727+
goto func_exit;
719728
}
720729

721730
/*******************************************************************//**
@@ -762,17 +771,11 @@ trx_rollback_or_clean_recovered(
762771

763772
assert_trx_in_rw_list(trx);
764773

765-
if (srv_shutdown_state != SRV_SHUTDOWN_NONE
766-
&& srv_fast_shutdown != 0) {
767-
all = FALSE;
768-
break;
769-
}
770-
771774
/* If this function does a cleanup or rollback
772775
then it will release the trx_sys->mutex, therefore
773776
we need to reacquire it before retrying the loop. */
774777

775-
if (trx_rollback_resurrected(trx, all)) {
778+
if (trx_rollback_resurrected(trx, &all)) {
776779

777780
mutex_enter(&trx_sys->mutex);
778781

storage/innobase/trx/trx0undo.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,9 +2023,13 @@ trx_undo_free_prepared(
20232023
/* fall through */
20242024
case TRX_UNDO_ACTIVE:
20252025
/* lock_trx_release_locks() assigns
2026-
trx->is_recovered=false */
2026+
trx->is_recovered=false and
2027+
trx->state = TRX_STATE_COMMITTED_IN_MEMORY,
2028+
also for transactions that we faked
2029+
to TRX_STATE_PREPARED in trx_rollback_resurrected(). */
20272030
ut_a(srv_read_only_mode
2028-
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
2031+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
2032+
|| srv_fast_shutdown);
20292033
break;
20302034
default:
20312035
ut_error;
@@ -2047,9 +2051,13 @@ trx_undo_free_prepared(
20472051
/* fall through */
20482052
case TRX_UNDO_ACTIVE:
20492053
/* lock_trx_release_locks() assigns
2050-
trx->is_recovered=false */
2054+
trx->is_recovered=false and
2055+
trx->state = TRX_STATE_COMMITTED_IN_MEMORY,
2056+
also for transactions that we faked
2057+
to TRX_STATE_PREPARED in trx_rollback_resurrected(). */
20512058
ut_a(srv_read_only_mode
2052-
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
2059+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
2060+
|| srv_fast_shutdown);
20532061
break;
20542062
default:
20552063
ut_error;

storage/xtradb/trx/trx0roll.cc

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ ibool
672672
trx_rollback_resurrected(
673673
/*=====================*/
674674
trx_t* trx,/*!< in: transaction to rollback or clean */
675-
ibool all)/*!< in: FALSE=roll back dictionary transactions;
675+
ibool*all)/*!< in/out: FALSE=roll back dictionary transactions;
676676
TRUE=roll back all non-PREPARED transactions */
677677
{
678678
ut_ad(mutex_own(&trx_sys->mutex));
@@ -683,16 +683,15 @@ trx_rollback_resurrected(
683683
to accidentally clean up a non-recovered transaction here. */
684684

685685
trx_mutex_enter(trx);
686-
boolis_recovered = trx->is_recovered;
687-
trx_state_tstate = trx->state;
688-
trx_mutex_exit(trx);
689-
690-
if (!is_recovered) {
686+
if (!trx->is_recovered) {
687+
func_exit:
688+
trx_mutex_exit(trx);
691689
return(FALSE);
692690
}
693691

694-
switch (state) {
692+
switch (trx->state) {
695693
case TRX_STATE_COMMITTED_IN_MEMORY:
694+
trx_mutex_exit(trx);
696695
mutex_exit(&trx_sys->mutex);
697696
fprintf(stderr,
698697
"InnoDB: Cleaning up trx with id " TRX_ID_FMT "\n",
@@ -701,21 +700,31 @@ trx_rollback_resurrected(
701700
trx_free_for_background(trx);
702701
return(TRUE);
703702
case TRX_STATE_ACTIVE:
704-
if (all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
703+
if (srv_shutdown_state != SRV_SHUTDOWN_NONE
704+
&& srv_fast_shutdown) {
705+
trx->state = TRX_STATE_PREPARED;
706+
trx_sys->n_prepared_trx++;
707+
trx_sys->n_prepared_recovered_trx++;
708+
*all = FALSE;
709+
goto func_exit;
710+
}
711+
trx_mutex_exit(trx);
712+
713+
if (*all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
705714
mutex_exit(&trx_sys->mutex);
706715
trx_rollback_active(trx);
707716
trx_free_for_background(trx);
708717
return(TRUE);
709718
}
710719
return(FALSE);
711720
case TRX_STATE_PREPARED:
712-
return(FALSE);
721+
goto func_exit;
713722
case TRX_STATE_NOT_STARTED:
714723
break;
715724
}
716725

717726
ut_error;
718-
return(FALSE);
727+
goto func_exit;
719728
}
720729

721730
/*******************************************************************//**
@@ -762,17 +771,11 @@ trx_rollback_or_clean_recovered(
762771

763772
assert_trx_in_rw_list(trx);
764773

765-
if (srv_shutdown_state != SRV_SHUTDOWN_NONE
766-
&& srv_fast_shutdown != 0) {
767-
all = FALSE;
768-
break;
769-
}
770-
771774
/* If this function does a cleanup or rollback
772775
then it will release the trx_sys->mutex, therefore
773776
we need to reacquire it before retrying the loop. */
774777

775-
if (trx_rollback_resurrected(trx, all)) {
778+
if (trx_rollback_resurrected(trx, &all)) {
776779

777780
mutex_enter(&trx_sys->mutex);
778781

storage/xtradb/trx/trx0undo.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,9 +2023,13 @@ trx_undo_free_prepared(
20232023
/* fall through */
20242024
case TRX_UNDO_ACTIVE:
20252025
/* lock_trx_release_locks() assigns
2026-
trx->is_recovered=false */
2026+
trx->is_recovered=false and
2027+
trx->state = TRX_STATE_COMMITTED_IN_MEMORY,
2028+
also for transactions that we faked
2029+
to TRX_STATE_PREPARED in trx_rollback_resurrected(). */
20272030
ut_a(srv_read_only_mode
2028-
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
2031+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
2032+
|| srv_fast_shutdown);
20292033
break;
20302034
default:
20312035
ut_error;
@@ -2047,9 +2051,13 @@ trx_undo_free_prepared(
20472051
/* fall through */
20482052
case TRX_UNDO_ACTIVE:
20492053
/* lock_trx_release_locks() assigns
2050-
trx->is_recovered=false */
2054+
trx->is_recovered=false and
2055+
trx->state = TRX_STATE_COMMITTED_IN_MEMORY,
2056+
also for transactions that we faked
2057+
to TRX_STATE_PREPARED in trx_rollback_resurrected(). */
20512058
ut_a(srv_read_only_mode
2052-
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
2059+
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
2060+
|| srv_fast_shutdown);
20532061
break;
20542062
default:
20552063
ut_error;

0 commit comments

Comments
 (0)