#At file:///Users/shulga/projects/mysql/mysql-5.1-bug11756013/ based on revid:davi.arnaut@stripped
3634 Dmitry Shulga 2011-05-31
Fixed Bug#11756013 (formerly known as bug#47870): BOGUS "THE TABLE MYSQL.PROC IS MISSING,..."
There is a race condition during concurrent processing of CALL statement to some procedure
specified by full qualified name SCHEMA_NAME.PROC_NAME and processing of DROP SCHEMA_NAME.
The problem was that there is a window for race condition when one server thread try to load
a stored procedure/function being executed and other thread try to drop schema that is part of
fully qualified name of procedure/function being executed.
This condition race window exists in implementation of function mysql_change_db() called
by db_load_routine() during loading of stored procedure/function to cache.
Function mysql_change_db() calls to check_db_dir_existence() that might failed
because specified database was dropped during concurrent execution of DROP SCHEMA statememt.
db_load_routine() calls mysql_change_db() with flag 'force_switch' set to true value so when
referenced db is not found then my_error() is not called and function mysql_change_db() returns ok.
This shadows information about schema opening error in db_load_routine(). Then db_load_routine()
makes attempt to parse stored procedure/function that is failed. This makes to return error to
sp_cache_routines_and_add_tables_aux() but since during error generation a call to my_error wasn't
made and hence THD::main_da wasn't set we set the generic "mysql.proc table corrupt" error when running
sp_cache_routines_and_add_tables_aux().
The solution is to install error hadler inside db_load_routine() before call to mysql_opt_change_db()
and deinstall after this functuon executed. If error handler called as a result of db check failure
it remembered this event and later inside db_load_routine() we can check value of this event.
If this event is signaled about droped schema then we call to my_error to set error in diagnostic area to
error ER_BAD_DB_ERROR.
@ sql/sql_db.cc
Added synchronization point "before_db_dir_check" to emulate a race condition during
processing of CALL/DROP SCHEMA.
modified:
mysql-test/t/sp_sync.test
sql/sp.cc
sql/sql_db.cc
=== modified file 'mysql-test/t/sp_sync.test'
--- a/mysql-test/t/sp_sync.test 2010-01-15 08:51:39 +0000
+++ b/mysql-test/t/sp_sync.test 2011-05-31 13:43:02 +0000
@@ -54,5 +54,25 @@ connection default;
DROP TABLE t1, t2;
DROP PROCEDURE p1;
+--echo #
+--echo # test for bug#
+--echo #
+
+CREATE SCHEMA s1;
+CREATE PROCEDURE s1.p1() BEGIN END;
+
+connect (con2, localhost, root);
+SET DEBUG_SYNC='before_db_dir_check SIGNAL check_db WAIT_FOR dropped_schema';
+--send CALL s1.p1
+
+connection default;
+SET DEBUG_SYNC='now WAIT_FOR check_db';
+DROP SCHEMA s1;
+SET DEBUG_SYNC='now SIGNAL dropped_schema';
+
+connection con2;
+--error ER_BAD_DB_ERROR
+--reap
+
SET DEBUG_SYNC = 'RESET';
=== modified file 'sql/sp.cc'
--- a/sql/sp.cc 2010-06-11 12:52:06 +0000
+++ b/sql/sp.cc 2011-05-31 13:43:02 +0000
@@ -707,6 +707,35 @@ Silence_deprecated_warning::handle_error
return FALSE;
}
+struct Catch_db_not_exists_error : public Internal_error_handler
+{
+public:
+ Catch_db_not_exists_error(bool* db_not_exist)
+ : schema_not_exist(db_not_exist)
+ {}
+ virtual bool handle_error(uint sql_errno, const char *message,
+ MYSQL_ERROR::enum_warning_level level,
+ THD *thd);
+private:
+ bool* schema_not_exist;
+};
+
+bool
+Catch_db_not_exists_error::handle_error(uint sql_errno, const char *message,
+ MYSQL_ERROR::enum_warning_level level,
+ THD *thd)
+{
+ *schema_not_exist= false;
+
+ if (sql_errno == ER_BAD_DB_ERROR &&
+ level == MYSQL_ERROR::WARN_LEVEL_NOTE)
+ {
+ *schema_not_exist= true;
+ return true;
+ }
+ return false;
+}
+
static int
db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
@@ -720,12 +749,12 @@ db_load_routine(THD *thd, int type, sp_n
char saved_cur_db_name_buf[NAME_LEN+1];
LEX_STRING saved_cur_db_name=
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
- bool cur_db_changed;
+ bool cur_db_changed, db_not_exists;
ulong old_sql_mode= thd->variables.sql_mode;
ha_rows old_select_limit= thd->variables.select_limit;
sp_rcontext *old_spcont= thd->spcont;
Silence_deprecated_warning warning_handler;
-
+ Catch_db_not_exists_error db_not_exists_handler(&db_not_exists);
char definer_user_name_holder[USERNAME_LENGTH + 1];
LEX_STRING definer_user_name= { definer_user_name_holder,
USERNAME_LENGTH };
@@ -766,6 +795,7 @@ db_load_routine(THD *thd, int type, sp_n
goto end;
}
+ thd->push_internal_handler(&db_not_exists_handler);
/*
Change the current database (if needed).
@@ -776,9 +806,17 @@ db_load_routine(THD *thd, int type, sp_n
&cur_db_changed))
{
ret= SP_INTERNAL_ERROR;
+ thd->pop_internal_handler();
goto end;
}
+ thd->pop_internal_handler();
+ if (db_not_exists)
+ {
+ ret= SP_INTERNAL_ERROR;
+ my_error(ER_BAD_DB_ERROR, MYF(0), name->m_db.str);
+ goto end;
+ }
thd->spcont= NULL;
{
=== modified file 'sql/sql_db.cc'
--- a/sql/sql_db.cc 2010-12-10 07:48:50 +0000
+++ b/sql/sql_db.cc 2011-05-31 13:43:02 +0000
@@ -26,6 +26,7 @@
#ifdef __WIN__
#include <direct.h>
#endif
+#include "debug_sync.h"
#define MAX_DROP_TABLE_Q_LEN 1024
@@ -1702,6 +1703,8 @@ bool mysql_change_db(THD *thd, const LEX
}
#endif
+ DEBUG_SYNC(thd, "before_db_dir_check");
+
if (check_db_dir_existence(new_db_file_name.str))
{
if (force_switch)
Attachment: [text/bzr-bundle] bzr/dmitry.shulga@oracle.com-20110531134302-ct94ykjvm02d6m4p.bundle
| Thread |
|---|
| • bzr commit into mysql-5.1 branch (Dmitry.Shulga:3634) Bug#47870 Bug#11756013 | Dmitry Shulga | 31 May |