Commit 5740638c authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-31132 Deadlock between DDL and purge of InnoDB history

log_free_check(): Assert that the caller must not hold
exclusive lock_sys.latch. This was the case for calls from
ibuf_delete_for_discarded_space(). This caused a deadlock with
another thread that would be holding a latch on a dirty page
that would need to be written so that the checkpoint would advance
and log_free_check() could return. That other thread was waiting
for a shared lock_sys.latch.

fil_delete_tablespace(): Do not invoke ibuf_delete_for_discarded_space()
because in DDL operations, we will be holding exclusive lock_sys.latch.

trx_t::commit(std::vector<pfs_os_file_t>&), innodb_drop_database(),
row_purge_remove_clust_if_poss_low(), row_undo_ins_remove_clust_rec(),
row_discard_tablespace_for_mysql():
Invoke ibuf_delete_for_discarded_space() on the deleted tablespaces after
releasing all latches.
parent d4265fbd
...@@ -68,6 +68,7 @@ before transaction commit and must be rolled back explicitly are as follows: ...@@ -68,6 +68,7 @@ before transaction commit and must be rolled back explicitly are as follows:
#include "dict0defrag_bg.h" #include "dict0defrag_bg.h"
#include "btr0defragment.h" #include "btr0defragment.h"
#include "ibuf0ibuf.h"
#include "lock0lock.h" #include "lock0lock.h"
#include "que0que.h" #include "que0que.h"
...@@ -237,6 +238,8 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted) ...@@ -237,6 +238,8 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
commit_persist(); commit_persist();
if (dict_operation) if (dict_operation)
{ {
std::vector<uint32_t> space_ids;
space_ids.reserve(mod_tables.size());
ut_ad(dict_sys.locked()); ut_ad(dict_sys.locked());
lock_sys.wr_lock(SRW_LOCK_CALL); lock_sys.wr_lock(SRW_LOCK_CALL);
mutex_lock(); mutex_lock();
...@@ -271,6 +274,7 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted) ...@@ -271,6 +274,7 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
dict_sys.remove(table); dict_sys.remove(table);
if (const auto id= space ? space->id : 0) if (const auto id= space ? space->id : 0)
{ {
space_ids.emplace_back(uint32_t(id));
pfs_os_file_t d= fil_delete_tablespace(id); pfs_os_file_t d= fil_delete_tablespace(id);
if (d != OS_FILE_CLOSED) if (d != OS_FILE_CLOSED)
deleted.emplace_back(d); deleted.emplace_back(d);
...@@ -283,6 +287,9 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted) ...@@ -283,6 +287,9 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
mysql_mutex_lock(&lock_sys.wait_mutex); mysql_mutex_lock(&lock_sys.wait_mutex);
lock_sys.deadlock_check(); lock_sys.deadlock_check();
mysql_mutex_unlock(&lock_sys.wait_mutex); mysql_mutex_unlock(&lock_sys.wait_mutex);
for (const auto id : space_ids)
ibuf_delete_for_discarded_space(id);
} }
commit_cleanup(); commit_cleanup();
} }
...@@ -45,7 +45,6 @@ Created 10/25/1995 Heikki Tuuri ...@@ -45,7 +45,6 @@ Created 10/25/1995 Heikki Tuuri
#include "srv0start.h" #include "srv0start.h"
#include "trx0purge.h" #include "trx0purge.h"
#include "buf0lru.h" #include "buf0lru.h"
#include "ibuf0ibuf.h"
#include "buf0flu.h" #include "buf0flu.h"
#include "log.h" #include "log.h"
#ifdef __linux__ #ifdef __linux__
...@@ -1689,7 +1688,6 @@ pfs_os_file_t fil_delete_tablespace(ulint id) ...@@ -1689,7 +1688,6 @@ pfs_os_file_t fil_delete_tablespace(ulint id)
fil_space_free_low(space); fil_space_free_low(space);
} }
ibuf_delete_for_discarded_space(id);
return handle; return handle;
} }
......
...@@ -1542,6 +1542,7 @@ static void innodb_drop_database(handlerton*, char *path) ...@@ -1542,6 +1542,7 @@ static void innodb_drop_database(handlerton*, char *path)
dfield_set_data(&dfield, namebuf, len); dfield_set_data(&dfield, namebuf, len);
dict_index_copy_types(&tuple, sys_index, 1); dict_index_copy_types(&tuple, sys_index, 1);
std::vector<pfs_os_file_t> to_close; std::vector<pfs_os_file_t> to_close;
std::vector<uint32_t> space_ids;
mtr_t mtr; mtr_t mtr;
mtr.start(); mtr.start();
pcur.btr_cur.page_cur.index = sys_index; pcur.btr_cur.page_cur.index = sys_index;
...@@ -1585,6 +1586,7 @@ static void innodb_drop_database(handlerton*, char *path) ...@@ -1585,6 +1586,7 @@ static void innodb_drop_database(handlerton*, char *path)
ut_ad("corrupted SYS_TABLES.SPACE" == 0); ut_ad("corrupted SYS_TABLES.SPACE" == 0);
else if (uint32_t space_id= mach_read_from_4(s)) else if (uint32_t space_id= mach_read_from_4(s))
{ {
space_ids.emplace_back(space_id);
pfs_os_file_t detached= fil_delete_tablespace(space_id); pfs_os_file_t detached= fil_delete_tablespace(space_id);
if (detached != OS_FILE_CLOSED) if (detached != OS_FILE_CLOSED)
to_close.emplace_back(detached); to_close.emplace_back(detached);
...@@ -1594,6 +1596,9 @@ static void innodb_drop_database(handlerton*, char *path) ...@@ -1594,6 +1596,9 @@ static void innodb_drop_database(handlerton*, char *path)
mtr.commit(); mtr.commit();
for (pfs_os_file_t detached : to_close) for (pfs_os_file_t detached : to_close)
os_file_close(detached); os_file_close(detached);
for (const auto id : space_ids)
ibuf_delete_for_discarded_space(id);
/* Any changes must be persisted before we return. */ /* Any changes must be persisted before we return. */
log_write_up_to(mtr.commit_lsn(), true); log_write_up_to(mtr.commit_lsn(), true);
} }
......
...@@ -73,15 +73,10 @@ log_reserve_and_write_fast( ...@@ -73,15 +73,10 @@ log_reserve_and_write_fast(
const void* str, const void* str,
ulint len, ulint len,
lsn_t* start_lsn); lsn_t* start_lsn);
/***********************************************************************//** /** Wait for a log checkpoint if needed.
Checks if there is need for a log buffer flush or a new checkpoint, and does NOTE that this function may only be called while not holding
this if yes. Any database operation should call this when it has modified any synchronization objects except dict_sys.latch. */
more than about 4 pages. NOTE that this function may only be called when the void log_free_check();
OS thread owns no synchronization objects except dict_sys.latch. */
UNIV_INLINE
void
log_free_check(void);
/*================*/
/** Extends the log buffer. /** Extends the log buffer.
@param[in] len requested minimum size in bytes */ @param[in] len requested minimum size in bytes */
......
...@@ -289,23 +289,3 @@ log_reserve_and_write_fast( ...@@ -289,23 +289,3 @@ log_reserve_and_write_fast(
return lsn; return lsn;
} }
/***********************************************************************//**
Checks if there is need for a log buffer flush or a new checkpoint, and does
this if yes. Any database operation should call this when it has modified
more than about 4 pages. NOTE that this function may only be called when the
OS thread owns no synchronization objects except dict_sys.latch. */
UNIV_INLINE
void
log_free_check(void)
/*================*/
{
/* During row_log_table_apply(), this function will be called while we
are holding some latches. This is OK, as long as we are not holding
any latches on buffer blocks. */
if (log_sys.check_flush_or_checkpoint()) {
log_check_margins();
}
}
...@@ -1065,6 +1065,16 @@ ATTRIBUTE_COLD void log_check_margins() ...@@ -1065,6 +1065,16 @@ ATTRIBUTE_COLD void log_check_margins()
while (log_sys.check_flush_or_checkpoint()); while (log_sys.check_flush_or_checkpoint());
} }
/** Wait for a log checkpoint if needed.
NOTE that this function may only be called while not holding
any synchronization objects except dict_sys.latch. */
void log_free_check()
{
ut_ad(!lock_sys.is_writer());
if (log_sys.check_flush_or_checkpoint())
log_check_margins();
}
extern void buf_resize_shutdown(); extern void buf_resize_shutdown();
/** Make a checkpoint at the latest lsn on shutdown. */ /** Make a checkpoint at the latest lsn on shutdown. */
......
...@@ -2492,6 +2492,7 @@ dberr_t row_discard_tablespace_for_mysql(dict_table_t *table, trx_t *trx) ...@@ -2492,6 +2492,7 @@ dberr_t row_discard_tablespace_for_mysql(dict_table_t *table, trx_t *trx)
if (fts_exist) if (fts_exist)
purge_sys.resume_FTS(); purge_sys.resume_FTS();
ibuf_delete_for_discarded_space(space_id);
buf_flush_remove_pages(space_id); buf_flush_remove_pages(space_id);
trx->op_info= ""; trx->op_info= "";
return err; return err;
......
...@@ -162,8 +162,9 @@ row_purge_remove_clust_if_poss_low( ...@@ -162,8 +162,9 @@ row_purge_remove_clust_if_poss_low(
ut_ad("corrupted SYS_INDEXES record" == 0); ut_ad("corrupted SYS_INDEXES record" == 0);
} }
if (const uint32_t space_id = dict_drop_index_tree( const uint32_t space_id = dict_drop_index_tree(
&node->pcur, nullptr, &mtr)) { &node->pcur, nullptr, &mtr);
if (space_id) {
if (table) { if (table) {
if (table->get_ref_count() == 0) { if (table->get_ref_count() == 0) {
dict_sys.remove(table); dict_sys.remove(table);
...@@ -184,6 +185,10 @@ row_purge_remove_clust_if_poss_low( ...@@ -184,6 +185,10 @@ row_purge_remove_clust_if_poss_low(
table = nullptr; table = nullptr;
} }
if (space_id) {
ibuf_delete_for_discarded_space(space_id);
}
purge_sys.check_stop_SYS(); purge_sys.check_stop_SYS();
mtr.start(); mtr.start();
index->set_modified(mtr); index->set_modified(mtr);
......
...@@ -147,8 +147,9 @@ row_undo_ins_remove_clust_rec( ...@@ -147,8 +147,9 @@ row_undo_ins_remove_clust_rec(
pfs_os_file_t d = OS_FILE_CLOSED; pfs_os_file_t d = OS_FILE_CLOSED;
if (const uint32_t space_id = dict_drop_index_tree( const uint32_t space_id = dict_drop_index_tree(
&node->pcur, node->trx, &mtr)) { &node->pcur, node->trx, &mtr);
if (space_id) {
if (table) { if (table) {
lock_release_on_rollback(node->trx, lock_release_on_rollback(node->trx,
table); table);
...@@ -187,6 +188,10 @@ row_undo_ins_remove_clust_rec( ...@@ -187,6 +188,10 @@ row_undo_ins_remove_clust_rec(
os_file_close(d); os_file_close(d);
} }
if (space_id) {
ibuf_delete_for_discarded_space(space_id);
}
mtr.start(); mtr.start();
ut_a(node->pcur.restore_position( ut_a(node->pcur.restore_position(
BTR_MODIFY_LEAF, &mtr) == btr_pcur_t::SAME_ALL); BTR_MODIFY_LEAF, &mtr) == btr_pcur_t::SAME_ALL);
......
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