List:Commits« Previous MessageNext Message »
From:Staale Smedseng Date:May 19 2009 8:19pm
Subject:bzr commit into mysql-5.1-bugteam branch (staale.smedseng:2888) Bug#43560
View as plain text  
#At file:///export/home/tmp/ss156133/z/43560-51/ based on revid:gshchepa@stripped

 2888 Staale Smedseng	2009-05-19
      Bug #43560 client crashes in mysql_stmt_execute/
      mysql_stmt_close after connection loss
      
      In the event of an unexpected disconnect from the server side
      (e.g., due to a server kill and restart), a C API client may
      experience a SIGSEGV in subsequent calls to functions related
      to prepared statements in the client library.
            
      Solution: The invalidation of client-side prepared statements
      in the event of connection loss is factored out into a new
      function mysql_prune_stmt_list(). The invalidation now happens
      in conjunction with the end_server() function, rather than
      after-the-fact in mysql_reconnect() as was previously the
      case.
      
      A test case for this bug is introduced in MTR test
      main.mysql_client_test. A corresponding debug feature is
      introduced in the server (close_conn_after_stmt_execute) to
      facilitate connection teardown.
     @ libmysql/client_settings.h
        Declaration of new function mysql_prune_stmt_list().
     @ sql-common/client.c
        Definition and use of new function mysql_prune_stmt_list().
        Removal of previously used code in mysql_reconnect().
     @ sql/sql_prepare.cc
        The debug feature close_conn_after_stmt_execute will close the
        current connection after executing the current prepared
        statement.
     @ tests/mysql_client_test.c
        A new function test_bug43560() is introduced in
        mysql_client_test.c. It sets up a table, turns off auto
        reconnect, activates the close_conn_after_stmt_execute, and
        tests for the correct error states when subsequently
        attempting to execute prepared statements.
                
        The function switches to a separate connection using the TCP
        protocol for duration of the subtest due to the semantics of
        buffering with the AF_LOCAL protocol used by default.
        
        Functions client_connect() and client_disconnect() are
        modified to handle opening/closing of other than the default
        connection, as well as given parameters for specifying
        protocol type and auto reconnect mode.

    modified:
      libmysql/client_settings.h
      sql-common/client.c
      sql/sql_prepare.cc
      tests/mysql_client_test.c
