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

Backport of revno: 2617.68.39

Bug #47249 assert in MDL_global_lock::is_lock_type_compatible

This assert could be triggered if LOCK TABLES were used to lock
both a table and a view that used the same table. The table would have
to be first WRITE locked and then READ locked. So "LOCK TABLES v1
WRITE, t1 READ" would eventually trigger the assert, "LOCK TABLES
v1 READ, t1 WRITE" would not. The reason is that the ordering of locks
in the interal representation made a difference when executing 
FLUSH TABLE on the table.

During FLUSH TABLE, a lock was upgraded to exclusive. If this lock
was of type MDL_SHARED and not MDL_SHARED_UPGRADABLE, an internal
counter in the MDL subsystem would get out of sync. This would happen
if the *last* mention of the table in LOCK TABLES was a READ lock.

The counter in question is the number exclusive locks (active or intention). 
This is used to make sure a global metadata lock is only taken when the 
counter is zero (= no conflicts). The counter is increased when a
MDL_EXCLUSIVE or MDL_SHARED_UPGRADABLE lock is taken, but not when 
upgrade_shared_lock_to_exclusive() is used to upgrade directly
from MDL_SHARED to MDL_EXCLUSIVE. 

This patch fixes the problem by searching for a TABLE instance locked
with MDL_SHARED_UPGRADABLE or MDL_EXCLUSIVE before calling
upgrade_shared_lock_to_exclusive(). The patch also adds an assert checking
that only MDL_SHARED_UPGRADABLE locks are upgraded to exclusive.

Test case added to lock_multi.test.
parent 59f82702
......@@ -219,3 +219,34 @@ flush tables with read lock;;
connection: default
flush tables;
drop table t1;
#
# Bug#47249 assert in MDL_global_lock::is_lock_type_compatible
#
DROP TABLE IF EXISTS t1;
DROP VIEW IF EXISTS v1;
#
# Test 1: LOCK TABLES v1 WRITE, t1 READ;
#
CREATE TABLE t1 ( f1 integer );
CREATE VIEW v1 AS SELECT f1 FROM t1 ;
# Connection 2
LOCK TABLES v1 WRITE, t1 READ;
FLUSH TABLE t1;
# Connection 1
LOCK TABLES t1 WRITE;
FLUSH TABLE t1;
DROP TABLE t1;
DROP VIEW v1;
#
# Test 2: LOCK TABLES t1 WRITE, v1 READ;
#
CREATE TABLE t1 ( f1 integer );
CREATE VIEW v1 AS SELECT f1 FROM t1 ;
# Connection 2
LOCK TABLES t1 WRITE, v1 READ;
FLUSH TABLE t1;
# Connection 1
LOCK TABLES t1 WRITE;
FLUSH TABLE t1;
DROP TABLE t1;
DROP VIEW v1;
......@@ -664,5 +664,63 @@ connection flush;
--reap
connection default;
disconnect flush;
--echo #
--echo # Bug#47249 assert in MDL_global_lock::is_lock_type_compatible
--echo #
--disable_warnings
DROP TABLE IF EXISTS t1;
DROP VIEW IF EXISTS v1;
--enable_warnings
--echo #
--echo # Test 1: LOCK TABLES v1 WRITE, t1 READ;
--echo #
CREATE TABLE t1 ( f1 integer );
CREATE VIEW v1 AS SELECT f1 FROM t1 ;
--echo # Connection 2
connect (con2,localhost,root);
LOCK TABLES v1 WRITE, t1 READ;
FLUSH TABLE t1;
disconnect con2;
--source include/wait_until_disconnected.inc
--echo # Connection 1
connection default;
LOCK TABLES t1 WRITE;
FLUSH TABLE t1; # Assertion happened here
# Cleanup
DROP TABLE t1;
DROP VIEW v1;
--echo #
--echo # Test 2: LOCK TABLES t1 WRITE, v1 READ;
--echo #
CREATE TABLE t1 ( f1 integer );
CREATE VIEW v1 AS SELECT f1 FROM t1 ;
--echo # Connection 2
connect (con2,localhost,root);
LOCK TABLES t1 WRITE, v1 READ;
FLUSH TABLE t1;
disconnect con2;
--source include/wait_until_disconnected.inc
--echo # Connection 1
connection default;
LOCK TABLES t1 WRITE;
FLUSH TABLE t1; # Assertion happened here
# Cleanup
DROP TABLE t1;
DROP VIEW v1;
# Wait till all disconnects are completed
--source include/wait_until_count_sessions.inc
......@@ -1005,6 +1005,9 @@ MDL_ticket::upgrade_shared_lock_to_exclusive()
if (m_type == MDL_EXCLUSIVE)
DBUG_RETURN(FALSE);
/* Only allow upgrades from MDL_SHARED_UPGRADABLE */
DBUG_ASSERT(m_type == MDL_SHARED_UPGRADABLE);
pthread_mutex_lock(&LOCK_mdl);
old_msg= MDL_ENTER_COND(thd, mysys_var);
......
......@@ -254,6 +254,10 @@ class MDL_ticket
mdl_cached_object_release_hook release_hook);
const MDL_context *get_ctx() const { return m_ctx; }
bool is_shared() const { return m_type < MDL_EXCLUSIVE; }
bool is_upgradable_or_exclusive() const
{
return m_type == MDL_SHARED_UPGRADABLE || m_type == MDL_EXCLUSIVE;
}
bool upgrade_shared_lock_to_exclusive();
void downgrade_exclusive_lock();
private:
......
......@@ -130,6 +130,8 @@ static bool tdc_wait_for_old_versions(THD *thd,
static bool
has_write_table_with_auto_increment(TABLE_LIST *tables);
TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
const char *table_name);
uint cached_open_tables(void)
{
......@@ -999,8 +1001,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
table_list= table_list->next_global)
{
/* A check that the table was locked for write is done by the caller. */
TABLE *table= find_locked_table(thd->open_tables, table_list->db,
table_list->table_name);
TABLE *table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db,
table_list->table_name);
/* May return NULL if this table has already been closed via an alias. */
if (! table)
......@@ -2942,6 +2944,34 @@ TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_na
}
/**
Find instance of TABLE with MDL_SHARED_UPGRADABLE or
MDL_EXCLUSIVE lock from the list of open tables.
@param list List of TABLE objects to be searched
@param db Database name.
@param table_name Name of table.
@return Pointer to MDL_SHARED_UPGRADABLE or MDL_EXCLUSIVE
TABLE instance, NULL otherwise.
*/
TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
const char *table_name)
{
TABLE *tab= find_locked_table(list, db, table_name);
while (tab != NULL)
{
if (tab->mdl_ticket != NULL &&
tab->mdl_ticket->is_upgradable_or_exclusive())
return tab;
tab= find_locked_table(tab->next, db, table_name);
}
return NULL;
}
/***********************************************************************
class Locked_tables_list implementation. Declared in sql_class.h
************************************************************************/
......
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