Commit 0abc107c authored by unknown's avatar unknown
Browse files

Bug#16581: deadlock: server and client both read from connection in

           'conc_sys' test

Concurrent execution of SELECT involing at least two INFORMATION_SCHEMA
tables, DROP DATABASE statement and DROP TABLE statement could have
resulted in stalled connection for this SELECT statement.

The problem was that for the first query of a join there was a race
between select from I_S.TABLES and DROP DATABASE, and the error (no
such database) was prepared to be send to the client, but the join
processing was continued.  On second query to I_S.COLUMNS there was a
race with DROP TABLE, but this error (no such table) was downgraded to
warning, and thd->net.report_error was reset.  And so neither result
nor error was sent to the client.

The solution is to stop join processing once it is clear we are going
to report a error, and also to downgrade to warnings file system errors
like 'no such database' (unless we are in the 'SHOW' command), because
I_S is designed not to use locks and the query to I_S should not abort
if something is dropped in the middle.

No test case is provided since this bug is a result of a race, and is
timing dependant.  But we test that plain SHOW TABLES and SHOW COLUMNS
give a error if there is no such database or a table respectively.


mysql-test/r/show_check.result:
  Add result for the test that SHOW TABLES and SHOW COLUMNS give a error
  if there is no such database or a table respectively.
mysql-test/t/show_check.test:
  Add test case that SHOW TABLES and SHOW COLUMNS give a error
  if there is no such database or a table respectively.
sql/mysql_priv.h:
  Remove prototype of mysql_find_files(), which is made static under
  find_files() name.
sql/sql_show.cc:
  Rename mysql_find_files() to find_files() and make it static.
  Return FIND_FILES_OK for success, FIND_FILES_OOM for out of memory,
  and FIND_FILES_DIR for directory reading error.
  Downgrade error to warning in get_all_tables() if it is a
  FIND_FILES_DIR error, and we are not in the 'SHOW' command.
  Once 'result' is set to 1 in get_schema_tables_result(), there's no
  need in continuing iterations, as we are about to return a error.
parent ec4a7522
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -625,3 +625,7 @@ View Create View
v1	CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select sql_cache 1 AS `1`
DROP PROCEDURE p1;
DROP VIEW v1;
SHOW TABLES FROM no_such_database;
ERROR 42000: Unknown database 'no_such_database'
SHOW COLUMNS FROM no_such_table;
ERROR 42S02: Table 'test.no_such_table' doesn't exist
+11 −0
Original line number Diff line number Diff line
@@ -495,4 +495,15 @@ SHOW CREATE VIEW v1;
DROP PROCEDURE p1;
DROP VIEW v1;


#
# Check that SHOW TABLES and SHOW COLUMNS give a error if there is no
# referenced database and table respectively.
#
--error ER_BAD_DB_ERROR
SHOW TABLES FROM no_such_database;
--error ER_NO_SUCH_TABLE
SHOW COLUMNS FROM no_such_table;


# End of 5.0 tests.
+0 −2
Original line number Diff line number Diff line
@@ -865,8 +865,6 @@ bool mysqld_show_create_db(THD *thd, char *dbname, HA_CREATE_INFO *create);
void mysqld_list_processes(THD *thd,const char *user,bool verbose);
int mysqld_show_status(THD *thd);
int mysqld_show_variables(THD *thd,const char *wild);
int mysql_find_files(THD *thd,List<char> *files, const char *db,
                const char *path, const char *wild, bool dir);
bool mysqld_show_storage_engines(THD *thd);
bool mysqld_show_privileges(THD *thd);
bool mysqld_show_column_types(THD *thd);
+66 −15
Original line number Diff line number Diff line
@@ -250,9 +250,35 @@ bool mysqld_show_column_types(THD *thd)
}


int
mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path,
                 const char *wild, bool dir)
/*
  find_files() - find files in a given directory.

  SYNOPSIS
    find_files()
    thd                 thread handler
    files               put found files in this list
    db                  database name to set in TABLE_LIST structure
    path                path to database
    wild                filter for found files
    dir                 read databases in path if TRUE, read .frm files in
                        database otherwise

  RETURN
    FIND_FILES_OK       success
    FIND_FILES_OOM      out of memory error
    FIND_FILES_DIR      no such directory, or directory can't be read
*/

enum find_files_result {
  FIND_FILES_OK,
  FIND_FILES_OOM,
  FIND_FILES_DIR
};

static
find_files_result
find_files(THD *thd, List<char> *files, const char *db,
           const char *path, const char *wild, bool dir)
{
  uint i;
  char *ext;
@@ -262,7 +288,7 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path,
  uint col_access=thd->col_access;
#endif
  TABLE_LIST table_list;
  DBUG_ENTER("mysql_find_files");
  DBUG_ENTER("find_files");

  if (wild && !wild[0])
    wild=0;
@@ -275,7 +301,7 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path,
      my_error(ER_BAD_DB_ERROR, MYF(ME_BELL+ME_WAITTANG), db);
    else
      my_error(ER_CANT_READ_DIR, MYF(ME_BELL+ME_WAITTANG), path, my_errno);
    DBUG_RETURN(-1);
    DBUG_RETURN(FIND_FILES_DIR);
  }

  for (i=0 ; i < (uint) dirp->number_off_files  ; i++)
@@ -337,7 +363,7 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path,
    if (files->push_back(thd->strdup(file->name)))
    {
      my_dirend(dirp);
      DBUG_RETURN(-1);
      DBUG_RETURN(FIND_FILES_OOM);
    }
  }
  DBUG_PRINT("info",("found: %d files", files->elements));
@@ -345,7 +371,7 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path,

  VOID(ha_find_files(thd,db,path,wild,dir,files));

  DBUG_RETURN(0);
  DBUG_RETURN(FIND_FILES_OK);
}


@@ -2000,8 +2026,8 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table)
                          wild string otherwise it's db name; 

  RETURN
    1	                  error
    0	                  success
    zero                  success
    non-zero              error
*/

int make_db_list(THD *thd, List<char> *files,
@@ -2027,8 +2053,8 @@ int make_db_list(THD *thd, List<char> *files,
      if (files->push_back(thd->strdup(information_schema_name.str)))
        return 1;
    }
    return mysql_find_files(thd, files, NullS, mysql_data_home,
                            idx_field_vals->db_value, 1);
    return (find_files(thd, files, NullS, mysql_data_home,
                       idx_field_vals->db_value, 1) != FIND_FILES_OK);
  }

  /*
@@ -2055,7 +2081,8 @@ int make_db_list(THD *thd, List<char> *files,
  if (files->push_back(thd->strdup(information_schema_name.str)))
    return 1;
  *with_i_schema= 1;
  return mysql_find_files(thd, files, NullS, mysql_data_home, NullS, 1);
  return (find_files(thd, files, NullS,
                     mysql_data_home, NullS, 1) != FIND_FILES_OK);
}


@@ -2204,9 +2231,28 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
        strxmov(path, mysql_data_home, "/", base_name, NullS);
        end= path + (len= unpack_dirname(path,path));
        len= FN_LEN - len;
        if (mysql_find_files(thd, &files, base_name, 
                             path, idx_field_vals.table_value, 0))
        find_files_result res= find_files(thd, &files, base_name, 
                                          path, idx_field_vals.table_value, 0);
        if (res != FIND_FILES_OK)
        {
          /*
            Downgrade errors about problems with database directory to
            warnings if this is not a 'SHOW' command.  Another thread
            may have dropped database, and we may still have a name
            for that directory.
          */
          if (res == FIND_FILES_DIR && lex->orig_sql_command == SQLCOM_END)
          {
            push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
                         thd->net.last_errno, thd->net.last_error);
            thd->clear_error();
            continue;
          }
          else
          {
            goto err;
          }
        }
        if (lower_case_table_names)
          orig_base_name= thd->strdup(base_name);
      }
@@ -3969,7 +4015,12 @@ bool get_schema_tables_result(JOIN *join)

      if (table_list->schema_table->fill_table(thd, table_list,
                                               tab->select_cond))
      {
        result= 1;
        join->error= 1;
        table_list->is_schema_table_processed= TRUE;
        break;
      }
      table_list->is_schema_table_processed= TRUE;
    }
  }