Skip to content

Commit 0da0bf2

Browse files
MDEV-37294 segv in flst::remove_complete(buf_block_t*, unsigned short, unsigned char*, mtr_t*)
Problem: ======= During system tablespace defragmentation, extent movement occurs in two phases: prepare and complete. 1) prepare phase validates involved pages and acquires necessary resources. 2) complete phase performs the actual data copy. Prepare phase fails to check whether allocating a page will make the extent FULL. When an extent has exactly (extent_size - 1) pages used, the prepare phase returns early without latching the prev/next extent descriptors needed for list manipulation. Complete phase then allocates the final page, making the extent full, and attempts to move it from FSEG_NOT_FULL/FSP_FREE_FRAG to FSEG_FULL/FSP_FULL_FRAG list. This fails with an assertion because the required blocks were never latched, causing a crash in flst::remove_complete(). Solution: ======== alloc_from_fseg_prepare(), alloc_from_free_frag_prepare(): call these function only if the extent will be full after allocation. This makes the prepare phase to acquire the necessary pages for FSP list manipulation find_new_extents(): Print more revised information about moving of extent data and destination extent also. defragment_level(): Move get_child_pages(new_block) before committing changes to enable proper rollback on failure. Well, this failure is theoretically impossible (new block is exact copy of validated old block).
1 parent 39edbba commit 0da0bf2

File tree

1 file changed

+36
-28
lines changed

1 file changed

+36
-28
lines changed

storage/innobase/fsp/fsp0fsp.cc

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4610,13 +4610,6 @@ class PageOperator final
46104610
@return error code */
46114611
dberr_t alloc_from_fseg_prepare() noexcept
46124612
{
4613-
uint32_t n_used= xdes_get_n_used(m_new_descr);
4614-
if (n_used < 1 || n_used >= m_extent_size)
4615-
return DB_CORRUPTION;
4616-
4617-
if (n_used < m_extent_size)
4618-
return DB_SUCCESS;
4619-
46204613
byte *lst= m_iblock->page.frame + uint16_t(m_ioffset + FSEG_NOT_FULL);
46214614
if (!mach_read_from_4(lst + FLST_LEN))
46224615
return DB_CORRUPTION;
@@ -4683,13 +4676,6 @@ class PageOperator final
46834676
@return error code */
46844677
dberr_t alloc_from_free_frag_prepare() noexcept
46854678
{
4686-
uint32_t n_used= xdes_get_n_used(m_new_descr);
4687-
if (n_used < 1 || n_used >= m_extent_size)
4688-
return DB_CORRUPTION;
4689-
4690-
if (n_used < m_extent_size)
4691-
return DB_SUCCESS;
4692-
46934679
byte *lst= m_header_block->page.frame + FSP_HEADER_OFFSET + FSP_FREE_FRAG;
46944680
if (!mach_read_from_4(lst + FLST_LEN))
46954681
return DB_CORRUPTION;
@@ -5085,14 +5071,15 @@ class PageOperator final
50855071
if (n_used == 0 || n_used >= m_extent_size)
50865072
return DB_CORRUPTION;
50875073

5088-
/* Allocate the page from file segment */
5089-
if (m_seg_id != FIL_NULL && m_new_state == XDES_FSEG &&
5090-
mach_read_from_8(m_new_descr + XDES_ID) == m_seg_id)
5074+
if (n_used != m_extent_size - 1);
5075+
/* After allocating the page from extent, it doesn't get
5076+
full. There will be no change in other pages */
5077+
else if (m_new_state == XDES_FSEG && m_seg_id != FIL_NULL &&
5078+
mach_read_from_8(m_new_descr + XDES_ID) == m_seg_id)
50915079
err= alloc_from_fseg_prepare();
5092-
/* Allocate the page from free frag */
50935080
else if (m_new_state == XDES_FREE_FRAG || m_new_state == XDES_FULL_FRAG)
50945081
err= alloc_from_free_frag_prepare();
5095-
else return DB_CORRUPTION;
5082+
else err= DB_CORRUPTION;
50965083

50975084
if (err) return err;
50985085
goto new_page;
@@ -5376,10 +5363,30 @@ class SpaceDefragmenter final
53765363

53775364
sql_print_information("InnoDB: System tablespace defragmentation "
53785365
"process starts");
5379-
sql_print_information("InnoDB: Moving the data from extents %"
5380-
PRIu32 " through %" PRIu32,
5381-
m_extent_map.begin()->first,
5382-
m_extent_map.rbegin()->first);
5366+
5367+
if (m_extent_map.size() == 1)
5368+
{
5369+
auto it= m_extent_map.begin();
5370+
sql_print_information("InnoDB: Moving the data from extent "
5371+
"%" PRIu32 " to extent %" PRIu32, it->first,
5372+
it->second);
5373+
}
5374+
else
5375+
{
5376+
sql_print_information("InnoDB: Moving the data from extents "
5377+
"%" PRIu32 " through %" PRIu32,
5378+
m_extent_map.begin()->first,
5379+
m_extent_map.rbegin()->first);
5380+
5381+
uint32_t min_dest= UINT32_MAX, max_dest= 0;
5382+
for (const auto &entry : m_extent_map)
5383+
{
5384+
min_dest= std::min(min_dest, entry.second);
5385+
max_dest= std::max(max_dest, entry.second);
5386+
}
5387+
sql_print_information("InnoDB: Destination extent range: "
5388+
"%" PRIu32 " through %" PRIu32, min_dest, max_dest);
5389+
}
53835390
return DB_SUCCESS;
53845391
}
53855392

@@ -5626,6 +5633,12 @@ dberr_t IndexDefragmenter::defragment_level(
56265633
block->page.frame + FIL_PAGE_TYPE,
56275634
srv_page_size - FIL_PAGE_TYPE - 8);
56285635

5636+
if (level)
5637+
{
5638+
err= get_child_pages(new_block);
5639+
if (err) goto err_exit;
5640+
}
5641+
56295642
/* Assign the new block page number in left, right
56305643
and parent block */
56315644
related_pages.complete(new_page_no, parent_offset);
@@ -5635,11 +5648,6 @@ dberr_t IndexDefragmenter::defragment_level(
56355648
/* Add the new page in inode fragment array */
56365649
operation.assign_frag_slot();
56375650

5638-
if (level)
5639-
{
5640-
err= get_child_pages(new_block);
5641-
if (err) return err;
5642-
}
56435651
goto fetch_next_page;
56445652
}
56455653

0 commit comments

Comments
 (0)