List:Commits« Previous MessageNext Message »
From:marko.makela Date:April 11 2011 2:48pm
Subject:bzr commit into mysql-trunk-innodb branch (marko.makela:3572)
View as plain text  
#At file:///home/marko/innobase/dev/mysql2a/5.6-innodb/ based on revid:kevin.lewis@strippedpfp74tdu9dy

 3572 Marko Mäkelä	2011-04-11 [merge]
      Merge mysql-5.5-innodb to mysql-trunk-innodb.

    added:
      mysql-test/suite/innodb/r/innodb_bug59641.result
      mysql-test/suite/innodb/t/innodb_bug59641.test
    modified:
      client/mysqltest.cc
      sql/sql_class.cc
      storage/innobase/handler/ha_innodb.cc
      storage/innobase/handler/ha_innodb.h
      storage/innobase/handler/handler0alter.cc
      storage/innobase/include/trx0sys.h
      storage/innobase/include/trx0trx.h
      storage/innobase/include/trx0undo.h
      storage/innobase/lock/lock0lock.c
      storage/innobase/log/log0log.c
      storage/innobase/trx/trx0sys.c
      storage/innobase/trx/trx0trx.c
      storage/innobase/trx/trx0undo.c
=== modified file 'client/mysqltest.cc'
--- a/client/mysqltest.cc	revid:kevin.lewis@stripped4tdu9dy
+++ b/client/mysqltest.cc	revid:marko.makela@oracle.com-20110411144725-2ovv2caa6ylhpam6
@@ -4569,13 +4569,14 @@ static int my_kill(int pid, int sig)
   command  called command
 
   DESCRIPTION
-  shutdown [<timeout>]
+  shutdown_server [<timeout>]
 
 */
 
 void do_shutdown_server(struct st_command *command)
 {
-  int timeout=60, pid;
+  long timeout=60;
+  int pid;
   DYNAMIC_STRING ds_pidfile_name;
   MYSQL* mysql = &cur_con->mysql;
   static DYNAMIC_STRING ds_timeout;
@@ -4590,8 +4591,9 @@ void do_shutdown_server(struct st_comman
 
   if (ds_timeout.length)
   {
-    timeout= atoi(ds_timeout.str);
-    if (timeout == 0)
+    char* endptr;
+    timeout= strtol(ds_timeout.str, &endptr, 10);
+    if (*endptr != '\0')
       die("Illegal argument for timeout: '%s'", ds_timeout.str);
   }
   dynstr_free(&ds_timeout);
@@ -4633,7 +4635,7 @@ void do_shutdown_server(struct st_comman
       DBUG_PRINT("info", ("Process %d does not exist anymore", pid));
       DBUG_VOID_RETURN;
     }
-    DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout));
+    DBUG_PRINT("info", ("Sleeping, timeout: %ld", timeout));
     my_sleep(1000000L);
   }
 

=== added file 'mysql-test/suite/innodb/r/innodb_bug59641.result'
--- a/mysql-test/suite/innodb/r/innodb_bug59641.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb/r/innodb_bug59641.result	revid:marko.makela@stripped
@@ -0,0 +1,57 @@
+CREATE TABLE t(a INT PRIMARY KEY, b INT)ENGINE=InnoDB;
+INSERT INTO t VALUES(2,2),(4,4),(8,8),(16,16),(32,32);
+COMMIT;
+XA START '123';
+INSERT INTO t VALUES(1,1);
+XA END '123';
+XA PREPARE '123';
+XA START '456';
+INSERT INTO t VALUES(3,47),(5,67);
+UPDATE t SET b=2*b WHERE a BETWEEN 5 AND 8;
+XA END '456';
+XA PREPARE '456';
+XA START '789';
+UPDATE t SET b=4*a WHERE a=32;
+XA END '789';
+XA PREPARE '789';
+call mtr.add_suppression("Found 3 prepared XA transactions");
+SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+SELECT * FROM t;
+a	b
+1	1
+2	2
+3	47
+4	4
+5	134
+8	16
+16	16
+32	128
+COMMIT;
+SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+SELECT * FROM t;
+a	b
+1	1
+2	2
+3	47
+4	4
+5	134
+8	16
+16	16
+32	128
+COMMIT;
+XA RECOVER;
+formatID	gtrid_length	bqual_length	data
+1	3	0	789
+1	3	0	456
+1	3	0	123
+XA ROLLBACK '123';
+XA ROLLBACK '456';
+XA COMMIT '789';
+SELECT * FROM t;
+a	b
+2	2
+4	4
+8	8
+16	16
+32	128
+DROP TABLE t;

