Skip to content

Commit d1a6792

Browse files
committed
MDEV-36122: Protect table references with a lock
dict_table_open_on_id(): Simplify the logic. dict_stats: A helper for acquiring MDL and opening the tables mysql.innodb_table_stats and mysql.innodb_index_stats. innodb_ft_aux_table_validate(): Contiguously hold dict_sys.latch while accessing the table that we open with dict_table_open_on_name(). lock_table_children(): Do not hold a table reference while invoking dict_acquire_mdl_shared<false>(), which may temporarily release and reacquire the shared dict_sys.latch that we are holding. prepare_inplace_alter_table_dict(): If an unexpected reference to the table exists, wait for the purge subsystem to release its table handle, similar to how we would do in case FULLTEXT INDEX existed. This function is supposed to be protected by MDL_EXCLUSIVE on the table name. If purge is going to access the table again later during is ALTER TABLE operation, it will have access to an MDL compatible name for it and therefore should conflict with any MDL_EXCLUSIVE that would cover ha_innobase::commit_inplace_alter_table(commit=true). ha_innobase::rename_table(): Before locking the data dictionary, ensure that the purge subsystem is not holding a reference to the table due to the lack of metadata locking, related to FULLTEXT INDEX or the row-level undo logging of ALTER IGNORE TABLE. ha_innobase::truncate(): Before locking the data dictionary, ensure that the purge subsystem is not holding a reference to the table due to insufficient metadata locking related to an earlier ALTER IGNORE TABLE operation. trx_purge_attach_undo_recs(), purge_sys_t::batch_cleanup(): Clear purge_sys.m_active only after all table handles have been released. With these changes, no caller of dict_acquire_mdl_shared<false> should be holding a table reference. All remaining calls to dict_table_open_on_name(dict_locked=false) except the one in fts_lock_table() and possibly in the DDL recovery predicate innodb_check_version() should be protected by MDL, but there currently is no assertion that would enforce this. Reviewed by: Debarun Banerjee
1 parent 4a21cba commit d1a6792

File tree

8 files changed

+235
-391
lines changed

8 files changed

+235
-391
lines changed

storage/innobase/dict/dict0defrag_bg.cc

Lines changed: 11 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -217,47 +217,17 @@ dberr_t dict_stats_save_defrag_summary(dict_index_t *index, THD *thd)
217217
if (index->is_ibuf())
218218
return DB_SUCCESS;
219219