=== modified file 'libmysql/client_settings.h'
--- a/libmysql/client_settings.h	2007-09-29 19:31:08 +0000
+++ b/libmysql/client_settings.h	2009-05-19 20:19:52 +0000
@@ -41,6 +41,7 @@ my_bool handle_local_infile(MYSQL *mysql
 
 void mysql_read_default_options(struct st_mysql_options *options,
 				const char *filename,const char *group);
+void mysql_prune_stmt_list(MYSQL *mysql);
 void mysql_detach_stmt_list(LIST **stmt_list, const char *func_name);
 MYSQL * STDCALL
 cli_mysql_real_connect(MYSQL *mysql,const char *host, const char *user,

=== modified file 'sql-common/client.c'
--- a/sql-common/client.c	2009-03-19 13:42:36 +0000
+++ b/sql-common/client.c	2009-05-19 20:19:52 +0000
@@ -924,6 +924,7 @@ void end_server(MYSQL *mysql)
     vio_delete(mysql->net.vio);
     reset_sigpipe(mysql);
     mysql->net.vio= 0;          /* Marker */
+    mysql_prune_stmt_list(mysql);
   }
   net_end(&mysql->net);
   free_old_query(mysql);
@@ -2526,30 +2527,9 @@ my_bool mysql_reconnect(MYSQL *mysql)
   tmp_mysql.reconnect= 1;
   tmp_mysql.free_me= mysql->free_me;
 
-  /*
-    For each stmt in mysql->stmts, move it to tmp_mysql if it is
-    in state MYSQL_STMT_INIT_DONE, otherwise close it.
-  */
-  {
-    LIST *element= mysql->stmts;
-    for (; element; element= element->next)
-    {
-      MYSQL_STMT *stmt= (MYSQL_STMT *) element->data;
-      if (stmt->state != MYSQL_STMT_INIT_DONE)
-      {
-        stmt->mysql= 0;
-        stmt->last_errno= CR_SERVER_LOST;
-        strmov(stmt->last_error, ER(CR_SERVER_LOST));
-        strmov(stmt->sqlstate, unknown_sqlstate);
-      }
-      else
-      {
-        tmp_mysql.stmts= list_add(tmp_mysql.stmts, &stmt->list);
-      }
-      /* No need to call list_delete for statement here */
-    }
-    mysql->stmts= NULL;
-  }
+  /* Move prepared statements (if any) over to the new mysql object */
+  tmp_mysql.stmts= mysql->stmts;
+  mysql->stmts= 0;
 
   /* Don't free options as these are now used in tmp_mysql */
   bzero((char*) &mysql->options,sizeof(mysql->options));
@@ -2639,6 +2619,46 @@ static void mysql_close_free(MYSQL *mysq
 }
 
 
+/**
+  For use when the connection to the server has been lost (in which case 
+  the server has discarded all information about prepared statements
+  associated with the connection).
+
+  Mark all statements in mysql->stmts by setting stmt->mysql= 0 if the
+  statement has transitioned beyond the MYSQL_STMT_INIT_DONE state, and
+  unlink the statement from the mysql->stmts list.
+
+  The remaining pruned list of statements (if any) is kept in mysql->stmts.
+
+  @param mysql       pointer to the MYSQL object
+
+  @return none
+*/
+void mysql_prune_stmt_list(MYSQL *mysql)
+{
+  LIST *element= mysql->stmts;
+  LIST *pruned_list= 0;
+
+  for (; element; element= element->next)
+  {
+    MYSQL_STMT *stmt= (MYSQL_STMT *) element->data;
+    if (stmt->state != MYSQL_STMT_INIT_DONE)
+    {
+      stmt->mysql= 0;
+      stmt->last_errno= CR_SERVER_LOST;
+      strmov(stmt->last_error, ER(CR_SERVER_LOST));
+      strmov(stmt->sqlstate, unknown_sqlstate);
+    }
+    else
+    {
+      pruned_list= list_add(pruned_list, element);
+    }
+  }
+
+  mysql->stmts= pruned_list;
+}
+
+
 /*
   Clear connection pointer of every statement: this is necessary
   to give error on attempt to use a prepared statement of closed

=== modified file 'sql/sql_prepare.cc'
--- a/sql/sql_prepare.cc	2009-05-15 12:57:51 +0000
+++ b/sql/sql_prepare.cc	2009-05-19 20:19:52 +0000
@@ -2461,6 +2461,9 @@ void mysql_stmt_execute(THD *thd, char *
 
   stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end);
 
+  /* Close connection socket; for use with client testing (Bug#43560). */
+  DBUG_EXECUTE_IF("close_conn_after_stmt_execute", vio_close(thd->net.vio););
+
   DBUG_VOID_RETURN;
 
 }

=== modified file 'tests/mysql_client_test.c'
--- a/tests/mysql_client_test.c	2009-05-05 10:34:25 +0000
+++ b/tests/mysql_client_test.c	2009-05-19 20:19:52 +0000
@@ -103,7 +103,7 @@ if (!opt_silent) \
 
 static void print_error(const char *msg);
 static void print_st_error(MYSQL_STMT *stmt, const char *msg);
-static void client_disconnect(void);
+static void client_disconnect(MYSQL* mysql, my_bool drop_db);
 
 
 /*
@@ -271,10 +271,20 @@ mysql_simple_prepare(MYSQL *mysql_arg, c
 }
 
 
-/* Connect to the server */
-
-static void client_connect(ulong flag)
+/**
+   Connect to the server with options given by arguments to this application,
+   stored in global variables opt_host, opt_user, opt_password, opt_db, 
+   opt_port and opt_unix_socket.
+
+   @param flag[in]           client_flag passed on to mysql_real_connect
+   @param protocol[in]       MYSQL_PROTOCOL_* to use for this connection
+   @param auto_reconnect[in] set to 1 for auto reconnect
+   
+   @return pointer to initialized and connected MYSQL object
+*/
+static MYSQL* client_connect(ulong flag, uint protocol, my_bool auto_reconnect)
 {
+  MYSQL* mysql;
   int  rc;
   static char query[MAX_TEST_QUERY_LENGTH];
   myheader_r("client_connect");
@@ -291,6 +301,7 @@ static void client_connect(ulong flag)
   }
   /* enable local infile, in non-binary builds often disabled by default */
   mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0);
