1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

Bug#30882 Dropping a temporary table inside a stored function may cause a server crash

If a stored function that contains a drop temporary table statement
is invoked by a create temporary table of the same name may cause
a server crash. The problem is that when dropping a table no check
is done to ensure that table is not being used by some outer query
(or outer statement), potentially leaving the outer query with a
reference to a stale (freed) table.

The solution is when dropping a temporary table, always check if
the table is being used by some outer statement as a temporary
table can be dropped inside stored procedures.

The check is performed by looking at the TABLE::query_id value for
temporary tables. To simplify this check and to solve a bug related
to handling of temporary tables in prelocked mode, this patch changes
the way in which this member is used to track the fact that table is
used/unused. Now we ensure that TABLE::query_id is zero for unused
temporary tables (which means that all temporary tables which were
used by a statement should be marked as free for reuse after it's
execution has been completed).
This commit is contained in:
davi@endora.local
2007-11-01 18:52:56 -02:00
parent 6bd9f5c1cb
commit cc007acb78
15 changed files with 431 additions and 83 deletions

View File

@ -1055,6 +1055,29 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh,
}
/**
Mark all temporary tables which were used by the current statement or
substatement as free for reuse, but only if the query_id can be cleared.
@param thd thread context
@remark For temp tables associated with a open SQL HANDLER the query_id
is not reset until the HANDLER is closed.
*/
static void mark_temp_tables_as_free_for_reuse(THD *thd)
{
for (TABLE *table= thd->temporary_tables ; table ; table= table->next)
{
if ((table->query_id == thd->query_id) && ! table->open_by_handler)
{
table->query_id= 0;
table->file->ha_reset();
}
}
}
/*
Mark all tables in the list which were used by current substatement
as free for reuse.
@ -1091,6 +1114,42 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
}
/**
Auxiliary function to close all tables in the open_tables list.
@param thd Thread context.
@remark It should not ordinarily be called directly.
*/
static void close_open_tables(THD *thd)
{
bool found_old_table= 0;
safe_mutex_assert_not_owner(&LOCK_open);
VOID(pthread_mutex_lock(&LOCK_open));
DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables));
while (thd->open_tables)
found_old_table|= close_thread_table(thd, &thd->open_tables);
thd->some_tables_deleted= 0;
/* Free tables to hold down open files */
while (open_cache.records > table_cache_size && unused_tables)
VOID(hash_delete(&open_cache,(uchar*) unused_tables)); /* purecov: tested */
check_unused();
if (found_old_table)
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
VOID(pthread_mutex_unlock(&LOCK_open));
}
/*
Close all tables used by the current substatement, or all tables
used by this thread if we are on the upper level.
@ -1098,26 +1157,19 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
SYNOPSIS
close_thread_tables()
thd Thread handler
lock_in_use Set to 1 (0 = default) if caller has a lock on
LOCK_open
skip_derived Set to 1 (0 = default) if we should not free derived
tables.
stopper When closing tables from thd->open_tables(->next)*,
don't close/remove tables starting from stopper.
IMPLEMENTATION
Unlocks tables and frees derived tables.
Put all normal tables used by thread in free list.
When in prelocked mode it will only close/mark as free for reuse
tables opened by this substatement, it will also check if we are
closing tables after execution of complete query (i.e. we are on
upper level) and will leave prelocked mode if needed.
It will only close/mark as free for reuse tables opened by this
substatement, it will also check if we are closing tables after
execution of complete query (i.e. we are on upper level) and will
leave prelocked mode if needed.
*/
void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived)
void close_thread_tables(THD *thd)
{
bool found_old_table;
prelocked_mode_type prelocked_mode= thd->prelocked_mode;
DBUG_ENTER("close_thread_tables");
@ -1132,7 +1184,7 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived)
derived tables with (sub-)statement instead of thread and destroy
them at the end of its execution.
*/
if (thd->derived_tables && !skip_derived)
if (thd->derived_tables)
{
TABLE *table, *next;
/*
@ -1147,13 +1199,10 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived)
thd->derived_tables= 0;
}
if (prelocked_mode)
{
/*
Mark all temporary tables used by this substatement as free for reuse.
*/
mark_used_tables_as_free_for_reuse(thd, thd->temporary_tables);
}
/*
Mark all temporary tables used by this statement as free for reuse.
*/
mark_temp_tables_as_free_for_reuse(thd);
if (thd->locked_tables || prelocked_mode)
{
@ -1217,28 +1266,8 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived)
if (!thd->active_transaction())
thd->transaction.xid_state.xid.null();
if (!lock_in_use)
VOID(pthread_mutex_lock(&LOCK_open));
DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables));
found_old_table= 0;
while (thd->open_tables)
found_old_table|= close_thread_table(thd, &thd->open_tables);
thd->some_tables_deleted=0;
/* Free tables to hold down open files */
while (open_cache.records > table_cache_size && unused_tables)
VOID(hash_delete(&open_cache,(uchar*) unused_tables)); /* purecov: tested */
check_unused();
if (found_old_table)
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
if (!lock_in_use)
VOID(pthread_mutex_unlock(&LOCK_open));
/* VOID(pthread_sigmask(SIG_SETMASK,&thd->signals,NULL)); */
if (thd->open_tables)
close_open_tables(thd);
if (prelocked_mode == PRELOCKED)
{
@ -1675,6 +1704,7 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list)
Try to locate the table in the list of thd->temporary_tables.
If the table is found:
- if the table is being used by some outer statement, fail.
- if the table is in thd->locked_tables, unlock it and
remove it from the list of locked tables. Currently only transactional
temporary tables are present in the locked_tables list.
@ -1689,24 +1719,34 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list)
thd->temporary_tables list, it's impossible to tell here whether
we're dealing with an internal or a user temporary table.
@retval TRUE the table was not found in the list of temporary tables
of this thread
@retval FALSE the table was found and dropped successfully.
@retval 0 the table was found and dropped successfully.
@retval 1 the table was not found in the list of temporary tables
of this thread
@retval -1 the table is in use by a outer query
*/
bool close_temporary_table(THD *thd, TABLE_LIST *table_list)
int drop_temporary_table(THD *thd, TABLE_LIST *table_list)
{
TABLE *table;
DBUG_ENTER("drop_temporary_table");
if (!(table= find_temporary_table(thd, table_list)))
return 1;
DBUG_RETURN(1);
/* Table might be in use by some outer statement. */
if (table->query_id && table->query_id != thd->query_id)
{
my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
DBUG_RETURN(-1);
}
/*
If LOCK TABLES list is not empty and contains this table,
unlock the table and remove the table from this list.
*/
mysql_lock_remove(thd, thd->locked_tables, table, FALSE);
close_temporary_table(thd, table, 1, 1);
return 0;
DBUG_RETURN(0);
}
/*
@ -2285,8 +2325,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
is always represented by only one TABLE object in THD, and
it can not be cloned. Emit an error for an unsupported behaviour.
*/
if (table->query_id == thd->query_id ||
thd->prelocked_mode && table->query_id)
if (table->query_id)
{
DBUG_PRINT("error",
("query_id: %lu server_id: %u pseudo_thread_id: %lu",
@ -2296,7 +2335,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
DBUG_RETURN(0);
}
table->query_id= thd->query_id;
table->clear_query_id= 1;
thd->thread_specific_used= TRUE;
DBUG_PRINT("info",("Using temporary table"));
goto reset;
@ -4306,7 +4344,6 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
sp_remove_not_own_routines(thd->lex);
for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global)
tmp->table= 0;
mark_used_tables_as_free_for_reuse(thd, thd->temporary_tables);
close_thread_tables(thd);
}