220-
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
221-
dict_table_t *table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
222-
DICT_ERR_IGNORE_NONE);
223-
if (table_stats)
224-
{
225-
dict_sys.freeze(SRW_LOCK_CALL);
226-
table_stats= dict_acquire_mdl_shared<false>(table_stats, thd, &mdl_table);
227-
dict_sys.unfreeze();
228-
}
229-
if (!table_stats || strcmp(table_stats->name.m_name, TABLE_STATS_NAME))
230-
{
231-
release_and_exit:
232-
if (table_stats)
233-
dict_table_close(table_stats, thd, mdl_table);
220+
dict_stats stats;
221+
if (stats.open(thd))
234222
return DB_STATS_DO_NOT_EXIST;
235-
}
236-
237-
dict_table_t *index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
238-
DICT_ERR_IGNORE_NONE);
239-
if (index_stats)
240-
{
241-
dict_sys.freeze(SRW_LOCK_CALL);
242-
index_stats= dict_acquire_mdl_shared<false>(index_stats, thd, &mdl_index);
243-
dict_sys.unfreeze();
244-
}
245-
if (!index_stats)
246-
goto release_and_exit;
247-
if (strcmp(index_stats->name.m_name, INDEX_STATS_NAME))
248-
{
249-
dict_table_close(index_stats, thd, mdl_index);
250-
goto release_and_exit;
251-
}
252-
253223
trx_t *trx= trx_create();
254224
trx->mysql_thd= thd;
255225
trx_start_internal(trx);
256226
dberr_t ret= trx->read_only
257227
? DB_READ_ONLY
258-
: lock_table_for_trx(table_stats, trx, LOCK_X);
228+
: lock_table_for_trx(stats.table(), trx, LOCK_X);
259229
if (ret == DB_SUCCESS)
260-
ret= lock_table_for_trx(index_stats, trx, LOCK_X);
230+
ret= lock_table_for_trx(stats.index(), trx, LOCK_X);
261231
row_mysql_lock_data_dictionary(trx);
262232
if (ret == DB_SUCCESS)
263233
ret= dict_stats_save_index_stat(index, time(nullptr), "n_pages_freed",
@@ -271,13 +241,9 @@ dberr_t dict_stats_save_defrag_summary(dict_index_t *index, THD *thd)
271241
else
272242
trx->rollback();
273243

274-
if (table_stats)
275-
dict_table_close(table_stats, thd, mdl_table);
276-
if (index_stats)
277-
dict_table_close(index_stats, thd, mdl_index);
278-
279244
row_mysql_unlock_data_dictionary(trx);
280245
trx->free();
246+
stats.close();
281247

282248
return ret;
283249
}
@@ -353,49 +319,18 @@ dict_stats_save_defrag_stats(
353319
if (!n_leaf_reserved)
354320
return DB_SUCCESS;
355321

356-
THD *thd= current_thd;
357-
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
358-
dict_table_t* table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
359-
DICT_ERR_IGNORE_NONE);
360-
if (table_stats)
361-
{
362-
dict_sys.freeze(SRW_LOCK_CALL);
363-
table_stats= dict_acquire_mdl_shared<false>(table_stats, thd, &mdl_table);
364-
dict_sys.unfreeze();
365-
}
366-
if (!table_stats || strcmp(table_stats->name.m_name, TABLE_STATS_NAME))
367-
{
368-
release_and_exit:
369-
if (table_stats)
370-
dict_table_close(table_stats, thd, mdl_table);
322+
THD *const thd= current_thd;
323+
dict_stats stats;
324+
if (stats.open(thd))
371325
return DB_STATS_DO_NOT_EXIST;
372-
}
373-
374-
dict_table_t *index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
375-
DICT_ERR_IGNORE_NONE);
376-
if (index_stats)
377-
{
378-
dict_sys.freeze(SRW_LOCK_CALL);
379-
index_stats= dict_acquire_mdl_shared<false>(index_stats, thd, &mdl_index);
380-
dict_sys.unfreeze();
381-
}
382-
if (!index_stats)
383-
goto release_and_exit;
384-
385-
if (strcmp(index_stats->name.m_name, INDEX_STATS_NAME))
386-
{
387-
dict_table_close(index_stats, thd, mdl_index);
388-
goto release_and_exit;
389-
}
390-
391326
trx_t *trx= trx_create();
392327
trx->mysql_thd= thd;
393328
trx_start_internal(trx);
394329
dberr_t ret= trx->read_only
395330
? DB_READ_ONLY
396-
: lock_table_for_trx(table_stats, trx, LOCK_X);
331+
: lock_table_for_trx(stats.table(), trx, LOCK_X);
397332
if (ret == DB_SUCCESS)
398-
ret= lock_table_for_trx(index_stats, trx, LOCK_X);
333+
ret= lock_table_for_trx(stats.index(), trx, LOCK_X);
399334

400335
row_mysql_lock_data_dictionary(trx);
401336

@@ -423,12 +358,9 @@ dict_stats_save_defrag_stats(
423358
else
424359
trx->rollback();
425360

426-
if (table_stats)
427-
dict_table_close(table_stats, thd, mdl_table);
428-
if (index_stats)
429-
dict_table_close(index_stats, thd, mdl_index);
430361
row_mysql_unlock_data_dictionary(trx);
431362
trx->free();
363+
stats.close();
432364

433365
return ret;
434366
}

