Commit d5a498ab authored by Dmitry Lenev's avatar Dmitry Lenev

Fix for bug #50908 "Assertion `handler_tables_hash.records == 0'

failed in enter_locked_tables_mode".

Server was aborted due to assertion failure when one tried to 
execute statement requiring prelocking (i.e. firing triggers
or using stored functions) while having open HANDLERs.

The problem was that THD::enter_locked_tables_mode() method
which was called at the beginning of execution of prelocked 
statement assumed there are no open HANDLERs. It had to do 
so because corresponding THD::leave_locked_tables_mode()
method was unable to properly restore MDL sentinel when
leaving LOCK TABLES/prelocked mode in the presence of open 
HANDLERs.

This patch solves this problem by changing the latter method
to properly restore MDL sentinel and thus removing need for 
this assumption. As a side-effect, it lifts unjustified
limitation by allowing to keep HANDLERs open when entering 
LOCK TABLES mode.

mysql-test/include/handler.inc:
  Adjusted tests after making LOCK TABLES not to close
  open HANDLERs. Added coverage for bug #50908
  "Assertion `handler_tables_hash.records == 0' failed
  in enter_locked_tables_mode".
mysql-test/r/handler_innodb.result:
  Updated test results (see include/handler.inc).
mysql-test/r/handler_myisam.result:
  Updated test results (see include/handler.inc).
sql/mysql_priv.h:
  Introduced mysql_ha_move_tickets_after_trans_sentinel()
  routine which allows to move tickets for metadata locks
  corresponding to open HANDLERs after transaction sentinel.
sql/sql_class.cc:
  Changed THD::leave_locked_tables_mode() to correctly restore 
  MDL sentinel value in the presence of open HANDLERs.
sql/sql_class.h:
  Removed assert from THD::enter_locked_tables_mode() as we
  no longer have to close HANDLERs when entering LOCK TABLES 
  or prelocked modes. Instead we keep them open and correctly
  restore MDL sentinel value after leaving them.
  Removal of assert also fixes problem from the bug report.
sql/sql_handler.cc:
  Introduced mysql_ha_move_tickets_after_trans_sentinel()
  routine which allows to move tickets for metadata locks
  corresponding to open HANDLERs after transaction sentinel.
sql/sql_parse.cc:
  We no longer have to close HANDLERs when entering LOCK TABLES 
  mode. Instead we keep them open and simply correctly restore 
  MDL sentinel value after leaving this mode.
