mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
Fixed deadlock with LOCK TABLES and ALTER TABLE
MDEV-21398 Deadlock (server hang) or assertion failure in Diagnostics_area::set_error_status upon ALTER under lock This failure could only happen if one locked the same table multiple times and then did an ALTER TABLE on the table. Major change is to change all instances of table->m_needs_reopen= true; to table->mark_table_for_reopen(); The main fix for the problem was to ensure that we mark all instances of the table in the locked_table_list and when we reopen the tables, we first close all tables before reopening and locking them. Other things: - Don't call thd->locked_tables_list.reopen_tables if there are no tables marked for reopen. (performance)
This commit is contained in:
@ -2161,9 +2161,9 @@ Locked_tables_list::init_locked_tables(THD *thd)
|
||||
in reopen_tables(). reopen_tables() is a critical
|
||||
path and we don't want to complicate it with extra allocations.
|
||||
*/
|
||||
m_reopen_array= (TABLE**)alloc_root(&m_locked_tables_root,
|
||||
sizeof(TABLE*) *
|
||||
(m_locked_tables_count+1));
|
||||
m_reopen_array= (TABLE_LIST**)alloc_root(&m_locked_tables_root,
|
||||
sizeof(TABLE_LIST*) *
|
||||
(m_locked_tables_count+1));
|
||||
if (m_reopen_array == NULL)
|
||||
{
|
||||
reset();
|
||||
@ -2273,6 +2273,7 @@ void Locked_tables_list::reset()
|
||||
m_locked_tables_last= &m_locked_tables;
|
||||
m_reopen_array= NULL;
|
||||
m_locked_tables_count= 0;
|
||||
some_table_marked_for_reopen= 0;
|
||||
}
|
||||
|
||||
|
||||
@ -2368,7 +2369,7 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
|
||||
in reopen_tables() always links the opened table
|
||||
to the beginning of the open_tables list.
|
||||
*/
|
||||
DBUG_ASSERT(thd->open_tables == m_reopen_array[reopen_count]);
|
||||
DBUG_ASSERT(thd->open_tables == m_reopen_array[reopen_count]->table);
|
||||
|
||||
thd->open_tables->pos_in_locked_tables->table= NULL;
|
||||
thd->open_tables->pos_in_locked_tables= 0;
|
||||
@ -2398,10 +2399,36 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Mark all instances of the table to be reopened
|
||||
|
||||
This is only needed when LOCK TABLES is active
|
||||
*/
|
||||
|
||||
void Locked_tables_list::mark_table_for_reopen(THD *thd, TABLE *table)
|
||||
{
|
||||
TABLE_SHARE *share= table->s;
|
||||
|
||||
for (TABLE_LIST *table_list= m_locked_tables;
|
||||
table_list; table_list= table_list->next_global)
|
||||
{
|
||||
if (table_list->table->s == share)
|
||||
table_list->table->internal_set_needs_reopen(true);
|
||||
}
|
||||
/* This is needed in the case where lock tables where not used */
|
||||
table->internal_set_needs_reopen(true);
|
||||
some_table_marked_for_reopen= 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Reopen the tables locked with LOCK TABLES and temporarily closed
|
||||
by a DDL statement or FLUSH TABLES.
|
||||
|
||||
@param need_reopen If set, reopen open tables that are marked with
|
||||
for reopen.
|
||||
If not set, reopen tables that where closed.
|
||||
|
||||
@note This function is a no-op if we're not under LOCK TABLES.
|
||||
|
||||
@return TRUE if an error reopening the tables. May happen in
|
||||
@ -2419,6 +2446,12 @@ Locked_tables_list::reopen_tables(THD *thd, bool need_reopen)
|
||||
MYSQL_LOCK *merged_lock;
|
||||
DBUG_ENTER("Locked_tables_list::reopen_tables");
|
||||
|
||||
DBUG_ASSERT(some_table_marked_for_reopen || !need_reopen);
|
||||
|
||||
|
||||
/* Reset flag that some table was marked for reopen */
|
||||
some_table_marked_for_reopen= 0;
|
||||
|
||||
for (TABLE_LIST *table_list= m_locked_tables;
|
||||
table_list; table_list= table_list->next_global)
|
||||
{
|
||||
@ -2442,24 +2475,32 @@ Locked_tables_list::reopen_tables(THD *thd, bool need_reopen)
|
||||
else
|
||||
{
|
||||
if (table_list->table) /* The table was not closed */
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Links into thd->open_tables upon success */
|
||||
if (open_table(thd, table_list, &ot_ctx))
|
||||
{
|
||||
unlink_all_closed_tables(thd, 0, reopen_count);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
table_list->table->pos_in_locked_tables= table_list;
|
||||
/* See also the comment on lock type in init_locked_tables(). */
|
||||
table_list->table->reginfo.lock_type= table_list->lock_type;
|
||||
|
||||
DBUG_ASSERT(reopen_count < m_locked_tables_count);
|
||||
m_reopen_array[reopen_count++]= table_list->table;
|
||||
m_reopen_array[reopen_count++]= table_list;
|
||||
}
|
||||
if (reopen_count)
|
||||
{
|
||||
TABLE **tables= (TABLE**) my_alloca(reopen_count * sizeof(TABLE*));
|
||||
|
||||
for (uint i= 0 ; i < reopen_count ; i++)
|
||||
{
|
||||
TABLE_LIST *table_list= m_reopen_array[i];
|
||||
/* Links into thd->open_tables upon success */
|
||||
if (open_table(thd, table_list, &ot_ctx))
|
||||
{
|
||||
unlink_all_closed_tables(thd, 0, i);
|
||||
my_afree((void*) tables);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
tables[i]= table_list->table;
|
||||
table_list->table->pos_in_locked_tables= table_list;
|
||||
/* See also the comment on lock type in init_locked_tables(). */
|
||||
table_list->table->reginfo.lock_type= table_list->lock_type;
|
||||
}
|
||||
|
||||
thd->in_lock_tables= 1;
|
||||
/*
|
||||
We re-lock all tables with mysql_lock_tables() at once rather
|
||||
@ -2472,7 +2513,7 @@ Locked_tables_list::reopen_tables(THD *thd, bool need_reopen)
|
||||
works fine. Patching legacy code of thr_lock.c is risking to
|
||||
break something else.
|
||||
*/
|
||||
lock= mysql_lock_tables(thd, m_reopen_array, reopen_count,
|
||||
lock= mysql_lock_tables(thd, tables, reopen_count,
|
||||
MYSQL_OPEN_REOPEN);
|
||||
thd->in_lock_tables= 0;
|
||||
if (lock == NULL || (merged_lock=
|
||||
@ -2481,9 +2522,11 @@ Locked_tables_list::reopen_tables(THD *thd, bool need_reopen)
|
||||
unlink_all_closed_tables(thd, lock, reopen_count);
|
||||
if (! thd->killed)
|
||||
my_error(ER_LOCK_DEADLOCK, MYF(0));
|
||||
my_afree((void*) tables);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
thd->lock= merged_lock;
|
||||
my_afree((void*) tables);
|
||||
}
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
Reference in New Issue
Block a user