=== added file 'mysql-test/suite/innodb/t/innodb_bug59641.test'
--- a/mysql-test/suite/innodb/t/innodb_bug59641.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb/t/innodb_bug59641.test	revid:marko.makela@stripped10411144725-2ovv2caa6ylhpam6
@@ -0,0 +1,66 @@
+# Bug #59641 Prepared XA transaction causes shutdown hang after a crash
+
+-- source include/not_embedded.inc
+-- source include/have_innodb.inc
+
+CREATE TABLE t(a INT PRIMARY KEY, b INT)ENGINE=InnoDB;
+INSERT INTO t VALUES(2,2),(4,4),(8,8),(16,16),(32,32);
+COMMIT;
+XA START '123';
+INSERT INTO t VALUES(1,1);
+XA END '123';
+XA PREPARE '123';
+
+CONNECT (con1,localhost,root,,);
+CONNECTION con1;
+
+XA START '456';
+INSERT INTO t VALUES(3,47),(5,67);
+UPDATE t SET b=2*b WHERE a BETWEEN 5 AND 8;
+XA END '456';
+XA PREPARE '456';
+
+CONNECT (con2,localhost,root,,);
+CONNECTION con2;
+
+XA START '789';
+UPDATE t SET b=4*a WHERE a=32;
+XA END '789';
+XA PREPARE '789';
+
+# The server would issue this warning on restart.
+call mtr.add_suppression("Found 3 prepared XA transactions");
+
+# Kill the server without sending a shutdown command
+-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- shutdown_server 0
+-- source include/wait_until_disconnected.inc
+
+# Restart the server.
+-- exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+SELECT * FROM t;
+COMMIT;
+
+# Shut down the server. This would hang because of the bug.
+-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- shutdown_server
+-- source include/wait_until_disconnected.inc
+
+# Restart the server.
+-- exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+
+SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+SELECT * FROM t;
+COMMIT;
+XA RECOVER;
+XA ROLLBACK '123';
+XA ROLLBACK '456';
+XA COMMIT '789';
+SELECT * FROM t;
+
+DROP TABLE t;

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	revid:kevin.lewis@oracle.com-20110408184214-7ucy2pfp74tdu9dy
+++ b/sql/sql_class.cc	revid:marko.makela@strippeda6ylhpam6
@@ -3882,6 +3882,7 @@ bool xid_cache_insert(XID *xid, enum xa_
     xs->xa_state=xa_state;
     xs->xid.set(xid);
     xs->in_thd=0;
+    xs->rm_error=0;
     res=my_hash_insert(&xid_cache, (uchar*)xs);
   }
   mysql_mutex_unlock(&LOCK_xid_cache);

=== modified file 'storage/innobase/handler/ha_innodb.cc'
--- a/storage/innobase/handler/ha_innodb.cc	revid:kevin.lewis@stripped84214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/handler/ha_innodb.cc	revid:marko.makela@strippedvv2caa6ylhpam6
@@ -6681,10 +6681,6 @@ create_table_def(
 	DBUG_PRINT("enter", ("table_name: %s", table_name));
 
 	ut_a(trx->mysql_thd != NULL);
-	if (IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(table_name,
-						  (THD*) trx->mysql_thd)) {
-		DBUG_RETURN(HA_ERR_GENERIC);
-	}
 
 	/* MySQL does the name length check. But we do additional check
 	on the name length here */
@@ -6805,6 +6801,8 @@ err_col:
 			col_len);
 	}
 