+  mysql_options(mysql, MYSQL_OPT_PROTOCOL, &protocol);
 
   if (!(mysql_real_connect(mysql, opt_host, opt_user,
                            opt_password, opt_db ? opt_db:"test", opt_port,
@@ -302,7 +313,7 @@ static void client_connect(ulong flag)
     fprintf(stdout, "\n Check the connection options using --help or -?\n");
     exit(1);
   }
-  mysql->reconnect= 1;
+  mysql->reconnect= auto_reconnect;
 
   if (!opt_silent)
     fprintf(stdout, "OK");
@@ -329,12 +340,14 @@ static void client_connect(ulong flag)
 
   if (!opt_silent)
     fprintf(stdout, "OK");
+
+  return mysql;
 }
 
 
 /* Close the connection */
 
-static void client_disconnect()
+static void client_disconnect(MYSQL* mysql, my_bool drop_db)
 {
   static char query[MAX_TEST_QUERY_LENGTH];
 
@@ -342,13 +355,16 @@ static void client_disconnect()
 
   if (mysql)
   {
-    if (!opt_silent)
-      fprintf(stdout, "\n dropping the test database '%s' ...", current_db);
-    strxmov(query, "DROP DATABASE IF EXISTS ", current_db, NullS);
+    if (drop_db)
+    {
+      if (!opt_silent)
+        fprintf(stdout, "\n dropping the test database '%s' ...", current_db);
+      strxmov(query, "DROP DATABASE IF EXISTS ", current_db, NullS);
 
-    mysql_query(mysql, query);
-    if (!opt_silent)
-      fprintf(stdout, "OK");
+      mysql_query(mysql, query);
+      if (!opt_silent)
+        fprintf(stdout, "OK");
+    }
 
     if (!opt_silent)
       fprintf(stdout, "\n closing the connection ...");
@@ -17713,6 +17729,100 @@ static void test_bug40365(void)
 
 
 /**
+  Subtest for Bug#43560. Verifies that a loss of connection on the server side
+  is handled well by the mysql_stmt_execute() call, i.e., no SIGSEGV due to
+  a vio socket that is cleared upon closed connection.
+
+  Assumes the presence of the close_conn_after_stmt_execute debug feature in
+  the server. Verifies that it is connected to a debug server before proceeding
+  with the test.
+ */
+static void test_bug43560(void)
+{
+  MYSQL*       conn;
+  uint         rc;
+  MYSQL_STMT   *stmt= 0;
+  MYSQL_BIND   bind;
+  my_bool      is_null= 0;
+  const uint   BUFSIZE= 256;
+  char         buffer[BUFSIZE];
+  const char*  values[] = {"eins", "zwei", "drei", "viele", NULL};
+  const char   insert_str[] = "INSERT INTO t1 (c2) VALUES (?)";
+  unsigned long length;
+  
+  DBUG_ENTER("test_bug43560");
+  myheader("test_bug43560");
+
+  /* Make sure we only run against a debug server. */
+  if (!strstr(mysql->server_version, "debug"))
+  {
+    fprintf(stdout, "Skipping test_bug43560: server not DEBUG version\n");
+    DBUG_VOID_RETURN;
+  }
+
+  /*
+    Set up a separate connection for this test to avoid messing up the
+    general MYSQL object used in other subtests. Use TCP protocol to avoid
+    problems with the buffer semantics of AF_UNIX, and turn off auto reconnect.
+  */
+  conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
+
+  rc= mysql_query(conn, "DROP TABLE IF EXISTS t1");
+  myquery(rc);
+  rc= mysql_query(conn,
+    "CREATE TABLE t1 (c1 INT PRIMARY KEY AUTO_INCREMENT, c2 CHAR(10))");
+  myquery(rc);
+
+  stmt= mysql_stmt_init(conn);
+  check_stmt(stmt);
+  rc= mysql_stmt_prepare(stmt, insert_str, strlen(insert_str));
+  check_execute(stmt, rc);
+
+  bind.buffer_type= MYSQL_TYPE_STRING;
+  bind.buffer_length= BUFSIZE;
+  bind.buffer= buffer;
+  bind.is_null= &is_null;
+  bind.length= &length;
+  rc= mysql_stmt_bind_param(stmt, &bind);
+  check_execute(stmt, rc);
+
+  /* First execute; should succeed. */
+  strncpy(buffer, values[0], BUFSIZE);
+  length= strlen(buffer);
+  rc= mysql_stmt_execute(stmt);
+  check_execute(stmt, rc);
+
+  /* 
+    Set up the server to close this session's server-side socket after
+    next execution of prep statement.
+  */
+  rc= mysql_query(conn,"SET SESSION debug='+d,close_conn_after_stmt_execute'");
+  myquery(rc);
+
+  /* Second execute; should fail due to socket closed during execution. */
+  strncpy(buffer, values[1], BUFSIZE);
+  length= strlen(buffer);
+  rc= mysql_stmt_execute(stmt);
+  DIE_UNLESS(rc && mysql_stmt_errno(stmt) == CR_SERVER_LOST);
+
+  /* 
+    Third execute; should fail (connection already closed), or SIGSEGV in
+    case of a Bug#43560 type regression in which case the whole test fails.
+  */
+  strncpy(buffer, values[2], BUFSIZE);
+  length= strlen(buffer);
+  rc= mysql_stmt_execute(stmt);
+  DIE_UNLESS(rc && mysql_stmt_errno(stmt) == CR_SERVER_LOST);
+
+  client_disconnect(conn, 0);
+  rc= mysql_query(mysql, "DROP TABLE t1");
+  myquery(rc);
+
+  DBUG_VOID_RETURN;
+}
+
+
+/**
   Bug#36326: nested transaction and select
 */
 
@@ -18140,6 +18250,7 @@ static struct my_tests_st my_tests[]= {
   { "test_wl4166_2", test_wl4166_2 },
   { "test_bug38486", test_bug38486 },
   { "test_bug40365", test_bug40365 },
+  { "test_bug43560", test_bug43560 },
 #ifdef HAVE_QUERY_CACHE
   { "test_bug36326", test_bug36326 },
 #endif
@@ -18268,7 +18379,8 @@ int main(int argc, char **argv)
                         (char**) embedded_server_groups))
     DIE("Can't initialize MySQL server");
 
-  client_connect(0);       /* connect to server */
+  /* connect to server with no flags, default protocol, auto reconnect true */
+  mysql= client_connect(0, MYSQL_PROTOCOL_DEFAULT, 1);
 
   total_time= 0;
   for (iter_count= 1; iter_count <= opt_count; iter_count++)
@@ -18298,7 +18410,7 @@ int main(int argc, char **argv)
 	  fprintf(stderr, "\n\nGiven test not found: '%s'\n", *argv);
 	  fprintf(stderr, "See legal test names with %s -T\n\nAborting!\n",
 		  my_progname);
-	  client_disconnect();
+	  client_disconnect(mysql, 1);
 	  free_defaults(defaults_argv);
 	  exit(1);
 	}
@@ -18311,7 +18423,7 @@ int main(int argc, char **argv)
     /* End of tests */
   }
 
-  client_disconnect();    /* disconnect from server */
+  client_disconnect(mysql, 1);    /* disconnect from server */
 
   free_defaults(defaults_argv);
   print_test_output();


Attachment: [text/bzr-bundle] bzr/staale.smedseng@sun.com-20090519201952-thq3r1nt75rmoaxy.bundle
Thread
bzr commit into mysql-5.1-bugteam branch (staale.smedseng:2888) Bug#43560Staale Smedseng19 May
  • Re: bzr commit into mysql-5.1-bugteam branch (staale.smedseng:2888)Bug#43560Davi Arnaut20 May