Skip to content

Commit bc540b8

Browse files
MDEV-23693 Failing assertion: my_atomic_load32_explicit(&lock->lock_word, MY_MEMORY_ORDER_RELAXED) == X_LOCK_DECR
InnoDB frees the block lock during buffer pool shrinking when other thread is yet to release the block lock. While shrinking the buffer pool, InnoDB allows the page to be freed unless it is buffer fixed. In some cases, InnoDB releases the latch after unfixing the block. Fix: ==== - InnoDB should unfix the block after releases the latch. - Add more assertion to check buffer fix while accessing the page. - Introduced block_hint structure to store buf_block_t pointer and allow accessing the buf_block_t pointer only by passing a functor. It returns original buf_block_t* pointer if it is valid or nullptr if the pointer become stale. - Replace buf_block_is_uncompressed() with buf_pool_t::is_block_pointer() This change is motivated by a change in mysql-5.7.32: mysql/mysql-server@46e60de Bug #31036301 ASSERTION FAILURE: SYNC0RW.IC:429:LOCK->LOCK_WORD
1 parent 6a614d6 commit bc540b8

File tree

16 files changed

+262
-165
lines changed

16 files changed

+262
-165
lines changed

storage/innobase/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ SET(INNOBASE_SOURCES
2828
btr/btr0scrub.cc
2929
btr/btr0sea.cc
3030
btr/btr0defragment.cc
31+
buf/buf0block_hint.cc
3132
buf/buf0buddy.cc
3233
buf/buf0buf.cc
3334
buf/buf0dblwr.cc

storage/innobase/btr/btr0bulk.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ PageBulk::latch()
695695
m_mtr.set_named_space(m_index->space);
696696
}
697697