+	srv_lower_case_table_names = lower_case_table_names;
+
 	error = row_create_table_for_mysql(table, trx);
 
 	if (error == DB_DUPLICATE_KEY) {
@@ -7222,36 +7220,15 @@ ha_innobase::create(
 		DBUG_RETURN(HA_ERR_TO_BIG_ROW);
 	}
 
-	/* Get the transaction associated with the current thd, or create one
-	if not yet created */
-
-	parent_trx = check_trx_exists(thd);
-
-	/* In case MySQL calls this in the middle of a SELECT query, release
-	possible adaptive hash latch to avoid deadlocks of threads */
-
-	trx_search_latch_release_if_reserved(parent_trx);
-
-	trx = innobase_trx_allocate(thd);
-
-	srv_lower_case_table_names = lower_case_table_names;
-
 	strcpy(name2, name);
 
 	normalize_table_name(norm_name, name2);
 
-	/* Latch the InnoDB data dictionary exclusively so that no deadlocks
-	or lock waits can happen in it during a table create operation.
-	Drop table etc. do this latching in row0mysql.c. */
-
-	row_mysql_lock_data_dictionary(trx);
-
 	/* Create the table definition in InnoDB */
 
 	/* Validate create options if innodb_strict_mode is set. */
 	if (!create_options_are_valid(thd, form, create_info)) {
-		error = ER_ILLEGAL_HA_CREATE_OPTION;
-		goto cleanup;
+		DBUG_RETURN(ER_ILLEGAL_HA_CREATE_OPTION);
 	}
 
 	if (create_info->key_block_size) {
@@ -7393,16 +7370,37 @@ ha_innobase::create(
 
 	/* Check for name conflicts (with reserved name) for
 	any user indices to be created. */
-	if (innobase_index_name_is_reserved(trx, form->key_info,
+	if (innobase_index_name_is_reserved(thd, form->key_info,
 					    form->s->keys)) {
-		error = -1;
-		goto cleanup;
+		DBUG_RETURN(-1);
+	}
+
+	if (IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(norm_name, thd)) {
+		DBUG_RETURN(HA_ERR_GENERIC);
 	}
 
 	if (create_info->options & HA_LEX_CREATE_TMP_TABLE) {
 		flags2 |= DICT_TF2_TEMPORARY;
 	}
 
+	/* Get the transaction associated with the current thd, or create one
+	if not yet created */
+
+	parent_trx = check_trx_exists(thd);
+
+	/* In case MySQL calls this in the middle of a SELECT query, release
+	possible adaptive hash latch to avoid deadlocks of threads */
+
+	trx_search_latch_release_if_reserved(parent_trx);
+
+	trx = innobase_trx_allocate(thd);
+
+	/* Latch the InnoDB data dictionary exclusively so that no deadlocks
+	or lock waits can happen in it during a table create operation.
+	Drop table etc. do this latching in row0mysql.c. */
+
+	row_mysql_lock_data_dictionary(trx);
+
 	error = create_table_def(trx, form, norm_name,
 		create_info->options & HA_LEX_CREATE_TMP_TABLE ? name2 : NULL,
 		flags, flags2);
@@ -7671,14 +7669,14 @@ ha_innobase::delete_table(
 
 	trx = innobase_trx_allocate(thd);
 
-	srv_lower_case_table_names = lower_case_table_names;
-
 	name_len = strlen(name);
 
 	ut_a(name_len < 1000);
 
 	/* Drop the table in InnoDB */
 
+	srv_lower_case_table_names = lower_case_table_names;
+
 	error = row_drop_table_for_mysql(norm_name, trx,
 					 thd_sql_command(thd)
 					 == SQLCOM_DROP_DB);
@@ -7790,8 +7788,6 @@ innobase_rename_table(
 	char*	norm_to;
 	char*	norm_from;
 
-	srv_lower_case_table_names = lower_case_table_names;
-
 	// Magic number 64 arbitrary
 	norm_to = (char*) my_malloc(strlen(to) + 64, MYF(0));
 	norm_from = (char*) my_malloc(strlen(from) + 64, MYF(0));
@@ -7806,6 +7802,8 @@ innobase_rename_table(
 		row_mysql_lock_data_dictionary(trx);
 	}
 
+	srv_lower_case_table_names = lower_case_table_names;
+
 	error = row_rename_table_for_mysql(
 		norm_from, norm_to, trx, lock_and_commit);
 
@@ -10790,7 +10788,7 @@ innobase_commit_by_xid(
 
 	if (trx) {
 		innobase_commit_low(trx);
-
+		trx_free_for_background(trx);
 		return(XA_OK);
 	} else {
 		return(XAER_NOTA);
@@ -10816,7 +10814,9 @@ innobase_rollback_by_xid(
 	trx = trx_get_trx_by_xid(xid);
 
 	if (trx) {
-		return(innobase_rollback_trx(trx));
+		int	ret = innobase_rollback_trx(trx);
+		trx_free_for_background(trx);
+		return(ret);
 	} else {
 		return(XAER_NOTA);
 	}
@@ -11994,17 +11994,17 @@ show_innodb_vars(
 
 /****************************************************************//**
 This function checks each index name for a table against reserved
-system default primary index name 'GEN_CLUST_INDEX'. If a name matches,
-this function pushes an warning message to the client, and returns true. */
+system default primary index name 'GEN_CLUST_INDEX'. If a name
+matches, this function pushes an warning message to the client,
+and returns true.
+@return true if the index name matches the reserved name */
 extern "C" UNIV_INTERN
 bool
 innobase_index_name_is_reserved(
 /*============================*/
-					/* out: true if an index name
-					matches the reserved name */
-	const trx_t*	trx,		/* in: InnoDB transaction handle */
-	const KEY*	key_info,	/* in: Indexes to be created */
-	ulint		num_of_keys)	/* in: Number of indexes to
+	THD*		thd,		/*!< in/out: MySQL connection */
+	const KEY*	key_info,	/*!< in: Indexes to be created */
+	ulint		num_of_keys)	/*!< in: Number of indexes to
 					be created. */
 {
 	const KEY*	key;
@@ -12016,7 +12016,7 @@ innobase_index_name_is_reserved(
 		if (innobase_strcasecmp(key->name,
 					innobase_index_reserve_name) == 0) {
 			/* Push warning to mysql */
-			push_warning_printf((THD*) trx->mysql_thd,
+			push_warning_printf(thd,
 					    MYSQL_ERROR::WARN_LEVEL_WARN,
 					    ER_WRONG_NAME_FOR_INDEX,
 					    "Cannot Create Index with name "

=== modified file 'storage/innobase/handler/ha_innodb.h'
--- a/storage/innobase/handler/ha_innodb.h	revid:kevin.lewis@stripped
+++ b/storage/innobase/handler/ha_innodb.h	revid:marko.makela@stripped
@@ -388,15 +388,14 @@ innobase_trx_allocate(
 This function checks each index name for a table against reserved
 system default primary index name 'GEN_CLUST_INDEX'. If a name
 matches, this function pushes an warning message to the client,
-and returns true. */
+and returns true.
+@return true if the index name matches the reserved name */
 extern "C"
 bool
 innobase_index_name_is_reserved(
 /*============================*/
-					/* out: true if the index name
-					matches the reserved name */
-	const trx_t*	trx,		/* in: InnoDB transaction handle */
-	const KEY*	key_info,	/* in: Indexes to be created */
-	ulint		num_of_keys);	/* in: Number of indexes to
+	THD*		thd,		/*!< in/out: MySQL connection */
+	const KEY*	key_info,	/*!< in: Indexes to be created */
+	ulint		num_of_keys);	/*!< in: Number of indexes to
 					be created. */
 

=== modified file 'storage/innobase/handler/handler0alter.cc'
--- a/storage/innobase/handler/handler0alter.cc	revid:kevin.lewis@oracle.com-20110408184214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/handler/handler0alter.cc	revid:marko.makela@strippedm-20110411144725-2ovv2caa6ylhpam6
@@ -629,21 +629,15 @@ innobase_add_index_cleanup(
 /*=======================*/
 	row_prebuilt_t*	prebuilt,		/*!< in/out: prebuilt */
 	trx_t*		trx,			/*!< in/out: transaction */
-	dict_table_t*	table,			/*!< in/out: table on which
+	dict_table_t*	table)			/*!< in/out: table on which
 						the indexes were going to be
 						created */
-	mem_heap_t*	heap)			/*!< in/own: heap that was
-						going to be used for the index
-						creation */
 {
-	mem_heap_free(heap);
-
 	trx_rollback_to_savepoint(trx, NULL);
 
 	ut_a(trx != prebuilt->trx);
 
 	trx_free_for_mysql(trx);
-	trx = NULL;
 
 	trx_commit_for_mysql(prebuilt->trx);
 
@@ -653,7 +647,7 @@ innobase_add_index_cleanup(
 
 		dict_mutex_enter_for_mysql();
 
-		/* Note: This check exludes the system tables. However, we
+		/* Note: This check excludes the system tables. However, we
 		should be safe because users cannot add indexes to system
 	        tables. */
 
@@ -706,43 +700,39 @@ ha_innobase::add_index(
 
 	update_thd();
 
-	heap = mem_heap_create(1024);
-
 	/* In case MySQL calls this in the middle of a SELECT query, release
 	possible adaptive hash latch to avoid deadlocks of threads. */
 	trx_search_latch_release_if_reserved(prebuilt->trx);
-	trx_start_if_not_started_xa(prebuilt->trx);
 
-	/* Create a background transaction for the operations on
-	the data dictionary tables. */
-	trx = innobase_trx_allocate(user_thd);
-	trx_start_if_not_started_xa(trx);
+	/* Check if the index name is reserved. */
+	if (innobase_index_name_is_reserved(user_thd, key_info, num_of_keys)) {
+		DBUG_RETURN(-1);
+	}
 
 	indexed_table = dict_table_open_on_name(prebuilt->table->name, FALSE);
 
 	innodb_table = indexed_table;
 
 	if (UNIV_UNLIKELY(!innodb_table)) {
-		innobase_add_index_cleanup(prebuilt, trx, NULL, heap);
-
 		DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
 	}
 
-	/* Check if the index name is reserved. */
-	if (innobase_index_name_is_reserved(trx, key_info, num_of_keys)) {
-		error = -1;
-	} else {
-		/* Check that index keys are sensible */
-		error = innobase_check_index_keys(key_info, num_of_keys,
-						  innodb_table);
-	}
+	/* Check that index keys are sensible */
+	error = innobase_check_index_keys(key_info, num_of_keys, innodb_table);
 
 	if (UNIV_UNLIKELY(error)) {
-		innobase_add_index_cleanup(prebuilt, trx, innodb_table, heap);
-
+		dict_table_close(innodb_table, FALSE);
 		DBUG_RETURN(error);
 	}
 
+	heap = mem_heap_create(1024);
+	trx_start_if_not_started(prebuilt->trx);
+
+	/* Create a background transaction for the operations on
+	the data dictionary tables. */
+	trx = innobase_trx_allocate(user_thd);
+	trx_start_if_not_started(trx);
+
 	/* We don't want this table to be evicted from the cache while we
 	are building an index on it. Another issue is that while we are
 	building the index this table could be referred to in a foreign
@@ -830,9 +820,10 @@ ha_innobase::add_index(
 			ut_d(dict_table_check_for_dup_indexes(innodb_table,
 							      FALSE));
 			row_mysql_unlock_data_dictionary(trx);
+			mem_heap_free(heap);
 
 			innobase_add_index_cleanup(
-				prebuilt, trx, innodb_table, heap);
+				prebuilt, trx, innodb_table);
 
 			DBUG_RETURN(error);
 		}
@@ -929,11 +920,11 @@ error_handling:
 		error = row_merge_rename_tables(innodb_table, indexed_table,
 						tmp_name, trx);
 
-		if (error != DB_SUCCESS) {
+		dict_table_close(innodb_table, dict_locked);
+		ut_a(innodb_table->n_ref_count == 1);
 
-			dict_table_close(innodb_table, dict_locked);
+		if (error != DB_SUCCESS) {
 
-			ut_a(innodb_table->n_ref_count == 1);
 			ut_a(indexed_table->n_ref_count == 0);
 
 			row_merge_drop_table(trx, indexed_table);
@@ -953,10 +944,6 @@ error_handling:
 
 		trx_commit_for_mysql(prebuilt->trx);
 
-		dict_table_close(innodb_table, dict_locked);
-
-		ut_a(innodb_table->n_ref_count == 1);
-
 		row_prebuilt_free(prebuilt, TRUE);
 
 		ut_a(innodb_table->n_ref_count == 0);

=== modified file 'storage/innobase/include/trx0sys.h'
--- a/storage/innobase/include/trx0sys.h	revid:kevin.lewis@stripped0408184214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/include/trx0sys.h	revid:marko.makela@stripped2ovv2caa6ylhpam6
@@ -494,7 +494,7 @@ trx_sys_get_n_trx(void);
 /*===================*/
 
 /*********************************************************************
-Check if there are any active transactions.
+Check if there are any active (non-prepared) transactions.
 @return total number of active transactions or 0 if none */
 UNIV_INTERN
 ulint
@@ -677,6 +677,10 @@ struct trx_sys_struct{
 	rw_lock_t	lock;		/*!< read-write lock protecting most
 					fields in this structure except when
 					noted otherwise */
+	ulint		n_mysql_trx;	/*!< Number of transactions currently
+					allocated for MySQL */
+	ulint		n_prepared_trx;	/*!< Number of transactions currently
+					in the XA PREPARED state */
 	trx_id_t	max_trx_id;	/*!< The smallest number not yet
 					assigned as a transaction id or
 					transaction number */

=== modified file 'storage/innobase/include/trx0trx.h'
--- a/storage/innobase/include/trx0trx.h	revid:kevin.lewis@strippedpfp74tdu9dy
+++ b/storage/innobase/include/trx0trx.h	revid:marko.makela@stripped
@@ -42,10 +42,6 @@ Created 3/26/1996 Heikki Tuuri
 /** Dummy session used currently in MySQL interface */
 extern sess_t*	trx_dummy_sess;
 
-/** Number of transactions currently allocated for MySQL: protected by
-trx_sys->lock */
-extern ulint	trx_n_mysql_transactions;
-
 /********************************************************************//**
 Releases the search latch if trx has reserved it. */
 UNIV_INTERN
@@ -93,18 +89,26 @@ trx_t*
 trx_allocate_for_background(void);
 /*=============================*/
 /********************************************************************//**
-Frees a transaction object for MySQL. */
+Frees a transaction object of a background operation of the master thread. */
 UNIV_INTERN
 void
-trx_free_for_mysql(
-/*===============*/
+trx_free_for_background(
+/*====================*/
 	trx_t*	trx);	/*!< in, own: trx object */
 /********************************************************************//**
-Frees a transaction object of a background operation of the master thread. */
+At shutdown, frees a transaction object that is in the PREPARED state. */
 UNIV_INTERN
 void
-trx_free_for_background(
-/*====================*/
+trx_free_prepared(
+/*==============*/
+	trx_t*	trx)	/*!< in, own: trx object */
+	UNIV_COLD __attribute__((nonnull));
+/********************************************************************//**
+Frees a transaction object for MySQL. */
+UNIV_INTERN
+void
+trx_free_for_mysql(
+/*===============*/
 	trx_t*	trx);	/*!< in, own: trx object */
 /****************************************************************//**
 Creates trx objects for transactions and initializes the trx list of
@@ -545,7 +549,7 @@ struct trx_struct{
 					and ACTIVE->PREPARED->COMMITTED
 					are possible when trx->in_trx_list.
 					The transition ACTIVE->PREPARED is
-					not protected by any mutex.
+					protected by trx_sys->lock.
 					The transitions ACTIVE->COMMITTED
 					and PREPARED->COMMITTED are protected
 					by lock_sys->mutex and trx->mutex.

=== modified file 'storage/innobase/include/trx0undo.h'
--- a/storage/innobase/include/trx0undo.h	revid:kevin.lewis@stripped
+++ b/storage/innobase/include/trx0undo.h	revid:marko.makela@oracle.com-20110411144725-2ovv2caa6ylhpam6
@@ -296,6 +296,15 @@ void
 trx_undo_insert_cleanup(
 /*====================*/
 	trx_t*	trx);	/*!< in: transaction handle */
+
+/********************************************************************//**
+At shutdown, frees the undo logs of a PREPARED transaction. */
+UNIV_INTERN
+void
+trx_undo_free_prepared(
+/*===================*/
+	trx_t*	trx)	/*!< in/out: PREPARED transaction */
+	UNIV_COLD __attribute__((nonnull));
 #endif /* !UNIV_HOTBACKUP */
 /***********************************************************//**
 Parses the redo log entry of an undo log page initialization.

=== modified file 'storage/innobase/lock/lock0lock.c'
--- a/storage/innobase/lock/lock0lock.c	revid:kevin.lewis@oracle.com-20110408184214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/lock/lock0lock.c	revid:marko.makela@stripped11144725-2ovv2caa6ylhpam6
@@ -6226,15 +6226,20 @@ lock_trx_release_locks(
 /*===================*/
 	trx_t*	trx)	/*!< in/out: transaction */
 {
+	if (UNIV_UNLIKELY(trx_state_eq(trx, TRX_STATE_PREPARED))) {
+		rw_lock_x_lock(&trx_sys->lock);
+		ut_a(trx_sys->n_prepared_trx > 0);
+		trx_sys->n_prepared_trx--;
+		rw_lock_x_unlock(&trx_sys->lock);
+	} else {
+		ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
+	}
+
 	/* The transition of trx->state to TRX_STATE_COMMITTED_IN_MEMORY
 	is protected by both the lock_sys->mutex and the trx->mutex. */
 	lock_mutex_enter();
 	trx_mutex_enter(trx);
 
-	ut_ad(trx->state == TRX_STATE_ACTIVE
-	      || trx->state == TRX_STATE_PREPARED);
-	ut_ad(trx->in_trx_list);
-
 	/* The following assignment makes the transaction committed in memory
 	and makes its changes to data visible to other transactions.
 	NOTE that there is a small discrepancy from the strict formal

=== modified file 'storage/innobase/log/log0log.c'
--- a/storage/innobase/log/log0log.c	revid:kevin.lewis@stripped0408184214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/log/log0log.c	revid:marko.makela@stripped2caa6ylhpam6
@@ -3141,9 +3141,10 @@ loop:
 		goto loop;
 	}
 
-	/* Check that there are no longer transactions. We need this wait even
-	for the 'very fast' shutdown, because the InnoDB layer may have
-	committed or prepared transactions and we don't want to lose them. */
+	/* Check that there are no longer transactions, except for
+	PREPARED ones. We need this wait even for the 'very fast'
+	shutdown, because the InnoDB layer may have committed or
+	prepared transactions and we don't want to lose them. */
 
 	total_trx = trx_sys_any_active_transactions();
 
@@ -3177,9 +3178,10 @@ loop:
 
 			switch (active_thd) {
 			case SRV_NONE:
-				/* This shouldn't happen because we've already
-				checked for this case before entering the if().
-				We handle it here to avoid a compiler worning. */
+				/* This shouldn't happen because we've
+				already checked for this case before
+				entering the if().  We handle it here
+				to avoid a compiler warning. */
 				ut_error;
 			case SRV_WORKER:
 				thread_type = "worker threads";

=== modified file 'storage/innobase/trx/trx0sys.c'
--- a/storage/innobase/trx/trx0sys.c	revid:kevin.lewis@stripped
+++ b/storage/innobase/trx/trx0sys.c	revid:marko.makela@strippedom-20110411144725-2ovv2caa6ylhpam6
@@ -37,6 +37,7 @@ Created 3/26/1996 Heikki Tuuri
 #include "trx0rseg.h"
 #include "trx0undo.h"
 #include "srv0srv.h"
+#include "srv0start.h"
 #include "trx0purge.h"
 #include "log0log.h"
 #include "log0recv.h"
@@ -1634,9 +1635,11 @@ trx_sys_close(void)
 /*===============*/
 {
 	ulint		i;
+	trx_t*		trx;
 	read_view_t*	view;
 
 	ut_ad(trx_sys != NULL);
+	ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS);
 
 	/* Check that all read views are closed except read view owned
 	by a purge. */
@@ -1671,6 +1674,14 @@ trx_sys_close(void)
 	trx_doublewrite = NULL;
 
 	rw_lock_x_lock(&trx_sys->lock);
+
+	/* Only prepared transactions may be left in the system. Free them. */
+	ut_a(UT_LIST_GET_LEN(trx_sys->trx_list) == trx_sys->n_prepared_trx);
+
+	while ((trx = UT_LIST_GET_FIRST(trx_sys->trx_list)) != NULL) {
+		trx_free_prepared(trx);
+	}
+
 	mutex_free(&trx_sys->read_view_mutex);
 
 	/* There can't be any active transactions. */
@@ -1713,7 +1724,7 @@ trx_sys_close(void)
 #endif /* !UNIV_HOTBACKUP */
 
 /*********************************************************************
-Check if there are any active transactions.
+Check if there are any active (non-prepared) transactions.
 @return total number of active transactions or 0 if none */
 UNIV_INTERN
 ulint
@@ -1725,7 +1736,9 @@ trx_sys_any_active_transactions(void)
 	rw_lock_s_lock(&trx_sys->lock);
 
 	total_trx = UT_LIST_GET_LEN(trx_sys->trx_list)
-	       	  + trx_n_mysql_transactions;
+		+ trx_sys->n_mysql_trx;
+	ut_a(total_trx >= trx_sys->n_prepared_trx);
+	total_trx -= trx_sys->n_prepared_trx;
 
 	rw_lock_s_unlock(&trx_sys->lock);
 

=== modified file 'storage/innobase/trx/trx0trx.c'
--- a/storage/innobase/trx/trx0trx.c	revid:kevin.lewis@stripped8184214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/trx/trx0trx.c	revid:marko.makela@strippeda6ylhpam6
@@ -49,10 +49,6 @@ Created 3/26/1996 Heikki Tuuri
 /** Dummy session used currently in MySQL interface */
 UNIV_INTERN sess_t*		trx_dummy_sess = NULL;
 
-/** Number of transactions currently allocated for MySQL: protected by
-trx_sys->lock */
-UNIV_INTERN ulint		trx_n_mysql_transactions = 0;
-
 #ifdef UNIV_PFS_MUTEX
 /* Key to register the mutex with performance schema */
 UNIV_INTERN mysql_pfs_key_t	trx_mutex_key;
@@ -171,7 +167,7 @@ trx_allocate_for_mysql(void)
 
 	rw_lock_x_lock(&trx_sys->lock);
 
-	++trx_n_mysql_transactions;
+	trx_sys->n_mysql_trx++;
 
 	ut_d(trx->in_mysql_trx_list = TRUE);
 	UT_LIST_ADD_FIRST(mysql_trx_list, trx_sys->mysql_trx_list, trx);
@@ -232,8 +228,6 @@ trx_free(
 		putc('\n', stderr);
 	}
 
-	trx_mutex_enter(trx);
-
 	ut_a(trx->magic_n == TRX_MAGIC_N);
 
 	trx->magic_n = 11112222;
@@ -266,16 +260,12 @@ trx_free(
 		mem_heap_free(trx->global_read_view_heap);
 	}
 
-	trx->global_read_view = NULL;
-
 	ut_a(trx->read_view == NULL);
 
 	ut_a(ib_vector_is_empty(trx->autoinc_locks));
 	/* We allocated a dedicated heap for the vector. */
 	ib_vector_free(trx->autoinc_locks);
 
-	trx_mutex_exit(trx);
-
 	mutex_free(&trx->mutex);
 
 	mem_free(trx);
@@ -293,6 +283,55 @@ trx_free_for_background(
 }
 
 /********************************************************************//**
+At shutdown, frees a transaction object that is in the PREPARED state. */
+UNIV_INTERN
+void
+trx_free_prepared(
+/*==============*/
+	trx_t*	trx)	/*!< in, own: trx object */
+{
+#ifdef UNIV_SYNC_DEBUG
+	ut_ad(rw_lock_own(&trx_sys->lock, RW_LOCK_EX));
+#endif /* UNIV_SYNC_DEBUG */
+
+	ut_a(trx_state_eq(trx, TRX_STATE_PREPARED));
+	ut_a(trx->magic_n == TRX_MAGIC_N);
+
+	trx_undo_free_prepared(trx);
+
+	mutex_free(&trx->undo_mutex);
+
+	if (trx->undo_no_arr) {
+		trx_undo_arr_free(trx->undo_no_arr);
+	}
+
+	ut_a(trx->lock.wait_lock == NULL);
+	ut_a(trx->lock.wait_thr == NULL);
+
+	ut_a(!trx->has_search_latch);
+
+	ut_a(trx->dict_operation_lock_mode == 0);
+
+	if (trx->lock.lock_heap) {
+		mem_heap_free(trx->lock.lock_heap);
+	}
+
+	ut_a(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0);
+
+	if (trx->global_read_view_heap) {
+		mem_heap_free(trx->global_read_view_heap);
+	}
+
+	ut_a(ib_vector_is_empty(trx->autoinc_locks));
+	ib_vector_free(trx->autoinc_locks);
+
+	UT_LIST_REMOVE(trx_list, trx_sys->trx_list, trx);
+
+	mutex_free(&trx->mutex);
+	mem_free(trx);
+}
+
+/********************************************************************//**
 Frees a transaction object for MySQL. */
 UNIV_INTERN
 void
@@ -308,7 +347,7 @@ trx_free_for_mysql(
 
 	ut_ad(trx_sys_validate_trx_list());
 
-	--trx_n_mysql_transactions;
+	trx_sys->n_mysql_trx--;
 
 	rw_lock_x_unlock(&trx_sys->lock);
 
@@ -401,6 +440,7 @@ trx_resurrect_insert(
 			if (srv_force_recovery == 0) {
 
 				trx->state = TRX_STATE_PREPARED;
+				trx_sys->n_prepared_trx++;
 			} else {
 				fprintf(stderr,
 					"InnoDB: Since innodb_force_recovery"
@@ -459,6 +499,11 @@ trx_resurrect_update_in_prepared_state(
 			" was in the XA prepared state.\n", trx->id);
 
 		if (srv_force_recovery == 0) {
+			if (trx_state_eq(trx, TRX_STATE_NOT_STARTED)) {
+				trx_sys->n_prepared_trx++;
+			} else {
+				ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED));
+			}
 
 			trx->state = TRX_STATE_PREPARED;
 		} else {
@@ -550,7 +595,7 @@ trx_lists_init_at_db_start(void)
 			continue;
 		}
 
-		/* Ressurrect transactions that were doing inserts. */
+		/* Resurrect transactions that were doing inserts. */
 		for (undo = UT_LIST_GET_FIRST(rseg->insert_undo_list);
 		     undo != NULL;
 		     undo = UT_LIST_GET_NEXT(undo_list, undo)) {
@@ -850,6 +895,7 @@ trx_commit(
 	/* Remove the transaction from the list of active transactions
 	now that it no longer holds any user locks. */
 
+	ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY));
 	rw_lock_x_lock(&trx_sys->lock);
 	ut_ad(trx->in_trx_list);
 	ut_d(trx->in_trx_list = FALSE);
@@ -1561,7 +1607,10 @@ trx_prepare(
 
 	/*--------------------------------------*/
 	ut_a(trx->state == TRX_STATE_ACTIVE);
+	rw_lock_x_lock(&trx_sys->lock);
 	trx->state = TRX_STATE_PREPARED;
+	trx_sys->n_prepared_trx++;
+	rw_lock_x_unlock(&trx_sys->lock);
 	/*--------------------------------------*/
 
 	if (lsn) {
@@ -1727,7 +1776,8 @@ trx_get_trx_by_xid(
 		of gtrid_length+bqual_length bytes should be
 		the same */
 
-		if (trx_state_eq(trx, TRX_STATE_PREPARED)
+		if (trx->is_recovered
+		    && trx_state_eq(trx, TRX_STATE_PREPARED)
 		    && xid->gtrid_length == trx->xid.gtrid_length
 		    && xid->bqual_length == trx->xid.bqual_length
 		    && memcmp(xid->data, trx->xid.data,

=== modified file 'storage/innobase/trx/trx0undo.c'
--- a/storage/innobase/trx/trx0undo.c	revid:kevin.lewis@stripped214-7ucy2pfp74tdu9dy
+++ b/storage/innobase/trx/trx0undo.c	revid:marko.makela@strippedlhpam6
@@ -36,6 +36,7 @@ Created 3/26/1996 Heikki Tuuri
 #include "trx0rseg.h"
 #include "trx0trx.h"
 #include "srv0srv.h"
+#include "srv0start.h"
 #include "trx0rec.h"
 #include "trx0purge.h"
 #include "srv0mon.h"
@@ -1983,4 +1984,31 @@ trx_undo_insert_cleanup(
 
 	mutex_exit(&(rseg->mutex));
 }
+
+/********************************************************************//**
+At shutdown, frees the undo logs of a PREPARED transaction. */
+UNIV_INTERN
+void
+trx_undo_free_prepared(
+/*===================*/
+	trx_t*	trx)	/*!< in/out: PREPARED transaction */
+{
+	mutex_enter(&trx->rseg->mutex);
+
+	ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS);
+
+	if (trx->update_undo) {
+		ut_a(trx->update_undo->state == TRX_UNDO_PREPARED);
+		UT_LIST_REMOVE(undo_list, trx->rseg->update_undo_list,
+			       trx->update_undo);
+		trx_undo_mem_free(trx->update_undo);
+	}
+	if (trx->insert_undo) {
+		ut_a(trx->insert_undo->state == TRX_UNDO_PREPARED);
+		UT_LIST_REMOVE(undo_list, trx->rseg->insert_undo_list,
+			       trx->insert_undo);
+		trx_undo_mem_free(trx->insert_undo);
+	}
+	mutex_exit(&trx->rseg->mutex);
+}
 #endif /* !UNIV_HOTBACKUP */

Attachment: [text/bzr-bundle] bzr/marko.makela@oracle.com-20110411144725-2ovv2caa6ylhpam6.bundle
Thread
bzr commit into mysql-trunk-innodb branch (marko.makela:3572) marko.makela11 Apr