storage/innobase/dict/dict0dict.cc

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -647,12 +647,9 @@ dict_acquire_mdl_shared(dict_table_t *table,
647647

648648
retry:
649649
ut_ad(!trylock == dict_sys.frozen());
650-
ut_ad(trylock || table->get_ref_count());
651650

652651
if (!table->is_readable() || table->corrupted)
653652
{
654-
if (!trylock)
655-
table->release();
656653
if (*mdl)
657654
{
658655
mdl_context->release_lock(*mdl);
@@ -664,10 +661,7 @@ dict_acquire_mdl_shared(dict_table_t *table,
664661
const table_id_t table_id{table->id};
665662

666663
if (!trylock)
667-
{
668-
table->release();
669664
dict_sys.unfreeze();
670-
}
671665

672666
{
673667
MDL_request request;
@@ -714,7 +708,8 @@ dict_acquire_mdl_shared(dict_table_t *table,
714708
return table;
715709
}
716710

717-
table->acquire();
711+
if (trylock)
712+
table->acquire();
718713

719714
if (!table->parse_name<true>(db_buf1, tbl_buf1, &db1_len, &tbl1_len))
720715
{
@@ -828,16 +823,29 @@ dict_table_t *dict_table_open_on_id(table_id_t table_id, bool dict_locked,
828823
dict_table_op_t table_op, THD *thd,
829824
MDL_ticket **mdl)
830825
{
826+
retry:
831827
if (!dict_locked)
832828
dict_sys.freeze(SRW_LOCK_CALL);
833829

834830
dict_table_t *table= dict_sys.find_table(table_id);
835831

836832
if (table)
837833
{
838-
table->acquire();
839-
if (thd && !dict_locked)
840-
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
834+
if (!dict_locked)
835+
{
836+
if (thd)
837+
{
838+
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
839+
if (table)
840+
goto acquire;
841+
}
842+
else
843+
acquire:
844+
table->acquire();
845+
dict_sys.unfreeze();
846+
}
847+
else
848+
table->acquire();
841849
}
842850
else if (table_op != DICT_TABLE_OP_OPEN_ONLY_IF_CACHED)
843851
{
@@ -850,24 +858,16 @@ dict_table_t *dict_table_open_on_id(table_id_t table_id, bool dict_locked,
850858
table_op == DICT_TABLE_OP_LOAD_TABLESPACE
851859
? DICT_ERR_IGNORE_RECOVER_LOCK
852860
: DICT_ERR_IGNORE_FK_NOKEY);
853-
if (table)
854-
table->acquire();
855861
if (!dict_locked)
856862
{
857863
dict_sys.unlock();
858-
if (table && thd)
859-
{
860-
dict_sys.freeze(SRW_LOCK_CALL);
861-
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
862-
dict_sys.unfreeze();
863-
}
864-
return table;
864+
if (table)
865+
goto retry;
865866
}
867+
else if (table)
868+
table->acquire();
866869
}
867870

868-
if (!dict_locked)
869-
dict_sys.unfreeze();
870-
871871
return table;
872872
}
873873

@@ -1084,6 +1084,55 @@ dict_table_open_on_name(
10841084
DBUG_RETURN(table);
10851085
}
10861086

1087+
bool dict_stats::open(THD *thd) noexcept
1088+
{
1089+
ut_ad(!mdl_table);
1090+
ut_ad(!mdl_index);
1091+
ut_ad(!table_stats);
1092+
ut_ad(!index_stats);
1093+
ut_ad(!mdl_context);
1094+
1095+
mdl_context= static_cast<MDL_context*>(thd_mdl_context(thd));
1096+
if (!mdl_context)
1097+
return true;
1098+
/* FIXME: use compatible type, and maybe remove this parameter altogether! */
1099+
const double timeout= double(global_system_variables.lock_wait_timeout);
1100+
MDL_request request;
1101+
MDL_REQUEST_INIT(&request, MDL_key::TABLE, "mysql", "innodb_table_stats",
1102+
MDL_SHARED, MDL_EXPLICIT);
1103+
if (UNIV_UNLIKELY(mdl_context->acquire_lock(&request, timeout)))
1104+
return true;
1105+
mdl_table= request.ticket;
1106+
MDL_REQUEST_INIT(&request, MDL_key::TABLE, "mysql", "innodb_index_stats",
1107+
MDL_SHARED, MDL_EXPLICIT);
1108+
if (UNIV_UNLIKELY(mdl_context->acquire_lock(&request, timeout)))
1109+
goto release_mdl;
1110+
mdl_index= request.ticket;
1111+
table_stats= dict_table_open_on_name("mysql/innodb_table_stats", false,
1112+
DICT_ERR_IGNORE_NONE);
1113+
if (!table_stats)
1114+
goto release_mdl;
1115+
index_stats= dict_table_open_on_name("mysql/innodb_index_stats", false,
1116+
DICT_ERR_IGNORE_NONE);
1117+
if (index_stats)
1118+
return false;
1119+
1120+
table_stats->release();
1121+
release_mdl:
1122+
if (mdl_index)
1123+
mdl_context->release_lock(mdl_index);
1124+
mdl_context->release_lock(mdl_table);
1125+
return true;
1126+
}
1127+
1128+
void dict_stats::close() noexcept
1129+
{
1130+
table_stats->release();
1131+
index_stats->release();
1132+
mdl_context->release_lock(mdl_table);
1133+
mdl_context->release_lock(mdl_index);
1134+
}
1135+
10871136
/**********************************************************************//**
10881137
Adds system columns to a table object. */
10891138
void

0 commit comments

Comments
 (0)