698+
ut_ad(m_block->page.buf_fix_count);
699+
698700
/* In case the block is S-latched by page_cleaner. */
699701
if (!buf_page_optimistic_get(RW_X_LATCH, m_block, m_modify_clock,
700702
__FILE__, __LINE__, &m_mtr)) {
@@ -713,6 +715,8 @@ PageBulk::latch()
713715

714716
buf_block_buf_fix_dec(m_block);
715717

718+
ut_ad(m_block->page.buf_fix_count);
719+
716720
ut_ad(m_cur_rec > m_page && m_cur_rec < m_heap_top);
717721

718722
return (m_err);

storage/innobase/btr/btr0cur.cc

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ btr_cur_optimistic_latch_leaves(
416416
ulint mode;
417417
ulint left_page_no;
418418
ulint curr_page_no;
419+
ut_ad(block->page.buf_fix_count);
420+
ut_ad(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE);
419421

420422
switch (*latch_mode) {
421423
case BTR_SEARCH_LEAF:
@@ -427,20 +429,10 @@ btr_cur_optimistic_latch_leaves(
427429
mode = *latch_mode == BTR_SEARCH_PREV
428430
? RW_S_LATCH : RW_X_LATCH;
429431

430-
buf_page_mutex_enter(block);
431-
if (buf_block_get_state(block) != BUF_BLOCK_FILE_PAGE) {
432-
buf_page_mutex_exit(block);
433-
return(false);
434-
}
435-
/* pin the block not to be relocated */
436-
buf_block_buf_fix_inc(block, file, line);
437-
buf_page_mutex_exit(block);
438-
439432
rw_lock_s_lock(&block->lock);
440433
if (block->modify_clock != modify_clock) {
441434
rw_lock_s_unlock(&block->lock);
442-
443-
goto unpin_failed;
435+
return false;
444436
}
445437

446438
curr_page_no = block->page.id.page_no();
@@ -470,7 +462,7 @@ btr_cur_optimistic_latch_leaves(
470462
/* release the left block */
471463
btr_leaf_page_release(
472464
cursor->left_block, mode, mtr);
473-
goto unpin_failed;
465+
return false;
474466
}
475467
} else {
476468
cursor->left_block = NULL;
@@ -480,23 +472,28 @@ btr_cur_optimistic_latch_leaves(
480472
file, line, mtr)) {
481473
if (btr_page_get_prev(buf_block_get_frame(block))
482474
== left_page_no) {
483-
buf_block_buf_fix_dec(block);
475+
/* block was already buffer-fixed while
476+
entering the function and
477+
buf_page_optimistic_get() buffer-fixes
478+
it again. */
479+
ut_ad(2 <= block->page.buf_fix_count);
484480
*latch_mode = mode;
485481
return(true);
486482
} else {
487-
/* release the block */
483+
/* release the block and decrement of
484+
buf_fix_count which was incremented
485+
in buf_page_optimistic_get() */
488486
btr_leaf_page_release(block, mode, mtr);
489487
}
490488
}
491489

490+
ut_ad(block->page.buf_fix_count);
492491
/* release the left block */
493492
if (cursor->left_block != NULL) {
494493
btr_leaf_page_release(cursor->left_block,
495494
mode, mtr);
496495
}
497-
unpin_failed:
498-
/* unpin the block */
499-
buf_block_buf_fix_dec(block);
496+
500497
return(false);
501498

502499
default:
@@ -1066,12 +1063,7 @@ btr_cur_search_to_nth_level_func(
10661063
guess = NULL;
10671064
#else
10681065
info = btr_search_get_info(index);
1069-
1070-
if (!buf_pool_is_obsolete(info->withdraw_clock)) {
1071-
guess = info->root_guess;
1072-
} else {
1073-
guess = NULL;
1074-
}
1066+
guess = info->root_guess;
10751067

10761068
#ifdef BTR_CUR_HASH_ADAPT
10771069
rw_lock_t* const search_latch = btr_get_search_latch(index);
@@ -1509,10 +1501,7 @@ btr_cur_search_to_nth_level_func(
15091501
}
15101502

15111503
#ifdef BTR_CUR_ADAPT
1512-
if (block != guess) {
1513-
info->root_guess = block;
1514-
info->withdraw_clock = buf_withdraw_clock;
1515-
}
1504+
info->root_guess = block;
15161505
#endif
15171506
}
15181507

storage/innobase/btr/btr0pcur.cc

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,10 @@ btr_pcur_store_position(
165165
index, rec, &cursor->old_n_fields,
166166
&cursor->old_rec_buf, &cursor->buf_size);
167167

168-
cursor->block_when_stored = block;
168+
cursor->block_when_stored.store(block);
169169

170170
/* Function try to check if block is S/X latch. */
171171
cursor->modify_clock = buf_block_get_modify_clock(block);
172-
cursor->withdraw_clock = buf_withdraw_clock;
173172
}
174173

175174
/**************************************************************//**
@@ -199,6 +198,26 @@ btr_pcur_copy_stored_position(
199198
pcur_receive->old_n_fields = pcur_donate->old_n_fields;
200199
}
201200

201+
/** Structure acts as functor to do the latching of leaf pages.
202+
It returns true if latching of leaf pages succeeded and false
203+
otherwise. */
204+
struct optimistic_latch_leaves
205+
{
206+
btr_pcur_t *const cursor;
207+
ulint *latch_mode;
208+
mtr_t *const mtr;
209+
210+
optimistic_latch_leaves(btr_pcur_t *cursor, ulint *latch_mode, mtr_t *mtr)
211+
:cursor(cursor), latch_mode(latch_mode), mtr(mtr) {}
212+
213+
bool operator() (buf_block_t *hint) const
214+
{
215+
return hint && btr_cur_optimistic_latch_leaves(
216+
hint, cursor->modify_clock, latch_mode,
217+
btr_pcur_get_btr_cur(cursor), __FILE__, __LINE__, mtr);
218+
}
219+
};
220+
202221
/**************************************************************//**
203222
Restores the stored position of a persistent cursor bufferfixing the page and
204223
obtaining the specified latches. If the cursor position was saved when the
@@ -261,7 +280,7 @@ btr_pcur_restore_position_func(
261280
cursor->latch_mode =
262281
BTR_LATCH_MODE_WITHOUT_INTENTION(latch_mode);
263282
cursor->pos_state = BTR_PCUR_IS_POSITIONED;
264-
cursor->block_when_stored = btr_pcur_get_block(cursor);
283+
cursor->block_when_stored.clear();
265284

266285
return(FALSE);
267286
}
@@ -276,12 +295,9 @@ btr_pcur_restore_position_func(
276295
case BTR_MODIFY_PREV:
277296
/* Try optimistic restoration. */
278297

279-
if (!buf_pool_is_obsolete(cursor->withdraw_clock)
280-
&& btr_cur_optimistic_latch_leaves(
281-
cursor->block_when_stored, cursor->modify_clock,
282-
&latch_mode, btr_pcur_get_btr_cur(cursor),
283-
file, line, mtr)) {
284-
298+
if (cursor->block_when_stored.run_with_hint(
299+
optimistic_latch_leaves(cursor, &latch_mode,
300+
mtr))) {
285301
cursor->pos_state = BTR_PCUR_IS_POSITIONED;
286302
cursor->latch_mode = latch_mode;
287303

@@ -378,11 +394,10 @@ btr_pcur_restore_position_func(
378394
since the cursor can now be on a different page!
379395
But we can retain the value of old_rec */
380396

381-
cursor->block_when_stored = btr_pcur_get_block(cursor);
397+
cursor->block_when_stored.store(btr_pcur_get_block(cursor));
382398
cursor->modify_clock = buf_block_get_modify_clock(
383-
cursor->block_when_stored);
399+
cursor->block_when_stored.block());
384400
cursor->old_stored = true;
385-
cursor->withdraw_clock = buf_withdraw_clock;
386401

387402
mem_heap_free(heap);
388403

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*****************************************************************************
2+
3+
Copyright (c) 2020, Oracle and/or its affiliates. All Rights Reserved.
4+
Copyright (c) 2020, MariaDB Corporation.
5+
6+
This program is free software; you can redistribute it and/or modify it under
7+
the terms of the GNU General Public License, version 2.0, as published by the
8+
Free Software Foundation.
9+
10+
This program is also distributed with certain software (including but not
11+
limited to OpenSSL) that is licensed under separate terms, as designated in a
12+
particular file or component or in included license documentation. The authors
13+
of MySQL hereby grant you an additional permission to link the program and
14+
your derivative works with the separately licensed software that they have
15+
included with MySQL.
16+
17+
This program is distributed in the hope that it will be useful, but WITHOUT
18+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19+
FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
20+
for more details.
21+
22+
You should have received a copy of the GNU General Public License along with
23+
this program; if not, write to the Free Software Foundation, Inc.,
24+
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25+
26+
*****************************************************************************/
27+
28+
#include "buf0block_hint.h"
29+
namespace buf {
30+
31+
void Block_hint::buffer_fix_block_if_still_valid()
32+
{
33+
/* We need to check if m_block points to one of chunks. For this to be
34+
meaningful we need to prevent freeing memory while we check, and until we
35+
buffer-fix the block. For this purpose it is enough to latch any of the many
36+
latches taken by buf_resize().
37+
However, for buffer-fixing to be meaningful, the block has to contain a page
38+
(as opposed to being already empty, which might mean that buf_pool_resize()
39+
can proceed and free it once we free the s-latch), so we confirm that the
40+
block contains a page. However, it is not sufficient to check that this is
41+
just any page, because just after we check it could get freed, unless we
42+
have a latch which prevents this. This is tricky because page_hash latches
43+
are sharded by page_id and we don't know the page_id until we look into the
44+
block. To solve this chicken-and-egg problem somewhat, we latch the shard
45+
for the m_page_id and compare block->page.id to it - so if is equal then we
46+
can be reasonably sure that we have the correct latch.
47+
There is still a theoretical problem here, where other threads might try
48+
to modify the m_block->page.id while we are comparing it, but the chance of
49+
accidentally causing the old space_id == m_page_id.m_space and the new
50+
page_no == m_page_id.m_page_no is minimal as compilers emit a single 8-byte
51+
comparison instruction to compare both at the same time atomically, and f()
52+
will probably double-check the block->page.id again, anyway.
53+
Finally, assuming that we have correct hash bucket latched, we should check if
54+
the state of the block is BUF_BLOCK_FILE_PAGE before buffer-fixing the block,
55+
as otherwise we risk buffer-fixing and operating on a block, which is already
56+
meant to be freed. In particular, buf_LRU_free_page() first calls
57+
buf_LRU_block_remove_hashed() under hash bucket latch protection to change the
58+
state to BUF_BLOCK_REMOVE_HASH and then releases the latch. Later it calls
59+
buf_LRU_block_free_hashed_page() without any latch to change the state to
60+
BUF_BLOCK_MEMORY and reset the page's id, which means buf_resize() can free it
61+
regardless of our buffer-fixing. */
62+
if (m_block)
63+
{
64+
const buf_pool_t *const buf_pool= buf_pool_get(m_page_id);
65+
rw_lock_t *latch= buf_page_hash_lock_get(buf_pool, m_page_id);
66+
rw_lock_s_lock(latch);
67+
/* If not own buf_pool_mutex, page_hash can be changed. */
68+
latch= buf_page_hash_lock_s_confirm(latch, buf_pool, m_page_id);
69+
if (buf_pool->is_block_field(m_block) &&
70+
m_page_id == m_block->page.id &&
71+
buf_block_get_state(m_block) == BUF_BLOCK_FILE_PAGE)
72+
buf_block_buf_fix_inc(m_block, __FILE__, __LINE__);
73+
else
74+
clear();
75+
rw_lock_s_unlock(latch);
76+
}
77+
}
78+
} // namespace buf

0 commit comments

Comments
 (0)