Commit bafe2403 authored by Jon Olav Hauglid's avatar Jon Olav Hauglid

Bug #11764779 (former 57649)

FLUSH TABLES under FLUSH TABLES <list> WITH READ LOCK leads 
to assert failure.

This assert was triggered if a statement tried up upgrade a metadata
lock with an active FLUSH TABLE <list> WITH READ LOCK. The assert 
checks that the connection already holds a global intention exclusive
metadata lock. However, FLUSH TABLE <list> WITH READ LOCK does not
acquire this lock in order to be compatible with FLUSH TABLES WITH
READ LOCK. Therefore any metadata lock upgrade caused the assert to
be triggered.

This patch fixes the problem by preventing metadata lock upgrade
if the connection has an active FLUSH TABLE <list> WITH READ LOCK.
ER_TABLE_NOT_LOCKED_FOR_WRITE will instead be reported to the client.

Test case added to flush.test.
parent b326b9a3
...@@ -451,3 +451,18 @@ unlock tables; ...@@ -451,3 +451,18 @@ unlock tables;
handler t1 close; handler t1 close;
# Cleanup. # Cleanup.
drop tables t1, t2; drop tables t1, t2;
#
# Bug#57649 FLUSH TABLES under FLUSH TABLES <list> WITH READ LOCK leads
# to assert failure.
#
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (a INT);
FLUSH TABLES t1 WITH READ LOCK;
FLUSH TABLES;
ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET @a= 1;
ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
ALTER TABLE t1 COMMENT 'test';
ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
UNLOCK TABLES;
DROP TABLE t1;
...@@ -644,3 +644,27 @@ disconnect con2; ...@@ -644,3 +644,27 @@ disconnect con2;
--source include/wait_until_disconnected.inc --source include/wait_until_disconnected.inc
connection default; connection default;
drop tables t1, t2; drop tables t1, t2;
--echo #
--echo # Bug#57649 FLUSH TABLES under FLUSH TABLES <list> WITH READ LOCK leads
--echo # to assert failure.
--echo #
--disable_warnings
DROP TABLE IF EXISTS t1;
--enable_warnings
CREATE TABLE t1 (a INT);
FLUSH TABLES t1 WITH READ LOCK;
# All these triggered the assertion
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
FLUSH TABLES;
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET @a= 1;
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
ALTER TABLE t1 COMMENT 'test';
UNLOCK TABLES;
DROP TABLE t1;
...@@ -1026,7 +1026,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, ...@@ -1026,7 +1026,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
table_list= table_list->next_global) table_list= table_list->next_global)
{ {
/* A check that the table was locked for write is done by the caller. */ /* A check that the table was locked for write is done by the caller. */
TABLE *table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db, TABLE *table= find_table_for_mdl_upgrade(thd, table_list->db,
table_list->table_name, TRUE); table_list->table_name, TRUE);
/* May return NULL if this table has already been closed via an alias. */ /* May return NULL if this table has already been closed via an alias. */
...@@ -3120,22 +3120,26 @@ TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name) ...@@ -3120,22 +3120,26 @@ TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
lock from the list of open tables, emit error if no such table lock from the list of open tables, emit error if no such table
found. found.
@param list List of TABLE objects to be searched @param thd Thread context
@param db Database name. @param db Database name.
@param table_name Name of table. @param table_name Name of table.
@param no_error Don't emit error if no suitable TABLE @param no_error Don't emit error if no suitable TABLE
instance were found. instance were found.
@note This function checks if the connection holds a global IX
metadata lock. If no such lock is found, it is not safe to
upgrade the lock and ER_TABLE_NOT_LOCKED_FOR_WRITE will be
reported.
@return Pointer to TABLE instance with MDL_SHARED_NO_WRITE, @return Pointer to TABLE instance with MDL_SHARED_NO_WRITE,
MDL_SHARED_NO_READ_WRITE, or MDL_EXCLUSIVE metadata MDL_SHARED_NO_READ_WRITE, or MDL_EXCLUSIVE metadata
lock, NULL otherwise. lock, NULL otherwise.
*/ */
TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db,
const char *table_name, const char *table_name, bool no_error)
bool no_error)
{ {
TABLE *tab= find_locked_table(list, db, table_name); TABLE *tab= find_locked_table(thd->open_tables, db, table_name);
if (!tab) if (!tab)
{ {
...@@ -3143,19 +3147,29 @@ TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, ...@@ -3143,19 +3147,29 @@ TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name); my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
return NULL; return NULL;
} }
else
/*
It is not safe to upgrade the metadata lock without a global IX lock.
This can happen with FLUSH TABLES <list> WITH READ LOCK as we in these
cases don't take a global IX lock in order to be compatible with
global read lock.
*/
if (!thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE))
{ {
while (tab->mdl_ticket != NULL && if (!no_error)
!tab->mdl_ticket->is_upgradable_or_exclusive() && my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
(tab= find_locked_table(tab->next, db, table_name))) return NULL;
continue;
if (!tab)
{
if (!no_error)
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
return 0;
}
} }
while (tab->mdl_ticket != NULL &&
!tab->mdl_ticket->is_upgradable_or_exclusive() &&
(tab= find_locked_table(tab->next, db, table_name)))
continue;
if (!tab && !no_error)
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
return tab; return tab;
} }
...@@ -4653,8 +4667,7 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, ...@@ -4653,8 +4667,7 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
Note that find_table_for_mdl_upgrade() will report an error if Note that find_table_for_mdl_upgrade() will report an error if
no suitable ticket is found. no suitable ticket is found.
*/ */
if (!find_table_for_mdl_upgrade(thd->open_tables, table->db, if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, false))
table->table_name, FALSE))
return TRUE; return TRUE;
} }
} }
......
/* Copyright 2006-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. /* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -290,7 +290,7 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, ...@@ -290,7 +290,7 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
char *cache_key, uint cache_key_length, char *cache_key, uint cache_key_length,
MEM_ROOT *mem_root, uint flags); MEM_ROOT *mem_root, uint flags);
void tdc_flush_unused_tables(); void tdc_flush_unused_tables();
TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db,
const char *table_name, const char *table_name,
bool no_error); bool no_error);
void mark_tmp_table_for_reuse(TABLE *table); void mark_tmp_table_for_reuse(TABLE *table);
......
/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. /* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -220,12 +220,26 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, ...@@ -220,12 +220,26 @@ bool reload_acl_and_cache(THD *thd, unsigned long options,
if (tables) if (tables)
{ {
for (TABLE_LIST *t= tables; t; t= t->next_local) for (TABLE_LIST *t= tables; t; t= t->next_local)
if (!find_table_for_mdl_upgrade(thd->open_tables, t->db, if (!find_table_for_mdl_upgrade(thd, t->db, t->table_name, false))
t->table_name, FALSE))
return 1; return 1;
} }
else else
{ {
/*
It is not safe to upgrade the metadata lock without GLOBAL IX lock.
This can happen with FLUSH TABLES <list> WITH READ LOCK as we in these
cases don't take a GLOBAL IX lock in order to be compatible with
global read lock.
*/
if (thd->open_tables &&
!thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE))
{
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
thd->open_tables->s->table_name.str);
return true;
}
for (TABLE *tab= thd->open_tables; tab; tab= tab->next) for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
{ {
if (! tab->mdl_ticket->is_upgradable_or_exclusive()) if (! tab->mdl_ticket->is_upgradable_or_exclusive())
......
...@@ -1917,7 +1917,7 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, ...@@ -1917,7 +1917,7 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
by parser) it is safe to cache pointer to the TABLE instances by parser) it is safe to cache pointer to the TABLE instances
in its elements. in its elements.
*/ */
table->table= find_table_for_mdl_upgrade(thd->open_tables, table->db, table->table= find_table_for_mdl_upgrade(thd, table->db,
table->table_name, false); table->table_name, false);
if (!table->table) if (!table->table)
DBUG_RETURN(true); DBUG_RETURN(true);
......
/* Copyright (C) 2004-2005 MySQL AB, 2008-2009 Sun Microsystems, Inc /* Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -467,8 +467,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) ...@@ -467,8 +467,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
if (thd->locked_tables_mode) if (thd->locked_tables_mode)
{ {
/* Under LOCK TABLES we must only accept write locked tables. */ /* Under LOCK TABLES we must only accept write locked tables. */
if (!(tables->table= find_table_for_mdl_upgrade(thd->open_tables, if (!(tables->table= find_table_for_mdl_upgrade(thd, tables->db,
tables->db,
tables->table_name, tables->table_name,
FALSE))) FALSE)))
goto end; goto end;
......
/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. /* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -327,7 +327,7 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref, ...@@ -327,7 +327,7 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref,
*/ */
if (thd->locked_tables_mode) if (thd->locked_tables_mode)
{ {
if (!(table= find_table_for_mdl_upgrade(thd->open_tables, table_ref->db, if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db,
table_ref->table_name, FALSE))) table_ref->table_name, FALSE)))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment