Skip to content
Permalink
Browse files
MDEV-29883 Deadlock between InnoDB statistics update and BLOB insert
The test innodb.innodb-wl5522-debug would occasionally hang
(especially when run with ./mtr --rr) due to a deadlock between
btr_store_big_rec_extern_fields() and dict_stats_analyze_index().
The two threads would acquire the clustered index root page latch and
the tablespace latch in the opposite order. The deadlock was possible
because dict_stats_analyze_index() was holding the index latch in
shared mode and an index root page latch, while waiting for the
tablespace latch. If a stronger dict_index_t::lock had been held
by dict_stats_analyze_index(), any operations that free or allocate
index pages would have been blocked.

In each caller of fseg_n_reserved_pages() except ibuf_init_at_db_start()
which is a special case for ibuf.index at database startup, we must hold
an index latch that prevents concurrent allocation or freeing of index
pages.

Any operation that allocates or free pages that belong to an index tree
must first acquire an index latch in Update or Exclusive mode, and while
holding that, acquire an index root page latch in Update or Exclusive
mode.

dict_index_t::clear(): Also acquire an index latch. Otherwise,
the test innodb.insert_into_empty could hang.

btr_get_size_and_reserved(): Assert that a strong enough index latch
is being held. Only acquire a shared fil_space_t::latch; we are only
reading, not modifying any data.

dict_stats_update_transient_for_index(),
dict_stats_analyze_index(): Acquire a strong enough index latch. Only
acquire a shared fil_space_t::latch.

These operations had followed the same order of acquiring latches in
every InnoDB version since the very beginning
(commit c533308).
The calls for acquiring tablespace latch had previously been moved in
commit 8783925 and
commit 1e9c922.

The hang was introduced in
commit 2e814d4 which imported
mysql/mysql-server@ac74632
which failed to strengthen the locking requirements of the function
btr_get_size().
  • Loading branch information
dr-m committed Oct 26, 2022
1 parent 78a04a4 commit 8b6a308
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 8 deletions.
@@ -1102,6 +1102,7 @@ dberr_t dict_index_t::clear(que_thr_t *thr)
mtr.set_log_mode(MTR_LOG_NO_REDO);
else
set_modified(mtr);
mtr_sx_lock_index(this, &mtr);

dberr_t err;
if (buf_block_t *root_block=
@@ -297,7 +297,7 @@ btr_get_size_and_reserved(
{
ulint dummy;

ut_ad(mtr->memo_contains(index->lock, MTR_MEMO_S_LOCK));
ut_ad(mtr->memo_contains(index->lock, MTR_MEMO_SX_LOCK));
ut_a(flag == BTR_N_LEAF_PAGES || flag == BTR_TOTAL_SIZE);

if (index->page == FIL_NULL
@@ -314,7 +314,7 @@ btr_get_size_and_reserved(
return ULINT_UNDEFINED;
}

mtr->x_lock_space(index->table->space);
mtr->s_lock_space(index->table->space);

ulint n = fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF
+ root->page.frame, used, mtr);
@@ -345,7 +345,7 @@ dict_stats_save_defrag_stats(
mtr_t mtr;
ulint n_leaf_pages;
mtr.start();
mtr_s_lock_index(index, &mtr);
mtr_sx_lock_index(index, &mtr);
ulint n_leaf_reserved= btr_get_size_and_reserved(index, BTR_N_LEAF_PAGES,
&n_leaf_pages, &mtr);
mtr.commit();
@@ -1418,7 +1418,8 @@ dict_stats_update_transient_for_index(
mtr_t mtr;

mtr.start();
mtr_s_lock_index(index, &mtr);
mtr_sx_lock_index(index, &mtr);

dberr_t err;
buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH,
&mtr, &err);
@@ -1434,7 +1435,7 @@ dict_stats_update_transient_for_index(
goto invalid;
}

mtr.x_lock_space(index->table->space);
mtr.s_lock_space(index->table->space);

ulint dummy, size;
index->stat_index_size
@@ -2559,9 +2560,9 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
DEBUG_PRINTF(" %s(index=%s)\n", __func__, index->name());

mtr.start();
mtr_s_lock_index(index, &mtr);
mtr_sx_lock_index(index, &mtr);
dberr_t err;
buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr, &err);
buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr, &err);
if (!root) {
empty_index:
mtr.commit();
@@ -2570,7 +2571,7 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
}

uint16_t root_level = btr_page_get_level(root->page.frame);
mtr.x_lock_space(index->table->space);
mtr.s_lock_space(index->table->space);
ulint dummy, size;
result.index_size
= fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF

0 comments on commit 8b6a308

Please sign in to comment.