Commit 17695cb4 authored by Gopal Shankar's avatar Gopal Shankar

Bug#14364558 ASSERT `TABLE_LIST->PRELOCKING_PLACEHOLDER==FALSE'

             FAILED IN CHECK_LOCK_AND_ST

Problem:
--------
lock_tables() is supposed to invoke check_lock_and_start_stmt()
for TABLE_LIST which are directly used by top level statement.
TABLE_LIST->prelocking_placeholder is set only for TABLE_LIST
which are used indirectly by stored programs invoked by top
level statement. Hence check_lock_and_start_stmt() should have
TABLE_LIST->prelocking_placeholder==false always, but it is
observed that this assert fails.

The failure is found during RQG test rqg_signal_resignal.

Analysis:
---------
open_tables() invokes open_and_process_routines() where it
finds all the TABLE_LIST that belong to the routine and
adds it to thd->lex->query_tables. During this process if
the open_and_process_routines() fail for some reason,
we are supposed to chop-off all the TABLE_LIST found during
calls to open_and_process_routines(). But, in practice this
is not happening.

thd->lex->query_tables_own_last is supposed to point to a
node in thd->lex->query_tables, which would be a first
TABLE_LIST used indirectly by stored programs invoked by
top level statement. This is found to be not-set correctly
when we plan to chop-off TABLE_LIST's, when
open_and_process_routines() failed.

close_tables_for_reopen() does chop-off all the TABLE_LIST
added after thd->lex->query_table_own_last. This is invoked
upon error in open_and_process_routines(). This call would
not work as expected as thd->lex->query_tables_own_last
is not set, or is not set to correctly.

Further, when open_tables() restarts the process of finding
TABLE_LIST belonging to stored programs, and as the
thd->lex->query_tables_own_last points to in-correct node,
there is possibility of new iteration setting the
thd->lex->query_tables_own_last past some old nodes that
belong to stored programs, added earlier and not removed.
Later when open_tables() completes, lock_tables() ends up
invoking check_lock_and_start_stmt() for TABLE_LIST which
belong to stored programs, which is not expected behavior
and hence we hit the assert
TABLE_LIST->prelocking_placeholder==false.

Due to above behavior, if a user application tries to
execute a SQL statement which invokes some stored function
and if the lock grant on stored function fails due to a
deadlock, then mysqld crashes.

Fix:
----
open_tables() remembers save_query_tables_last which points
to thd-lex->query_tables_last before calls to
open_and_process_routines(). If there is no known
thd->lex->query_tables_own_last set, we are now setting
thd->lex->query_tables_own_last to save_query_tables_last.
This will make sure that the call to close_tables_for_reopen()
will chop-off the list correctly, in other words we now
remove all the nodes added to thd->lex->query_tables, by
previous calls to open_and_process_routines().

Further, it is found that the problem exists starting
from 5.5, due to a code refactoring effort related to
open_tables(). Hence, the fix will be pushed in 5.5, 5.6
and trunk.
parent a16e00a6
...@@ -4955,8 +4955,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, ...@@ -4955,8 +4955,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
*/ */
if (thd->locked_tables_mode <= LTM_LOCK_TABLES) if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
{ {
bool need_prelocking= FALSE;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
/* /*
Process elements of the prelocking set which are present there Process elements of the prelocking set which are present there
since parsing stage or were added to it by invocations of since parsing stage or were added to it by invocations of
...@@ -4969,10 +4967,19 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, ...@@ -4969,10 +4967,19 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
for (Sroutine_hash_entry *rt= *sroutine_to_open; rt; for (Sroutine_hash_entry *rt= *sroutine_to_open; rt;
sroutine_to_open= &rt->next, rt= rt->next) sroutine_to_open= &rt->next, rt= rt->next)
{ {
bool need_prelocking= false;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy, error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy,
has_prelocking_list, &ot_ctx, has_prelocking_list, &ot_ctx,
&need_prelocking); &need_prelocking);
if (need_prelocking && ! thd->lex->requires_prelocking())
thd->lex->mark_as_requiring_prelocking(save_query_tables_last);
if (need_prelocking && ! *start)
*start= thd->lex->query_tables;
if (error) if (error)
{ {
if (ot_ctx.can_recover_from_failed_open()) if (ot_ctx.can_recover_from_failed_open())
...@@ -4993,12 +5000,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, ...@@ -4993,12 +5000,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
goto err; goto err;
} }
} }
if (need_prelocking && ! thd->lex->requires_prelocking())
thd->lex->mark_as_requiring_prelocking(save_query_tables_last);
if (need_prelocking && ! *start)
*start= thd->lex->query_tables;
} }
} }
...@@ -5271,6 +5272,12 @@ static bool check_lock_and_start_stmt(THD *thd, ...@@ -5271,6 +5272,12 @@ static bool check_lock_and_start_stmt(THD *thd,
thr_lock_type lock_type; thr_lock_type lock_type;
DBUG_ENTER("check_lock_and_start_stmt"); DBUG_ENTER("check_lock_and_start_stmt");
/*
Prelocking placeholder is not set for TABLE_LIST that
are directly used by TOP level statement.
*/
DBUG_ASSERT(table_list->prelocking_placeholder == false);
/* /*
TL_WRITE_DEFAULT and TL_READ_DEFAULT are supposed to be parser only TL_WRITE_DEFAULT and TL_READ_DEFAULT are supposed to be parser only
types of locks so they should be converted to appropriate other types types of locks so they should be converted to appropriate other types
......
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