MDEV-27783 InnoDB: Failing assertion: table->get_ref_count() == 0 upon ALTER...

MDEV-27783 InnoDB: Failing assertion: table->get_ref_count() == 0 upon ALTER TABLE ... MODIFY COLUMN

- There is a race condition occurs between purge thread and DDL.
So purge thread can increment n_ref_count even after DDL does
purge_sys_t::stop_FTS().

- dict_table_open_on_id for purge thread should check
purge_sys.must_wait_FTS() before acquring the table.

- purge_sys.stop_FTS() does acquire dict_sys.latch for setting
the purge system flag and check table ref count on auxilary tables.
parent 4e1ca388
...@@ -68,6 +68,7 @@ Created 1/8/1996 Heikki Tuuri ...@@ -68,6 +68,7 @@ Created 1/8/1996 Heikki Tuuri
#include "srv0mon.h" #include "srv0mon.h"
#include "srv0start.h" #include "srv0start.h"
#include "trx0undo.h" #include "trx0undo.h"
#include "trx0purge.h"
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
...@@ -819,12 +820,14 @@ template dict_table_t* dict_acquire_mdl_shared<true> ...@@ -819,12 +820,14 @@ template dict_table_t* dict_acquire_mdl_shared<true>
(dict_table_t*,THD*,MDL_ticket**,dict_table_op_t); (dict_table_t*,THD*,MDL_ticket**,dict_table_op_t);
/** Look up a table by numeric identifier. /** Look up a table by numeric identifier.
@tparam purge_thd Whether the function is called by purge thread
@param[in] table_id table identifier @param[in] table_id table identifier
@param[in] dict_locked data dictionary locked @param[in] dict_locked data dictionary locked
@param[in] table_op operation to perform when opening @param[in] table_op operation to perform when opening
@param[in,out] thd background thread, or NULL to not acquire MDL @param[in,out] thd background thread, or NULL to not acquire MDL
@param[out] mdl mdl ticket, or NULL @param[out] mdl mdl ticket, or NULL
@return table, NULL if does not exist */ @return table, NULL if does not exist */
template <bool purge_thd>
dict_table_t* dict_table_t*
dict_table_open_on_id(table_id_t table_id, bool dict_locked, dict_table_open_on_id(table_id_t table_id, bool dict_locked,
dict_table_op_t table_op, THD *thd, dict_table_op_t table_op, THD *thd,
...@@ -837,6 +840,12 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked, ...@@ -837,6 +840,12 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked,
if (table) if (table)
{ {
if (purge_thd && purge_sys.must_wait_FTS())
{
table= nullptr;
goto func_exit;
}
table->acquire(); table->acquire();
if (thd && !dict_locked) if (thd && !dict_locked)
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op); table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
...@@ -853,7 +862,14 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked, ...@@ -853,7 +862,14 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked,
? DICT_ERR_IGNORE_RECOVER_LOCK ? DICT_ERR_IGNORE_RECOVER_LOCK
: DICT_ERR_IGNORE_FK_NOKEY); : DICT_ERR_IGNORE_FK_NOKEY);
if (table) if (table)
{
if (purge_thd && purge_sys.must_wait_FTS())
{
dict_sys.unlock();
return nullptr;
}
table->acquire(); table->acquire();
}
if (!dict_locked) if (!dict_locked)
{ {
dict_sys.unlock(); dict_sys.unlock();
...@@ -867,12 +883,22 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked, ...@@ -867,12 +883,22 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked,
} }
} }
func_exit:
if (!dict_locked) if (!dict_locked)
dict_sys.unfreeze(); dict_sys.unfreeze();
return table; return table;
} }
template dict_table_t* dict_table_open_on_id<false>
(table_id_t table_id, bool dict_locked,
dict_table_op_t table_op, THD *thd,
MDL_ticket **mdl);
template dict_table_t* dict_table_open_on_id<true>
(table_id_t table_id, bool dict_locked,
dict_table_op_t table_op, THD *thd,
MDL_ticket **mdl);
/********************************************************************//** /********************************************************************//**
Looks for column n position in the clustered index. Looks for column n position in the clustered index.
@return position in internal representation of the clustered index */ @return position in internal representation of the clustered index */
......
...@@ -1555,12 +1555,16 @@ have any other reference count. ...@@ -1555,12 +1555,16 @@ have any other reference count.
static void fts_table_no_ref_count(const char *table_name) static void fts_table_no_ref_count(const char *table_name)
{ {
dict_table_t *table= dict_table_open_on_name( dict_table_t *table= dict_table_open_on_name(
table_name, false, DICT_ERR_IGNORE_TABLESPACE); table_name, true, DICT_ERR_IGNORE_TABLESPACE);
if (!table) if (!table)
return; return;
while (table->get_ref_count() > 1) while (table->get_ref_count() > 1)
{
dict_sys.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
dict_sys.lock(SRW_LOCK_CALL);
}
table->release(); table->release();
} }
...@@ -1572,8 +1576,10 @@ and common table associated with the fts table. ...@@ -1572,8 +1576,10 @@ and common table associated with the fts table.
already stopped*/ already stopped*/
void purge_sys_t::stop_FTS(const dict_table_t &table, bool already_stopped) void purge_sys_t::stop_FTS(const dict_table_t &table, bool already_stopped)
{ {
dict_sys.lock(SRW_LOCK_CALL);
if (!already_stopped) if (!already_stopped)
purge_sys.stop_FTS(); purge_sys.stop_FTS();
fts_table_t fts_table; fts_table_t fts_table;
char table_name[MAX_FULL_NAME_LEN]; char table_name[MAX_FULL_NAME_LEN];
...@@ -1582,28 +1588,31 @@ void purge_sys_t::stop_FTS(const dict_table_t &table, bool already_stopped) ...@@ -1582,28 +1588,31 @@ void purge_sys_t::stop_FTS(const dict_table_t &table, bool already_stopped)
for (const char **suffix= fts_common_tables; *suffix; suffix++) for (const char **suffix= fts_common_tables; *suffix; suffix++)
{ {
fts_table.suffix= *suffix; fts_table.suffix= *suffix;
fts_get_table_name(&fts_table, table_name, false); fts_get_table_name(&fts_table, table_name, true);
fts_table_no_ref_count(table_name); fts_table_no_ref_count(table_name);
} }
if (!table.fts) if (table.fts)
return;
auto indexes= table.fts->indexes;
if (!indexes)
return;
for (ulint i= 0;i < ib_vector_size(indexes); ++i)
{ {
const dict_index_t *index= static_cast<const dict_index_t*>( if (auto indexes= table.fts->indexes)
ib_vector_getp(indexes, i));
FTS_INIT_INDEX_TABLE(&fts_table, nullptr, FTS_INDEX_TABLE, index);
for (const fts_index_selector_t *s= fts_index_selector;
s->suffix; s++)
{ {
fts_table.suffix= s->suffix; for (ulint i= 0;i < ib_vector_size(indexes); ++i)
fts_get_table_name(&fts_table, table_name, false); {
fts_table_no_ref_count(table_name); const dict_index_t *index= static_cast<const dict_index_t*>(
ib_vector_getp(indexes, i));
FTS_INIT_INDEX_TABLE(&fts_table, nullptr, FTS_INDEX_TABLE, index);
for (const fts_index_selector_t *s= fts_index_selector;
s->suffix; s++)
{
fts_table.suffix= s->suffix;
fts_get_table_name(&fts_table, table_name, true);
fts_table_no_ref_count(table_name);
}
}
} }
} }
dict_sys.unlock();
} }
/** Lock the internal FTS_ tables for table, before fts_drop_tables(). /** Lock the internal FTS_ tables for table, before fts_drop_tables().
......
...@@ -146,6 +146,7 @@ dict_acquire_mdl_shared(dict_table_t *table, ...@@ -146,6 +146,7 @@ dict_acquire_mdl_shared(dict_table_t *table,
@param[in,out] thd background thread, or NULL to not acquire MDL @param[in,out] thd background thread, or NULL to not acquire MDL
@param[out] mdl mdl ticket, or NULL @param[out] mdl mdl ticket, or NULL
@return table, NULL if does not exist */ @return table, NULL if does not exist */
template<bool purge_thd= false>
dict_table_t* dict_table_t*
dict_table_open_on_id(table_id_t table_id, bool dict_locked, dict_table_open_on_id(table_id_t table_id, bool dict_locked,
dict_table_op_t table_op, THD *thd= nullptr, dict_table_op_t table_op, THD *thd= nullptr,
......
...@@ -1027,10 +1027,14 @@ row_purge_parse_undo_rec( ...@@ -1027,10 +1027,14 @@ row_purge_parse_undo_rec(
try_again: try_again:
purge_sys.check_stop_FTS(); purge_sys.check_stop_FTS();
node->table = dict_table_open_on_id( node->table = dict_table_open_on_id<true>(
table_id, false, DICT_TABLE_OP_NORMAL, node->purge_thd, table_id, false, DICT_TABLE_OP_NORMAL, node->purge_thd,
&node->mdl_ticket); &node->mdl_ticket);
if (!node->table && purge_sys.must_wait_FTS()) {
goto try_again;
}
if (!node->table) { if (!node->table) {
/* The table has been dropped: no need to do purge and /* The table has been dropped: no need to do purge and
release mdl happened as a part of open process itself */ release mdl happened as a part of open process itself */
......
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