diff options
author | Thirunarayanan Balathandayuthapani <thiru@mariadb.com> | 2019-04-24 12:31:24 +0530 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2019-04-24 11:46:14 +0300 |
commit | d5da8ae04d57556f517c0f03afeafe73c6cc75d1 (patch) | |
tree | d12ada3a85fbb66d7807a0daf1d755c5cfa4dae3 | |
parent | cb8d888c42aa9504db2df686f0e963b99079e287 (diff) | |
download | mariadb-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.result | 310 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/xa_debug.test | 45 | ||||
-rw-r--r-- | sql/handler.cc | 1 | ||||
-rw-r--r-- | storage/innobase/include/trx0sys.ic | 12 | ||||
-rw-r--r-- | storage/innobase/include/trx0trx.h | 3 | ||||
-rw-r--r-- | storage/innobase/lock/lock0lock.c | 39 | ||||
-rw-r--r-- | storage/innobase/read/read0read.c | 36 | ||||
-rw-r--r-- | storage/innobase/trx/trx0roll.c | 2 | ||||
-rw-r--r-- | storage/innobase/trx/trx0sys.c | 3 | ||||
-rw-r--r-- | storage/innobase/trx/trx0trx.c | 27 | ||||
-rw-r--r-- | storage/xtradb/include/trx0trx.h | 3 | ||||
-rw-r--r-- | storage/xtradb/lock/lock0lock.c | 39 | ||||
-rw-r--r-- | storage/xtradb/trx/trx0roll.c | 2 | ||||
-rw-r--r-- | storage/xtradb/trx/trx0sys.c | 3 | ||||
-rw-r--r-- | storage/xtradb/trx/trx0trx.c | 27 |
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, |