Skip to content

Commit c7e12e6

Browse files
committed
MDEV-29930 Lock order inversion in ibuf_remove_free_page()
The function ibuf_remove_free_page() was waiting for ibuf_mutex while holding ibuf.index->lock. This constitutes a lock order inversion and may cause InnoDB to hang when innodb_change_buffering is enabled and ibuf_merge_or_delete_for_page() is being executed concurrently. In fact, there is no need for ibuf_remove_free_page() to reacquire ibuf_mutex if we make ibuf.seg_size and ibuf.free_list_len protected by the ibuf.index root page latch rather than ibuf_mutex. ibuf.seg_size: Instead of ibuf_mutex, let the ibuf.index root page latch protect this field, just like ibuf.empty. ibuf.free_list_len: Document that modifications are protected by the ibuf.index root page latch. ibuf_init_at_db_start(): Acquire the root page latch before updating ibuf.seg_size. ibuf_data_enough_free_for_insert(), ibuf_data_too_much_free(): Note that ibuf.free_list_len is not fully protected. ibuf_remove_free_page(): Acquire the ibuf.index root page latch before accessing ibuf.free_list_len. Simplify the way how the root page latch is released and reacquired. Acquire and release ibuf_mutex only once. ibuf_print(): Acquire the ibuf.index root page latch before reading ibuf.free_list_len and ibuf.seg_size.
1 parent ef2f3d2 commit c7e12e6

File tree

2 files changed

+33
-25
lines changed

2 files changed

+33
-25
lines changed

storage/innobase/ibuf/ibuf0ibuf.cc

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,15 @@ ibuf_init_at_db_start(void)
414414
return err;
415415
}
416416

417+
if (buf_block_t* block =
418+
buf_page_get_gen(page_id_t(IBUF_SPACE_ID,
419+
FSP_IBUF_TREE_ROOT_PAGE_NO),
420+
0, RW_X_LATCH, nullptr, BUF_GET, &mtr, &err)) {
421+
root = buf_block_get_frame(block);
422+
} else {
423+
goto err_exit;
424+
}
425+
417426
fseg_n_reserved_pages(*header_page,
418427
IBUF_HEADER + IBUF_TREE_SEG_HEADER
419428
+ header_page->page.frame, &ibuf.seg_size, &mtr);
@@ -424,15 +433,6 @@ ibuf_init_at_db_start(void)
424433
ut_ad(ibuf.seg_size >= 2);
425434
} while (0);
426435

427-
if (buf_block_t* block =
428-
buf_page_get_gen(page_id_t(IBUF_SPACE_ID,
429-
FSP_IBUF_TREE_ROOT_PAGE_NO),
430-
0, RW_X_LATCH, nullptr, BUF_GET, &mtr, &err)) {
431-
root = buf_block_get_frame(block);
432-
} else {
433-
goto err_exit;
434-
}
435-
436436
DBUG_EXECUTE_IF("ibuf_init_corrupt",
437437
err = DB_CORRUPTION;
438438
goto err_exit;);
@@ -1748,6 +1748,7 @@ static inline bool ibuf_data_enough_free_for_insert()
17481748
inserts buffered for pages that we read to the buffer pool, without
17491749
any risk of running out of free space in the insert buffer. */
17501750

1751+
/* ibuf.free_list_len is NOT protected by the root page latch here */
17511752
return(ibuf.free_list_len >= (ibuf.size / 2) + 3 * ibuf.height);
17521753
}
17531754

@@ -1762,6 +1763,8 @@ ibuf_data_too_much_free(void)
17621763
{
17631764
mysql_mutex_assert_owner(&ibuf_mutex);
17641765

1766+
/* In ibuf_free_excess_pages(), ibuf.free_list_len is
1767+
NOT protected by the root page latch */
17651768
return(ibuf.free_list_len >= 3 + (ibuf.size / 2) + 3 * ibuf.height);
17661769
}
17671770

@@ -1876,7 +1879,7 @@ static dberr_t ibuf_remove_free_page(bool all = false)
18761879
mysql_mutex_lock(&ibuf_pessimistic_insert_mutex);
18771880
mysql_mutex_lock(&ibuf_mutex);
18781881

