summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThirunarayanan Balathandayuthapani <thiru@mariadb.com>2019-04-24 12:31:24 +0530
committerMarko Mäkelä <marko.makela@mariadb.com>2019-04-24 11:46:14 +0300
commitd5da8ae04d57556f517c0f03afeafe73c6cc75d1 (patch)
treed12ada3a85fbb66d7807a0daf1d755c5cfa4dae3
parentcb8d888c42aa9504db2df686f0e963b99079e287 (diff)
downloadmariadb-git-d5da8ae04d57556f517c0f03afeafe73c6cc75d1.tar.gz
MDEV-15772 Potential list overrun during XA recovery
InnoDB could return the same list again and again if the buffer passed to trx_recover_for_mysql() is smaller than the number of transactions that InnoDB recovered in XA PREPARE state. We introduce the transaction state TRX_PREPARED_RECOVERED, which is like TRX_PREPARED, but will be set during trx_recover_for_mysql() so that each transaction will only be returned once. Because init_server_components() is invoking ha_recover() twice, we must reset the state of the transactions back to TRX_PREPARED after returning the complete list, so that repeated traversals will see the complete list again, instead of seeing an empty list. Without this tweak, the test main.tc_heuristic_recover would hang in MariaDB 10.1.
-rw-r--r--mysql-test/suite/innodb/r/xa_debug.result310
-rw-r--r--mysql-test/suite/innodb/t/xa_debug.test45
-rw-r--r--sql/handler.cc1
-rw-r--r--storage/innobase/include/trx0sys.ic12
-rw-r--r--storage/innobase/include/trx0trx.h3
-rw-r--r--storage/innobase/lock/lock0lock.c39
-rw-r--r--storage/innobase/read/read0read.c36
-rw-r--r--storage/innobase/trx/trx0roll.c2
-rw-r--r--storage/innobase/trx/trx0sys.c3
-rw-r--r--storage/innobase/trx/trx0trx.c27
-rw-r--r--storage/xtradb/include/trx0trx.h3
-rw-r--r--storage/xtradb/lock/lock0lock.c39
-rw-r--r--storage/xtradb/trx/trx0roll.c2
-rw-r--r--storage/xtradb/trx/trx0sys.c3
-rw-r--r--storage/xtradb/trx/trx0trx.c27
15 files changed, 481 insertions, 71 deletions
diff --git a/mysql-test/suite/innodb/r/xa_debug.result b/mysql-test/suite/innodb/r/xa_debug.result
new file mode 100644
index 00000000000..43a3b299468
--- /dev/null
+++ b/mysql-test/suite/innodb/r/xa_debug.result
@@ -0,0 +1,310 @@
+call mtr.add_suppression("Found 50 prepared XA transactions");
+create table t1 (a int) engine=innodb;
+insert into t1 values(1);
+xa start 'test50';
+insert into t1 values(1);
+xa end 'test50';
+xa prepare 'test50';
+xa start 'test49';
+insert into t1 values(1);
+xa end 'test49';
+xa prepare 'test49';
+xa start 'test48';
+insert into t1 values(1);
+xa end 'test48';
+xa prepare 'test48';
+xa start 'test47';
+insert into t1 values(1);
+xa end 'test47';
+xa prepare 'test47';
+xa start 'test46';
+insert into t1 values(1);
+xa end 'test46';
+xa prepare 'test46';
+xa start 'test45';
+insert into t1 values(1);
+xa end 'test45';
+xa prepare 'test45';
+xa start 'test44';
+insert into t1 values(1);
+xa end 'test44';
+xa prepare 'test44';
+xa start 'test43';
+insert into t1 values(1);
+xa end 'test43';
+xa prepare 'test43';
+xa start 'test42';
+insert into t1 values(1);
+xa end 'test42';
+xa prepare 'test42';
+xa start 'test41';
+insert into t1 values(1);
+xa end 'test41';
+xa prepare 'test41';
+xa start 'test40';
+insert into t1 values(1);
+xa end 'test40';
+xa prepare 'test40';
+xa start 'test39';
+insert into t1 values(1);
+xa end 'test39';
+xa prepare 'test39';
+xa start 'test38';
+insert into t1 values(1);
+xa end 'test38';
+xa prepare 'test38';
+xa start 'test37';
+insert into t1 values(1);
+xa end 'test37';
+xa prepare 'test37';
+xa start 'test36';
+insert into t1 values(1);
+xa end 'test36';
+xa prepare 'test36';
+xa start 'test35';
+insert into t1 values(1);
+xa end 'test35';
+xa prepare 'test35';
+xa start 'test34';
+insert into t1 values(1);
+xa end 'test34';
+xa prepare 'test34';
+xa start 'test33';
+insert into t1 values(1);
+xa end 'test33';
+xa prepare 'test33';
+xa start 'test32';
+insert into t1 values(1);
+xa end 'test32';
+xa prepare 'test32';
+xa start 'test31';
+insert into t1 values(1);
+xa end 'test31';
+xa prepare 'test31';
+xa start 'test30';
+insert into t1 values(1);
+xa end 'test30';
+xa prepare 'test30';
+xa start 'test29';
+insert into t1 values(1);
+xa end 'test29';
+xa prepare 'test29';
+xa start 'test28';
+insert into t1 values(1);
+xa end 'test28';
+xa prepare 'test28';
+xa start 'test27';
+insert into t1 values(1);
+xa end 'test27';
+xa prepare 'test27';
+xa start 'test26';
+insert into t1 values(1);
+xa end 'test26';
+xa prepare 'test26';
+xa start 'test25';
+insert into t1 values(1);
+xa end 'test25';
+xa prepare 'test25';
+xa start 'test24';
+insert into t1 values(1);
+xa end 'test24';
+xa prepare 'test24';
+xa start 'test23';
+insert into t1 values(1);
+xa end 'test23';
+xa prepare 'test23';
+xa start 'test22';
+insert into t1 values(1);
+xa end 'test22';
+xa prepare 'test22';
+xa start 'test21';
+insert into t1 values(1);
+xa end 'test21';
+xa prepare 'test21';
+xa start 'test20';
+insert into t1 values(1);
+xa end 'test20';
+xa prepare 'test20';
+xa start 'test19';
+insert into t1 values(1);
+xa end 'test19';
+xa prepare 'test19';
+xa start 'test18';
+insert into t1 values(1);
+xa end 'test18';
+xa prepare 'test18';
+xa start 'test17';
+insert into t1 values(1);
+xa end 'test17';
+xa prepare 'test17';
+xa start 'test16';
+insert into t1 values(1);
+xa end 'test16';
+xa prepare 'test16';
+xa start 'test15';
+insert into t1 values(1);
+xa end 'test15';
+xa prepare 'test15';
+xa start 'test14';
+insert into t1 values(1);
+xa end 'test14';
+xa prepare 'test14';
+xa start 'test13';
+insert into t1 values(1);
+xa end 'test13';
+xa prepare 'test13';
+xa start 'test12';
+insert into t1 values(1);
+xa end 'test12';
+xa prepare 'test12';
+xa start 'test11';
+insert into t1 values(1);
+xa end 'test11';
+xa prepare 'test11';
+xa start 'test10';
+insert into t1 values(1);
+xa end 'test10';
+xa prepare 'test10';
+xa start 'test9';
+insert into t1 values(1);
+xa end 'test9';
+xa prepare 'test9';
+xa start 'test8';
+insert into t1 values(1);
+xa end 'test8';
+xa prepare 'test8';
+xa start 'test7';
+insert into t1 values(1);
+xa end 'test7';
+xa prepare 'test7';
+xa start 'test6';
+insert into t1 values(1);
+xa end 'test6';
+xa prepare 'test6';
+xa start 'test5';
+insert into t1 values(1);
+xa end 'test5';
+xa prepare 'test5';
+xa start 'test4';
+insert into t1 values(1);
+xa end 'test4';
+xa prepare 'test4';
+xa start 'test3';
+insert into t1 values(1);
+xa end 'test3';
+xa prepare 'test3';
+xa start 'test2';
+insert into t1 values(1);
+xa end 'test2';
+xa prepare 'test2';
+xa start 'test1';
+insert into t1 values(1);
+xa end 'test1';
+xa prepare 'test1';
+xa recover;
+formatID gtrid_length bqual_length data
+1 5 0 test1
+1 5 0 test2
+1 5 0 test3
+1 5 0 test4
+1 5 0 test5
+1 5 0 test6
+1 5 0 test7
+1 5 0 test8
+1 5 0 test9
+1 6 0 test10
+1 6 0 test11
+1 6 0 test12
+1 6 0 test13
+1 6 0 test14
+1 6 0 test15
+1 6 0 test16
+1 6 0 test17
+1 6 0 test18
+1 6 0 test19
+1 6 0 test20
+1 6 0 test21
+1 6 0 test22
+1 6 0 test23
+1 6 0 test24
+1 6 0 test25
+1 6 0 test26
+1 6 0 test27
+1 6 0 test28
+1 6 0 test29
+1 6 0 test30
+1 6 0 test31
+1 6 0 test32
+1 6 0 test33
+1 6 0 test34
+1 6 0 test35
+1 6 0 test36
+1 6 0 test37
+1 6 0 test38
+1 6 0 test39
+1 6 0 test40
+1 6 0 test41
+1 6 0 test42
+1 6 0 test43
+1 6 0 test44
+1 6 0 test45
+1 6 0 test46
+1 6 0 test47
+1 6 0 test48
+1 6 0 test49
+1 6 0 test50
+xa recover;
+formatID gtrid_length bqual_length data
+1 5 0 test1
+1 5 0 test2
+1 5 0 test3
+1 5 0 test4
+1 5 0 test5
+1 5 0 test6
+1 5 0 test7
+1 5 0 test8
+1 5 0 test9
+1 6 0 test10
+1 6 0 test11
+1 6 0 test12
+1 6 0 test13
+1 6 0 test14
+1 6 0 test15
+1 6 0 test16
+1 6 0 test17
+1 6 0 test18
+1 6 0 test19
+1 6 0 test20
+1 6 0 test21
+1 6 0 test22
+1 6 0 test23
+1 6 0 test24
+1 6 0 test25
+1 6 0 test26
+1 6 0 test27
+1 6 0 test28
+1 6 0 test29
+1 6 0 test30
+1 6 0 test31
+1 6 0 test32
+1 6 0 test33
+1 6 0 test34
+1 6 0 test35
+1 6 0 test36
+1 6 0 test37
+1 6 0 test38
+1 6 0 test39
+1 6 0 test40
+1 6 0 test41
+1 6 0 test42
+1 6 0 test43
+1 6 0 test44
+1 6 0 test45
+1 6 0 test46
+1 6 0 test47
+1 6 0 test48
+1 6 0 test49
+1 6 0 test50
+xa recover;
+formatID gtrid_length bqual_length data
+drop table t1;
diff --git a/mysql-test/suite/innodb/t/xa_debug.test b/mysql-test/suite/innodb/t/xa_debug.test
new file mode 100644
index 00000000000..5724891bb65
--- /dev/null
+++ b/mysql-test/suite/innodb/t/xa_debug.test
@@ -0,0 +1,45 @@
+-- source include/have_innodb.inc
+-- source include/have_debug.inc
+-- source include/not_embedded.inc
+
+call mtr.add_suppression("Found 50 prepared XA transactions");
+create table t1 (a int) engine=innodb;
+insert into t1 values(1);
+
+let $trial = 50;
+while ($trial)
+{
+--connect (con$trial, localhost, root,,)
+let $st_pre = `select concat('test', $trial)`;
+eval xa start '$st_pre';
+insert into t1 values(1);
+eval xa end '$st_pre';
+eval xa prepare '$st_pre';
+dec $trial;
+}
+
+connection default;
+# Kill and restart the server.
+-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- shutdown_server 0
+-- source include/wait_until_disconnected.inc
+
+-- exec echo "restart:--debug_dbug=+d,min_xa_len" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+-- disable_reconnect
+--sorted_result
+xa recover;
+--sorted_result
+xa recover;
+--disable_query_log
+let $trial = 50;
+while ($trial)
+{
+let $st_pre = `select concat('test', $trial)`;
+eval xa commit '$st_pre';
+dec $trial;
+}
+--enable_query_log
+xa recover;
+drop table t1;
diff --git a/sql/handler.cc b/sql/handler.cc
index ab4d9fd37c9..8c6270a042e 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -1740,6 +1740,7 @@ int ha_recover(HASH *commit_list)
for (info.len= MAX_XID_LIST_SIZE ;
info.list==0 && info.len > MIN_XID_LIST_SIZE; info.len/=2)
{
+ DBUG_EXECUTE_IF("min_xa_len", info.len = 16;);
info.list=(XID *)my_malloc(info.len*sizeof(XID), MYF(0));
}
if (!info.list)
diff --git a/storage/innobase/include/trx0sys.ic b/storage/innobase/include/trx0sys.ic
index 4e7c0ee1f3a..5d33f348822 100644
--- a/storage/innobase/include/trx0sys.ic
+++ b/storage/innobase/include/trx0sys.ic
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -353,10 +354,13 @@ trx_is_active(
}
trx = trx_get_on_id(trx_id);
- if (trx && (trx->conc_state == TRX_ACTIVE
- || trx->conc_state == TRX_PREPARED)) {
-
- return(TRUE);
+ if (trx) {
+ switch (trx->conc_state) {
+ case TRX_ACTIVE:
+ case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
+ return(TRUE);
+ }
}
return(FALSE);
diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h
index 4ade245f03e..9cbc7987eff 100644
--- a/storage/innobase/include/trx0trx.h
+++ b/storage/innobase/include/trx0trx.h
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2010, Innobase Oy. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -741,6 +742,8 @@ struct trx_struct{
#define TRX_ACTIVE 1
#define TRX_COMMITTED_IN_MEMORY 2
#define TRX_PREPARED 3 /* Support for 2PC/XA */
+#define TRX_PREPARED_RECOVERED 4 /* XA PREPARE transaction that
+ was returned to ha_recover() */
/* Transaction execution states when trx->conc_state == TRX_ACTIVE */
#define TRX_QUE_RUNNING 0 /* transaction is running */
diff --git a/storage/innobase/lock/lock0lock.c b/storage/innobase/lock/lock0lock.c
index 6a3ca383ade..28ecb30e2b7 100644
--- a/storage/innobase/lock/lock0lock.c
+++ b/storage/innobase/lock/lock0lock.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2015, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -4782,6 +4783,22 @@ print_rec:
}
#ifdef UNIV_DEBUG
+/** Validate the state of a transaction that holds locks */
+static void lock_trx_state_validate(const trx_t* trx)
+{
+ switch (trx->conc_state) {
+ case TRX_NOT_STARTED:
+ break;
+ case TRX_ACTIVE:
+ case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
+ case TRX_COMMITTED_IN_MEMORY:
+ return;
+
+ }
+ ut_ad(!"wrong state");
+}
+
/*********************************************************************//**
Validates the lock queue on a table.
@return TRUE if ok */
@@ -4798,9 +4815,7 @@ lock_table_queue_validate(
lock = UT_LIST_GET_FIRST(table->locks);
while (lock) {
- ut_a(((lock->trx)->conc_state == TRX_ACTIVE)
- || ((lock->trx)->conc_state == TRX_PREPARED)
- || ((lock->trx)->conc_state == TRX_COMMITTED_IN_MEMORY));
+ ut_d(lock_trx_state_validate(lock->trx));
if (!lock_get_wait(lock)) {
@@ -4848,15 +4863,7 @@ lock_rec_queue_validate(
lock = lock_rec_get_first(block, heap_no);
while (lock) {
- switch(lock->trx->conc_state) {
- case TRX_ACTIVE:
- case TRX_PREPARED:
- case TRX_COMMITTED_IN_MEMORY:
- break;
- default:
- ut_error;
- }
-
+ ut_d(lock_trx_state_validate(lock->trx));
ut_a(trx_in_trx_list(lock->trx));
if (lock_get_wait(lock)) {
@@ -4935,9 +4942,7 @@ lock_rec_queue_validate(
lock = lock_rec_get_first(block, heap_no);
while (lock) {
- ut_a(lock->trx->conc_state == TRX_ACTIVE
- || lock->trx->conc_state == TRX_PREPARED
- || lock->trx->conc_state == TRX_COMMITTED_IN_MEMORY);
+ ut_d(lock_trx_state_validate(lock->trx));
ut_a(trx_in_trx_list(lock->trx));
if (index) {
@@ -5014,10 +5019,8 @@ loop:
}
}
+ ut_d(lock_trx_state_validate(lock->trx));
ut_a(trx_in_trx_list(lock->trx));
- ut_a(lock->trx->conc_state == TRX_ACTIVE
- || lock->trx->conc_state == TRX_PREPARED
- || lock->trx->conc_state == TRX_COMMITTED_IN_MEMORY);
# ifdef UNIV_SYNC_DEBUG
/* Only validate the record queues when this thread is not
diff --git a/storage/innobase/read/read0read.c b/storage/innobase/read/read0read.c
index b87a3715f65..1de78aae708 100644
--- a/storage/innobase/read/read0read.c
+++ b/storage/innobase/read/read0read.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1997, 2009, Innobase Oy. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -272,15 +273,19 @@ read_view_open_now(
view->low_limit_id = view->low_limit_no;
n = 0;
- trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
/* No active transaction should be visible, except cr_trx */
- while (trx) {
- if (trx->id != cr_trx_id
- && (trx->conc_state == TRX_ACTIVE
- || trx->conc_state == TRX_PREPARED)) {
+ for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx;
+ trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+ if (trx->id == cr_trx_id) {
+ continue;
+ }
+ switch (trx->conc_state) {
+ case TRX_ACTIVE:
+ case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
read_view_set_nth_trx_id(view, n, trx->id);
n++;
@@ -296,8 +301,6 @@ read_view_open_now(
view->low_limit_no = trx->no;
}
}
-
- trx = UT_LIST_GET_NEXT(trx_list, trx);
}
view->n_trx_ids = n;
@@ -437,18 +440,15 @@ read_cursor_view_create_for_mysql(
view->low_limit_id = view->low_limit_no;
n = 0;
- trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
/* No active transaction should be visible */
-
- while (trx) {
-
- if (trx->conc_state == TRX_ACTIVE
- || trx->conc_state == TRX_PREPARED) {
-
- read_view_set_nth_trx_id(view, n, trx->id);
-
- n++;
+ for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx;
+ trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+ switch (trx->conc_state) {
+ case TRX_ACTIVE:
+ case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
+ read_view_set_nth_trx_id(view, n++, trx->id);
/* NOTE that a transaction whose trx number is <
trx_sys->max_trx_id can still be active, if it is
@@ -461,8 +461,6 @@ read_cursor_view_create_for_mysql(
view->low_limit_no = trx->no;
}
}
-
- trx = UT_LIST_GET_NEXT(trx_list, trx);
}
view->n_trx_ids = n;
diff --git a/storage/innobase/trx/trx0roll.c b/storage/innobase/trx/trx0roll.c
index 638e06191bc..a6fa9dc73a9 100644
--- a/storage/innobase/trx/trx0roll.c
+++ b/storage/innobase/trx/trx0roll.c
@@ -2,6 +2,7 @@
Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved.
Copyright (c) 2017, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -564,6 +565,7 @@ loop:
switch (trx->conc_state) {
case TRX_NOT_STARTED:
case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
continue;
case TRX_COMMITTED_IN_MEMORY:
diff --git a/storage/innobase/trx/trx0sys.c b/storage/innobase/trx/trx0sys.c
index 256e22d1b50..8fcc935e673 100644
--- a/storage/innobase/trx/trx0sys.c
+++ b/storage/innobase/trx/trx0sys.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2013, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -1031,7 +1032,7 @@ trx_sys_init_at_db_start(void)
trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
for (;;) {
-
+ ut_ad(trx->conc_state != TRX_PREPARED_RECOVERED);
if (trx->conc_state != TRX_PREPARED) {
rows_to_undo += trx->undo_no;
}
diff --git a/storage/innobase/trx/trx0trx.c b/storage/innobase/trx/trx0trx.c
index 151e70013f9..7a53a78f2d9 100644
--- a/storage/innobase/trx/trx0trx.c
+++ b/storage/innobase/trx/trx0trx.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2011, Innobase Oy. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -348,7 +349,8 @@ trx_free_prepared(
trx_t* trx) /*!< in, own: trx object */
{
ut_ad(mutex_own(&kernel_mutex));
- ut_a(trx->conc_state == TRX_PREPARED);
+ ut_a(trx->conc_state == TRX_PREPARED
+ || trx->conc_state == TRX_PREPARED_RECOVERED);
ut_a(trx->magic_n == TRX_MAGIC_N);
/* Prepared transactions are sort of active; they allow
@@ -932,10 +934,11 @@ trx_commit_off_kernel(
lsn = 0;
}
- ut_ad(trx->conc_state == TRX_ACTIVE || trx->conc_state == TRX_PREPARED);
+ ut_ad(trx->conc_state == TRX_ACTIVE || trx->conc_state == TRX_PREPARED
+ || trx->conc_state == TRX_PREPARED_RECOVERED);
ut_ad(mutex_own(&kernel_mutex));
- if (UNIV_UNLIKELY(trx->conc_state == TRX_PREPARED)) {
+ if (UNIV_UNLIKELY(trx->conc_state != TRX_ACTIVE)) {
ut_a(trx_n_prepared > 0);
trx_n_prepared--;
}
@@ -2072,6 +2075,7 @@ trx_recover_for_mysql(
while (trx) {
if (trx->conc_state == TRX_PREPARED) {
+ trx->conc_state = TRX_PREPARED_RECOVERED;
xid_list[count] = trx->xid;
if (count == 0) {
@@ -2096,13 +2100,25 @@ trx_recover_for_mysql(
count++;
if (count == len) {
- break;
+ goto partial;
}
}
trx = UT_LIST_GET_NEXT(trx_list, trx);
}
+ /* After returning the full list, reset the state, because
+ init_server_components() wants to recover the collection of
+ transactions twice, by first calling tc_log->open() and then
+ ha_recover() directly. */
+ for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx;
+ trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+ if (trx->conc_state == TRX_PREPARED_RECOVERED) {
+ trx->conc_state = TRX_PREPARED;
+ }
+ }
+
+partial:
mutex_exit(&kernel_mutex);
if (count > 0){
@@ -2144,7 +2160,8 @@ trx_get_trx_by_xid(
the same */
if (trx->is_recovered
- && trx->conc_state == TRX_PREPARED
+ && (trx->conc_state == TRX_PREPARED
+ || trx->conc_state == TRX_PREPARED_RECOVERED)
&& xid->gtrid_length == trx->xid.gtrid_length
&& xid->bqual_length == trx->xid.bqual_length
&& memcmp(xid->data, trx->xid.data,
diff --git a/storage/xtradb/include/trx0trx.h b/storage/xtradb/include/trx0trx.h
index fcfcb14ae3b..c3844cce84e 100644
--- a/storage/xtradb/include/trx0trx.h
+++ b/storage/xtradb/include/trx0trx.h
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2010, Innobase Oy. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -802,6 +803,8 @@ struct trx_struct{
#define TRX_ACTIVE 1
#define TRX_COMMITTED_IN_MEMORY 2
#define TRX_PREPARED 3 /* Support for 2PC/XA */
+#define TRX_PREPARED_RECOVERED 4 /* XA PREPARE transaction that
+ was returned to ha_recover() */
/* Transaction execution states when trx->conc_state == TRX_ACTIVE */
#define TRX_QUE_RUNNING 0 /* transaction is running */
diff --git a/storage/xtradb/lock/lock0lock.c b/storage/xtradb/lock/lock0lock.c
index 6e574eb282e..d1be28c7f30 100644
--- a/storage/xtradb/lock/lock0lock.c
+++ b/storage/xtradb/lock/lock0lock.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2015, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -4811,6 +4812,22 @@ print_rec:
}
#ifdef UNIV_DEBUG
+/** Validate the state of a transaction that holds locks */
+static void lock_trx_state_validate(const trx_t* trx)
+{
+ switch (trx->state) {
+ case TRX_NOT_STARTED:
+ break;
+ case TRX_ACTIVE:
+ case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
+ case TRX_COMMITTED_IN_MEMORY:
+ return;
+
+ }
+ ut_ad(!"wrong state");
+}
+
/*********************************************************************//**
Validates the lock queue on a table.
@return TRUE if ok */
@@ -4827,9 +4844,7 @@ lock_table_queue_validate(
lock = UT_LIST_GET_FIRST(table->locks);
while (lock) {
- ut_a(((lock->trx)->state == TRX_ACTIVE)
- || ((lock->trx)->state == TRX_PREPARED)
- || ((lock->trx)->state == TRX_COMMITTED_IN_MEMORY));
+ ut_d(lock_trx_state_validate(lock->trx));
if (!lock_get_wait(lock)) {
@@ -4877,15 +4892,7 @@ lock_rec_queue_validate(
lock = lock_rec_get_first(block, heap_no);
while (lock) {
- switch(lock->trx->state) {
- case TRX_ACTIVE:
- case TRX_PREPARED:
- case TRX_COMMITTED_IN_MEMORY:
- break;
- default:
- ut_error;
- }
-
+ ut_d(lock_trx_state_validate(lock->trx));
ut_a(trx_in_trx_list(lock->trx));
if (lock_get_wait(lock)) {
@@ -4964,9 +4971,7 @@ lock_rec_queue_validate(
lock = lock_rec_get_first(block, heap_no);
while (lock) {
- ut_a(lock->trx->state == TRX_ACTIVE
- || lock->trx->state == TRX_PREPARED
- || lock->trx->state == TRX_COMMITTED_IN_MEMORY);
+ ut_d(lock_trx_state_validate(lock->trx));
ut_a(trx_in_trx_list(lock->trx));
if (index) {
@@ -5043,10 +5048,8 @@ loop:
}
}
+ ut_d(lock_trx_state_validate(lock->trx));
ut_a(trx_in_trx_list(lock->trx));
- ut_a(lock->trx->state == TRX_ACTIVE
- || lock->trx->state == TRX_PREPARED
- || lock->trx->state == TRX_COMMITTED_IN_MEMORY);
# ifdef UNIV_SYNC_DEBUG
/* Only validate the record queues when this thread is not
diff --git a/storage/xtradb/trx/trx0roll.c b/storage/xtradb/trx/trx0roll.c
index 8c0438ab975..e715780f165 100644
--- a/storage/xtradb/trx/trx0roll.c
+++ b/storage/xtradb/trx/trx0roll.c
@@ -2,6 +2,7 @@
Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved.
Copyright (c) 2017, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -564,6 +565,7 @@ loop:
switch (trx->state) {
case TRX_NOT_STARTED:
case TRX_PREPARED:
+ case TRX_PREPARED_RECOVERED:
continue;
case TRX_COMMITTED_IN_MEMORY:
diff --git a/storage/xtradb/trx/trx0sys.c b/storage/xtradb/trx/trx0sys.c
index f801331dbf0..ea3196d182c 100644
--- a/storage/xtradb/trx/trx0sys.c
+++ b/storage/xtradb/trx/trx0sys.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2013, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -1355,7 +1356,7 @@ trx_sys_init_at_db_start(void)
trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
for (;;) {
-
+ ut_ad(trx->state != TRX_PREPARED_RECOVERED);
if (trx->state != TRX_PREPARED) {
rows_to_undo += trx->undo_no;
}
diff --git a/storage/xtradb/trx/trx0trx.c b/storage/xtradb/trx/trx0trx.c
index dd7b8bfdd1d..da9de9f004b 100644
--- a/storage/xtradb/trx/trx0trx.c
+++ b/storage/xtradb/trx/trx0trx.c
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2011, Innobase Oy. All Rights Reserved.
+Copyright (c) 2019, 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
@@ -481,7 +482,8 @@ trx_free_prepared(
trx_t* trx) /*!< in, own: trx object */
{
ut_ad(mutex_own(&kernel_mutex));
- ut_a(trx->state == TRX_PREPARED);
+ ut_a(trx->state == TRX_PREPARED
+ || trx->state == TRX_PREPARED_RECOVERED);
ut_a(trx->magic_n == TRX_MAGIC_N);
/* Prepared transactions are sort of active; they allow
@@ -1148,10 +1150,11 @@ trx_commit_off_kernel(
lsn = 0;
}
- ut_ad(trx->state == TRX_ACTIVE || trx->state == TRX_PREPARED);
+ ut_ad(trx->state == TRX_ACTIVE || trx->state == TRX_PREPARED
+ || trx->state == TRX_PREPARED_RECOVERED);
ut_ad(mutex_own(&kernel_mutex));
- if (UNIV_UNLIKELY(trx->state == TRX_PREPARED)) {
+ if (UNIV_UNLIKELY(trx->state != TRX_ACTIVE)) {
ut_a(trx_n_prepared > 0);
trx_n_prepared--;
}
@@ -2359,6 +2362,7 @@ trx_recover_for_mysql(
while (trx) {
if (trx->state == TRX_PREPARED) {
+ trx->state = TRX_PREPARED_RECOVERED;
xid_list[count] = trx->xid;
if (count == 0) {
@@ -2383,13 +2387,25 @@ trx_recover_for_mysql(
count++;
if (count == len) {
- break;
+ goto partial;
}
}
trx = UT_LIST_GET_NEXT(trx_list, trx);
}
+ /* After returning the full list, reset the state, because
+ init_server_components() wants to recover the collection of
+ transactions twice, by first calling tc_log->open() and then
+ ha_recover() directly. */
+ for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx;
+ trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+ if (trx->state == TRX_PREPARED_RECOVERED) {
+ trx->state = TRX_PREPARED;
+ }
+ }
+
+partial:
mutex_exit(&kernel_mutex);
if (count > 0){
@@ -2431,7 +2447,8 @@ trx_get_trx_by_xid(
the same */
if (trx->is_recovered
- && trx->state == TRX_PREPARED
+ && (trx->state == TRX_PREPARED
+ || trx->state == TRX_PREPARED_RECOVERED)
&& xid->gtrid_length == trx->xid.gtrid_length
&& xid->bqual_length == trx->xid.bqual_length
&& memcmp(xid->data, trx->xid.data,