parent e6cb88eb
......@@ -804,12 +804,10 @@ handler t1 open;
handler t2 open;
handler t3 open;
--echo #
--echo # LOCK TABLES implicitly closes all handlers.
--echo #
lock table t3 read;
--echo #
--echo # No HANDLER sql is available under lock tables anyway.
--echo # No HANDLER sql is allowed under LOCK TABLES.
--echo # But it does not implicitly closes all handlers.
--echo #
lock table t1 read;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t1 open;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
......@@ -818,18 +816,18 @@ handler t1 read next;
handler t2 close;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t3 open;
--echo # After UNLOCK TABLES no handlers are around, they were
--echo # implicitly closed.
--echo # After UNLOCK TABLES handlers should be around and
--echo # we should be able to continue reading through them.
unlock tables;
drop temporary table t3;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--error ER_UNKNOWN_TABLE
handler t1 close;
handler t2 read next;
handler t2 close;
--error ER_UNKNOWN_TABLE
handler t3 read next;
handler t3 close;
drop temporary table t3;
--echo #
--echo # Other operations also implicitly close handler:
--echo # Other operations that implicitly close handler:
--echo #
--echo # TRUNCATE
--echo #
......@@ -922,6 +920,93 @@ handler t1 read a prev;
handler t1 close;
unlock tables;
--echo #
--echo # Let us also check that these operations behave in similar
--echo # way under LOCK TABLES.
--echo #
--echo # TRUNCATE under LOCK TABLES.
--echo #
handler t1 open;
lock tables t1 write;
truncate table t1;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
handler t1 open;
--echo #
--echo # CREATE TRIGGER under LOCK TABLES.
--echo #
lock tables t1 write;
create trigger t1_ai after insert on t1 for each row set @a=1;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # DROP TRIGGER under LOCK TABLES.
--echo #
handler t1 open;
lock tables t1 write;
drop trigger t1_ai;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # ALTER TABLE under LOCK TABLES.
--echo #
handler t1 open;
lock tables t1 write;
alter table t1 drop column b;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # ANALYZE TABLE under LOCK TABLES.
--echo #
handler t1 open;
lock tables t1 write;
analyze table t1;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # OPTIMIZE TABLE under LOCK TABLES.
--echo #
handler t1 open;
lock tables t1 write;
optimize table t1;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # REPAIR TABLE under LOCK TABLES.
--echo #
handler t1 open;
lock tables t1 write;
repair table t1;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # DROP TABLE under LOCK TABLES, naturally.
--echo #
handler t1 open;
lock tables t1 write;
drop table t1;
unlock tables;
--error ER_UNKNOWN_TABLE
handler t1 read next;
create table t1 (a int, b int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
--echo #
--echo # FLUSH TABLE doesn't close the table but loses the position
--echo #
handler t1 open;
handler t1 read a prev;
lock tables t1 write;
flush table t1;
unlock tables;
handler t1 read a prev;
handler t1 close;
--echo #
--echo # Explore the effect of HANDLER locks on concurrent DDL
--echo #
handler t1 open;
......@@ -1433,6 +1518,8 @@ lock table not_exists_write read;
--echo # We still have the read lock.
--error ER_CANT_UPDATE_WITH_READLOCK
drop table t1;
handler t1 read next;
handler t1 close;
handler t1 open;
select a from t2;
handler t1 read next;
......@@ -1542,3 +1629,55 @@ SELECT table_name, table_comment FROM information_schema.tables
HANDLER t1 CLOSE;
DROP TABLE t1;
--echo #
--echo # Test for bug #50908 "Assertion `handler_tables_hash.records == 0'
--echo # failed in enter_locked_tables_mode".
--echo #
--disable_warnings
drop tables if exists t1, t2;
drop function if exists f1;
--enable_warnings
create table t1 (i int);
insert into t1 values (1), (2);
create table t2 (j int);
insert into t2 values (1);
create function f1() returns int return (select count(*) from t2);
--echo # Check that open HANDLER survives statement executed in
--echo # prelocked mode.
handler t1 open;
handler t1 read next;
--echo # The below statement were aborted due to an assertion failure.
select f1() from t2;
handler t1 read next;
handler t1 close;
--echo # Check that the same happens under GLOBAL READ LOCK.
flush tables with read lock;
handler t1 open;
handler t1 read next;
select f1() from t2;
handler t1 read next;
unlock tables;
handler t1 close;
--echo # Now, check that the same happens if LOCK TABLES is executed.
handler t1 open;
handler t1 read next;
lock table t2 read;
select * from t2;
unlock tables;
handler t1 read next;
handler t1 close;
--echo # Finally, check scenario with GRL and LOCK TABLES.
flush tables with read lock;
handler t1 open;
handler t1 read next;
lock table t2 read;
select * from t2;
--echo # This unlocks both tables and GRL.
unlock tables;
handler t1 read next;
handler t1 close;
--echo # Clean-up.
drop function f1;
drop tables t1, t2;
......@@ -788,12 +788,10 @@ handler t1 open;
handler t2 open;
handler t3 open;
#
# LOCK TABLES implicitly closes all handlers.
#
lock table t3 read;
#
# No HANDLER sql is available under lock tables anyway.
# No HANDLER sql is allowed under LOCK TABLES.
# But it does not implicitly closes all handlers.
#
lock table t1 read;
handler t1 open;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
handler t1 read next;
......@@ -802,18 +800,24 @@ handler t2 close;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
handler t3 open;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
# After UNLOCK TABLES no handlers are around, they were
# implicitly closed.
# After UNLOCK TABLES handlers should be around and
# we should be able to continue reading through them.
unlock tables;
drop temporary table t3;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
a
1
handler t1 close;
handler t2 read next;
a
1
handler t2 close;
ERROR 42S02: Unknown table 't2' in HANDLER
handler t3 read next;
ERROR 42S02: Unknown table 't3' in HANDLER
a
1
handler t3 close;
drop temporary table t3;
#
# Other operations also implicitly close handler:
# Other operations that implicitly close handler:
#
# TRUNCATE
#
......@@ -923,6 +927,104 @@ NULL 5
handler t1 close;
unlock tables;
#
# Let us also check that these operations behave in similar
# way under LOCK TABLES.
#
# TRUNCATE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
truncate table t1;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
handler t1 open;
#
# CREATE TRIGGER under LOCK TABLES.
#
lock tables t1 write;
create trigger t1_ai after insert on t1 for each row set @a=1;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# DROP TRIGGER under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
drop trigger t1_ai;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# ALTER TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
alter table t1 drop column b;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# ANALYZE TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
analyze table t1;
Table Op Msg_type Msg_text
test.t1 analyze status OK
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# OPTIMIZE TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
optimize table t1;
Table Op Msg_type Msg_text
test.t1 optimize note Table does not support optimize, doing recreate + analyze instead
test.t1 optimize status OK
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# REPAIR TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
repair table t1;
Table Op Msg_type Msg_text
test.t1 repair note The storage engine for the table doesn't support repair
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# DROP TABLE under LOCK TABLES, naturally.
#
handler t1 open;
lock tables t1 write;
drop table t1;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
create table t1 (a int, b int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
#
# FLUSH TABLE doesn't close the table but loses the position
#
handler t1 open;
handler t1 read a prev;
a b
5 NULL
lock tables t1 write;
flush table t1;
unlock tables;
handler t1 read a prev;
a b
5 NULL
handler t1 close;
#
# Explore the effect of HANDLER locks on concurrent DDL
#
handler t1 open;
......@@ -936,11 +1038,11 @@ drop table t1 ;
# Waitng for 'drop table t1' to get blocked...
# --> connection default
handler t1 read a prev;
b a
NULL 5
a b
5 NULL
handler t1 read a prev;
b a
NULL 4
a b
4 NULL
handler t1 close;
# --> connection con1
# Reaping 'drop table t1'...
......@@ -1383,6 +1485,10 @@ ERROR 42S02: Table 'test.not_exists_write' doesn't exist
# We still have the read lock.
drop table t1;
ERROR HY000: Can't execute the query because you have a conflicting read lock
handler t1 read next;
a b
1 1
handler t1 close;
handler t1 open;
select a from t2;
a
......@@ -1489,3 +1595,75 @@ table_name table_comment
t1
HANDLER t1 CLOSE;
DROP TABLE t1;
#
# Test for bug #50908 "Assertion `handler_tables_hash.records == 0'
# failed in enter_locked_tables_mode".
#
drop tables if exists t1, t2;
drop function if exists f1;
create table t1 (i int);
insert into t1 values (1), (2);
create table t2 (j int);
insert into t2 values (1);
create function f1() returns int return (select count(*) from t2);
# Check that open HANDLER survives statement executed in
# prelocked mode.
handler t1 open;
handler t1 read next;
i
1
# The below statement were aborted due to an assertion failure.
select f1() from t2;
f1()
1
handler t1 read next;
i
2
handler t1 close;
# Check that the same happens under GLOBAL READ LOCK.
flush tables with read lock;
handler t1 open;
handler t1 read next;
i
1
select f1() from t2;
f1()
1
handler t1 read next;
i
2
unlock tables;
handler t1 close;
# Now, check that the same happens if LOCK TABLES is executed.
handler t1 open;
handler t1 read next;
i
1
lock table t2 read;
select * from t2;
j
1
unlock tables;
handler t1 read next;
i
2
handler t1 close;
# Finally, check scenario with GRL and LOCK TABLES.
flush tables with read lock;
handler t1 open;
handler t1 read next;
i
1
lock table t2 read;
select * from t2;
j
1
# This unlocks both tables and GRL.
unlock tables;
handler t1 read next;
i
2
handler t1 close;
# Clean-up.
drop function f1;
drop tables t1, t2;
......@@ -786,12 +786,10 @@ handler t1 open;
handler t2 open;
handler t3 open;
#
# LOCK TABLES implicitly closes all handlers.
#
lock table t3 read;
#
# No HANDLER sql is available under lock tables anyway.
# No HANDLER sql is allowed under LOCK TABLES.
# But it does not implicitly closes all handlers.
#
lock table t1 read;
handler t1 open;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
handler t1 read next;
......@@ -800,18 +798,24 @@ handler t2 close;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
handler t3 open;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
# After UNLOCK TABLES no handlers are around, they were
# implicitly closed.
# After UNLOCK TABLES handlers should be around and
# we should be able to continue reading through them.
unlock tables;
drop temporary table t3;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
a
1
handler t1 close;
handler t2 read next;
a
1
handler t2 close;
ERROR 42S02: Unknown table 't2' in HANDLER
handler t3 read next;
ERROR 42S02: Unknown table 't3' in HANDLER
a
1
handler t3 close;
drop temporary table t3;
#
# Other operations also implicitly close handler:
# Other operations that implicitly close handler:
#
# TRUNCATE
#
......@@ -920,6 +924,103 @@ NULL 5
handler t1 close;
unlock tables;
#
# Let us also check that these operations behave in similar
# way under LOCK TABLES.
#
# TRUNCATE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
truncate table t1;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
handler t1 open;
#
# CREATE TRIGGER under LOCK TABLES.
#
lock tables t1 write;
create trigger t1_ai after insert on t1 for each row set @a=1;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# DROP TRIGGER under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
drop trigger t1_ai;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# ALTER TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
alter table t1 drop column b;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# ANALYZE TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
analyze table t1;
Table Op Msg_type Msg_text
test.t1 analyze status Table is already up to date
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# OPTIMIZE TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
optimize table t1;
Table Op Msg_type Msg_text
test.t1 optimize status OK
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# REPAIR TABLE under LOCK TABLES.
#
handler t1 open;
lock tables t1 write;
repair table t1;
Table Op Msg_type Msg_text
test.t1 repair status OK
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
#
# DROP TABLE under LOCK TABLES, naturally.
#
handler t1 open;
lock tables t1 write;
drop table t1;
unlock tables;
handler t1 read next;
ERROR 42S02: Unknown table 't1' in HANDLER
create table t1 (a int, b int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
#
# FLUSH TABLE doesn't close the table but loses the position
#
handler t1 open;
handler t1 read a prev;
a b
5 NULL
lock tables t1 write;
flush table t1;
unlock tables;
handler t1 read a prev;
a b
5 NULL
handler t1 close;
#
# Explore the effect of HANDLER locks on concurrent DDL
#
handler t1 open;
......@@ -933,11 +1034,11 @@ drop table t1 ;
# Waitng for 'drop table t1' to get blocked...
# --> connection default
handler t1 read a prev;
b a
NULL 5
a b
5 NULL
handler t1 read a prev;
b a
NULL 4
a b
4 NULL
handler t1 close;
# --> connection con1
# Reaping 'drop table t1'...
......@@ -1380,6 +1481,10 @@ ERROR 42S02: Table 'test.not_exists_write' doesn't exist
# We still have the read lock.
drop table t1;
ERROR HY000: Can't execute the query because you have a conflicting read lock
handler t1 read next;
a b
1 1
handler t1 close;
handler t1 open;
select a from t2;
a
......@@ -1487,6 +1592,78 @@ t1
HANDLER t1 CLOSE;
DROP TABLE t1;
#
# Test for bug #50908 "Assertion `handler_tables_hash.records == 0'
# failed in enter_locked_tables_mode".
#
drop tables if exists t1, t2;
drop function if exists f1;
create table t1 (i int);
insert into t1 values (1), (2);
create table t2 (j int);
insert into t2 values (1);
create function f1() returns int return (select count(*) from t2);
# Check that open HANDLER survives statement executed in
# prelocked mode.
handler t1 open;
handler t1 read next;
i
1
# The below statement were aborted due to an assertion failure.
select f1() from t2;
f1()
1
handler t1 read next;
i
2
handler t1 close;
# Check that the same happens under GLOBAL READ LOCK.
flush tables with read lock;
handler t1 open;
handler t1 read next;
i
1
select f1() from t2;
f1()
1
handler t1 read next;
i
2
unlock tables;
handler t1 close;
# Now, check that the same happens if LOCK TABLES is executed.
handler t1 open;
handler t1 read next;
i
1
lock table t2 read;
select * from t2;
j
1
unlock tables;
handler t1 read next;
i
2
handler t1 close;
# Finally, check scenario with GRL and LOCK TABLES.
flush tables with read lock;
handler t1 open;
handler t1 read next;
i
1
lock table t2 read;
select * from t2;
j
1
# This unlocks both tables and GRL.
unlock tables;
handler t1 read next;
i
2
handler t1 close;
# Clean-up.
drop function f1;
drop tables t1, t2;
#
# BUG #46456: HANDLER OPEN + TRUNCATE + DROP (temporary) TABLE, crash
#
CREATE TABLE t1 AS SELECT 1 AS f1;
......
......@@ -1438,6 +1438,7 @@ bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *,
void mysql_ha_flush(THD *thd);
void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables);
void mysql_ha_cleanup(THD *thd);
void mysql_ha_move_tickets_after_trans_sentinel(THD *thd);
/* sql_base.cc */
#define TMP_TABLE_KEY_EXTRA 8
......
......@@ -3311,6 +3311,22 @@ void THD::set_query_id(query_id_t new_query_id)
}
/**
Leave explicit LOCK TABLES or prelocked mode and restore value of
transaction sentinel in MDL subsystem.
*/
void THD::leave_locked_tables_mode()
{
locked_tables_mode= LTM_NONE;
/* Make sure we don't release the global read lock when leaving LTM. */
mdl_context.reset_trans_sentinel(global_read_lock.global_shared_lock());
/* Also ensure that we don't release metadata locks for open HANDLERs. */
if (handler_tables_hash.records)
mysql_ha_move_tickets_after_trans_sentinel(this);
}
/**
Mark transaction to rollback and mark error as fatal to a sub-statement.
......
......@@ -2683,17 +2683,11 @@ public:
void enter_locked_tables_mode(enum_locked_tables_mode mode_arg)
{
DBUG_ASSERT(locked_tables_mode == LTM_NONE);
DBUG_ASSERT(handler_tables_hash.records == 0);
mdl_context.set_trans_sentinel();
locked_tables_mode= mode_arg;
}
void leave_locked_tables_mode()
{
locked_tables_mode= LTM_NONE;
/* Make sure we don't release the global read lock when leaving LTM. */
mdl_context.reset_trans_sentinel(global_read_lock.global_shared_lock());
}
void leave_locked_tables_mode();
int decide_logging_format(TABLE_LIST *tables);
private:
......
......@@ -837,3 +837,27 @@ void mysql_ha_cleanup(THD *thd)
DBUG_VOID_RETURN;
}
/**
Move tickets for metadata locks corresponding to open HANDLERs
after transaction sentinel in order to protect them from being
released at the end of transaction.
@param thd Thread identifier.
*/
void mysql_ha_move_tickets_after_trans_sentinel(THD *thd)
{
TABLE_LIST *hash_tables;
DBUG_ENTER("mysql_ha_move_tickets_after_trans_sentinel");
for (uint i= 0; i < thd->handler_tables_hash.records; i++)
{
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
if (hash_tables->table && hash_tables->table->mdl_ticket)
thd->mdl_context.
move_ticket_after_trans_sentinel(hash_tables->table->mdl_ticket);
}
DBUG_VOID_RETURN;
}
......@@ -3283,14 +3283,6 @@ end_with_restore_list:
break;
case SQLCOM_LOCK_TABLES:
thd->locked_tables_list.unlock_locked_tables(thd);
/*
As of 5.5, entering LOCK TABLES mode implicitly closes all
open HANDLERs. Both HANDLER code and LOCK TABLES mode use
the sentinel mechanism in MDL subsystem and thus could not be
used at the same time. All HANDLER operations are prohibited
under LOCK TABLES anyway.
*/
mysql_ha_cleanup(thd);
/* we must end the trasaction first, regardless of anything */
if (trans_commit_implicit(thd))
goto error;
......
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