1879-
if (!header_page || (!all && !ibuf_data_too_much_free())) {
1882+
if (!header_page) {
18801883
early_exit:
18811884
mysql_mutex_unlock(&ibuf_mutex);
18821885
mysql_mutex_unlock(&ibuf_pessimistic_insert_mutex);
@@ -1892,7 +1895,10 @@ static dberr_t ibuf_remove_free_page(bool all = false)
18921895
goto early_exit;
18931896
}
18941897

1895-
const auto root_savepoint = mtr.get_savepoint() - 1;
1898+
if (!all && !ibuf_data_too_much_free()) {
1899+
goto early_exit;
1900+
}
1901+
18961902
const uint32_t page_no = flst_get_last(PAGE_HEADER
18971903
+ PAGE_BTR_IBUF_FREE_LIST
18981904
+ root->page.frame).page;
@@ -1910,7 +1916,6 @@ static dberr_t ibuf_remove_free_page(bool all = false)
19101916
because in fseg_free_page we access level 1 pages, and the root
19111917
is a level 2 page. */
19121918
root->page.lock.u_unlock();
1913-
mtr.lock_register(root_savepoint, MTR_MEMO_BUF_FIX);
19141919
ibuf_exit(&mtr);
19151920

19161921
/* Since pessimistic inserts were prevented, we know that the
@@ -1926,15 +1931,14 @@ static dberr_t ibuf_remove_free_page(bool all = false)
19261931
header_page + IBUF_HEADER + IBUF_TREE_SEG_HEADER,
19271932
fil_system.sys_space, page_no, &mtr);
19281933

1934+
root->page.lock.u_lock();
1935+
19291936
if (err != DB_SUCCESS) {
19301937
goto func_exit;
19311938
}
19321939

19331940
ibuf_enter(&mtr);
19341941

1935-
mysql_mutex_lock(&ibuf_mutex);
1936-
mtr.upgrade_buffer_fix(root_savepoint, RW_X_LATCH);
1937-
19381942
/* Remove the page from the free list and update the ibuf size data */
19391943
if (buf_block_t* block =
19401944
buf_page_get_gen(page_id, 0, RW_X_LATCH, nullptr, BUF_GET,
@@ -1954,8 +1958,6 @@ static dberr_t ibuf_remove_free_page(bool all = false)
19541958
}
19551959

19561960
func_exit:
1957-
mysql_mutex_unlock(&ibuf_mutex);
1958-
19591961
if (bitmap_page) {
19601962
/* Set the bit indicating that this page is no more an
19611963
ibuf tree page (level 2 page) */
@@ -4486,8 +4488,14 @@ ibuf_print(
44864488
}
44874489

44884490
const uint32_t size= ibuf.size;
4491+
mtr_t mtr;
4492+
std::ignore=
4493+
buf_page_get_gen(page_id_t{IBUF_SPACE_ID, FSP_IBUF_TREE_ROOT_PAGE_NO},
4494+
0, RW_S_LATCH, nullptr, BUF_GET, &mtr, nullptr);
44894495
const uint32_t free_list_len= ibuf.free_list_len;
44904496
const uint32_t seg_size= ibuf.seg_size;
4497+
mtr.commit();
4498+
44914499
mysql_mutex_unlock(&ibuf_mutex);
44924500

44934501
fprintf(file,

storage/innobase/include/ibuf0ibuf.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,15 @@ struct ibuf_t{
6868
ibuf index tree, in pages */
6969
uint32_t seg_size; /*!< allocated pages of the file
7070
segment containing ibuf header and
71-
tree */
72-
bool empty; /*!< Protected by the page
73-
latch of the root page of the
74-
insert buffer tree
75-
(FSP_IBUF_TREE_ROOT_PAGE_NO). true
76-
if and only if the insert
77-
buffer tree is empty. */
71+
tree; protected by the ibuf.index
72+
root page latch. */
73+
bool empty; /*!< whether the change buffer is
74+
empty; protected by the ibuf.index
75+
root page latch. */
7876
uint8_t height; /*!< tree height */
79-
uint32_t free_list_len; /*!< length of the free list */
77+
Atomic_counter<uint32_t> free_list_len; /*!< length of the free list;
78+
modified when holding
79+
the ibuf.index root page latch */
8080
dict_index_t* index; /*!< insert buffer index */
8181

8282
/** number of pages merged */

0 commit comments

Comments
 (0)