Commit 6861b1bf authored by unknown's avatar unknown
Browse files

A fix and a test case for Bug#12243 "MySQL Server crashes with 2

cursors (+ commit)" and Bug#11832 "Server crash with InnoDB + Cursors"
See comments to the changed files.


innobase/include/read0read.h:
  - add cursor_view_t::n_mysql_tables_in_use
innobase/read/read0read.c:
  - maintain cursor_view_t::n_mysql_tables_in_use. InnoDB
  maintains trx->n_mysql_tables_in_use to know when it can auto-commit
  a read-only statement. When this count drops to zero,
  MySQL has ended processing of such statement and InnoDB can commit.
  Cursors should not break this invariant, and should exclude the tables
  used in a cursor from the count of active tables.
  When a cursor is closed, the number of its tables is added back, 
  to ensure that close_thread_tables->unlock_external->
  ha_innobase::external_lock(F_UNLCK) won't drop the count in trx 
  below zero.
innobase/row/row0sel.c:
  - remove the restoration of the global read view from 
  row_search_for_mysql: MySQL may call row_search_for_mysql
  more than once when fetching a row for a cursor (e.g. if there
  is a WHERE clause that filters out some rows).
sql/ha_innodb.cc:
  - add more verbose printout for the case when we close an InnoDB
  connection without priorlly issuing a commit or rollback. The problem
  should be investigated.
tests/mysql_client_test.c:
  - add a test case for Bug#12243 "MySQL Server crashes with 2 cursors 
  (+ commit)"
parent cf1a9d3b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -136,6 +136,9 @@ struct cursor_view_struct{
				/* Memory heap for the cursor view */
	read_view_t*	read_view;	
				/* Consistent read view of the cursor*/
	ulint		n_mysql_tables_in_use;
                                /* number of Innobase tables used in the
				  processing of this cursor */
};

#ifndef UNIV_NONINL
+9 −0
Original line number Diff line number Diff line
@@ -286,6 +286,11 @@ read_cursor_view_create_for_mysql(
	curview = (cursor_view_t*) mem_heap_alloc(heap, sizeof(cursor_view_t));
	curview->heap = heap;

        /* Drop cursor tables from consideration when evaluating the need of
          auto-commit */
	curview->n_mysql_tables_in_use = cr_trx->n_mysql_tables_in_use;
	cr_trx->n_mysql_tables_in_use = 0;

	mutex_enter(&kernel_mutex);

	curview->read_view = read_view_create_low(
@@ -360,6 +365,10 @@ read_cursor_view_close_for_mysql(
	ut_a(curview->read_view); 
	ut_a(curview->heap);

        /* Add cursor's tables to the global count of active tables that
          belong to this transaction */
	trx->n_mysql_tables_in_use += curview->n_mysql_tables_in_use;

	mutex_enter(&kernel_mutex);

	read_view_close(curview->read_view);
+0 −12
Original line number Diff line number Diff line
@@ -3101,12 +3101,6 @@ row_search_for_mysql(
"InnoDB: how you can resolve the problem.\n",
				prebuilt->table->name);

		/* Restore a global read view back to a transaction. This 
		forces MySQL always to set a cursor view before fetch from
		a cursor. */

		trx->read_view = trx->global_read_view;

		return(DB_ERROR);
	}

@@ -4098,12 +4092,6 @@ row_search_for_mysql(
	}

func_exit:
	/* Restore a global read view back to a transaction. This 
	forces MySQL always to set a cursor view before fetch from
	a cursor. */

	trx->read_view = trx->global_read_view;

	trx->op_info = "";
	if (UNIV_LIKELY_NULL(heap)) {
		mem_heap_free(heap);
+9 −0
Original line number Diff line number Diff line
@@ -2146,6 +2146,15 @@ innobase_close_connection(

	ut_a(trx);

        if (trx->active_trans == 0
	    && trx->conc_state != TRX_NOT_STARTED) {
	    
	        fprintf(stderr,
"InnoDB: Error: trx->active_trans == 0\n"
"InnoDB: but trx->conc_state != TRX_NOT_STARTED\n");
	}


	if (trx->conc_state != TRX_NOT_STARTED &&
            global_system_variables.log_warnings)
          sql_print_warning("MySQL is closing a connection that has an active "
+61 −0
Original line number Diff line number Diff line
@@ -14179,6 +14179,66 @@ static void test_bug11901()
  myquery(rc);
}

/* Bug#12243: multiple cursors, crash in a fetch after commit. */

static void test_bug12243()
{
  MYSQL_STMT *stmt1, *stmt2;
  int rc;
  const char *stmt_text;
  ulong type;

  myheader("test_bug12243");

  if (! have_innodb)
  {
    if (!opt_silent)
      printf("This test requires InnoDB.\n");
    return;
  }

  /* create tables */
  mysql_query(mysql, "drop table if exists t1");
  mysql_query(mysql, "create table t1 (a int) engine=InnoDB");
  rc= mysql_query(mysql, "insert into t1 (a) values (1), (2)");
  myquery(rc);
  mysql_autocommit(mysql, FALSE);
  /* create statement */
  stmt1= mysql_stmt_init(mysql);
  stmt2= mysql_stmt_init(mysql);
  type= (ulong) CURSOR_TYPE_READ_ONLY;
  mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type);
  mysql_stmt_attr_set(stmt2, STMT_ATTR_CURSOR_TYPE, (const void*) &type);

  stmt_text= "select a from t1";

  rc= mysql_stmt_prepare(stmt1, stmt_text, strlen(stmt_text));
  check_execute(stmt1, rc);
  rc= mysql_stmt_execute(stmt1);
  check_execute(stmt1, rc);
  rc= mysql_stmt_fetch(stmt1);
  check_execute(stmt1, rc);

  rc= mysql_stmt_prepare(stmt2, stmt_text, strlen(stmt_text));
  check_execute(stmt2, rc);
  rc= mysql_stmt_execute(stmt2);
  check_execute(stmt2, rc);
  rc= mysql_stmt_fetch(stmt2);
  check_execute(stmt2, rc);

  rc= mysql_stmt_close(stmt1);
  check_execute(stmt1, rc);
  rc= mysql_commit(mysql);
  myquery(rc);
  rc= mysql_stmt_fetch(stmt2);
  check_execute(stmt2, rc);

  mysql_stmt_close(stmt2);
  rc= mysql_query(mysql, "drop table t1");
  myquery(rc);
  mysql_autocommit(mysql, TRUE);                /* restore default */
}

/*
  Read and parse arguments and MySQL options from my.cnf
*/
@@ -14427,6 +14487,7 @@ static struct my_tests_st my_tests[]= {
  { "test_bug12001", test_bug12001 },
  { "test_bug11909", test_bug11909 },
  { "test_bug11901", test_bug11901 },
  { "test_bug12243", test_bug12243 },
  { 0, 0 }
};