Skip to content

Commit 8c944fc

Browse files
ram1048Marcin Babij
authored andcommitted
Bug#35037114 Server crash in old_index != nullptr at my_server_abort
Issue: When clearing AHI index from all pages of the buffer pool, the block state may change to BUF_BLOCK_MEMORY before acquiring the block mutex. In these cases the AHI index will be null, and we must skip this block and go to the next instead of trying to make it null using btr_search_set_block_not_cached function. Fix: Continue to next block after asserting that AHI index is null for blocks in other state (state other than BUF_BLOCK_FILE_PAGE and BUF_BLOCK_REMOVE_HASH). Background: As indicated by AHI index's documentation, index may be modified to nullptr if btr_search_enabled is false and block is held in private in BUF_BLOCK_REMOVE_HASH state in buf_LRU_free_page(). This is what has happended in this scenario. Change-Id: Ib0d07b1b1bf13c4cb8872bc06b97e95d86b74c40
1 parent 80accb4 commit 8c944fc

File tree

4 files changed

+140
-0
lines changed

4 files changed

+140
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
CREATE TABLE t1 (
2+
id INT PRIMARY KEY
3+
);
4+
INSERT INTO t1 (id) VALUES (1);
5+
INSERT INTO t1 (id) SELECT (SELECT MAX(id) FROM t1)+id FROM t1;
6+
INSERT INTO t1 (id) SELECT (SELECT MAX(id) FROM t1)+id FROM t1;
7+
INSERT INTO t1 (id) SELECT (SELECT MAX(id) FROM t1)+id FROM t1;
8+
INSERT INTO t1 (id) SELECT (SELECT MAX(id) FROM t1)+id FROM t1;
9+
CREATE TABLE t2 (
10+
id INT PRIMARY KEY,
11+
c1 CHAR(250) NOT NULL DEFAULT 'a',
12+
c2 CHAR(250) NOT NULL DEFAULT 'a',
13+
c3 CHAR(250) NOT NULL DEFAULT 'a',
14+
c4 CHAR(250) NOT NULL DEFAULT 'a',
15+
c5 CHAR(250) NOT NULL DEFAULT 'a',
16+
c6 CHAR(250) NOT NULL DEFAULT 'a',
17+
c7 CHAR(250) NOT NULL DEFAULT 'a',
18+
c8 CHAR(250) NOT NULL DEFAULT 'a',
19+
c9 CHAR(250) NOT NULL DEFAULT 'a'
20+
);
21+
INSERT INTO t2 (id) SELECT id FROM t1;
22+
# restart: --innodb_page_size=4k --innodb_buffer_pool_chunk_size=1048576 --innodb-buffer-pool-instances=1 --innodb-buffer-pool-size=6M
23+
# Populate the buffer pool with pages
24+
SELECT COUNT(*) FROM t2;
25+
COUNT(*)
26+
16
27+
# Connect now before updating the global system variable
28+
SET DEBUG_SYNC='buf_pool_clear_hash_index_will_process_block SIGNAL found_block WAIT_FOR process_block';
29+
SET GLOBAL DEBUG="+d,buf_pool_clear_hash_index_check_other_blocks";
30+
SET GLOBAL innodb_adaptive_hash_index=OFF;
31+
SET DEBUG_SYNC='now WAIT_FOR found_block';
32+
BEGIN;
33+
SELECT COUNT(*) FROM t2 FOR SHARE;
34+
COUNT(*)
35+
16
36+
# Wait for sometime to allow conn2 to bring new pages to the buffer pool
37+
SET DEBUG_SYNC='now SIGNAL process_block';
38+
COMMIT;
39+
SET GLOBAL DEBUG="-d,buf_pool_clear_hash_index_check_other_blocks";
40+
# restart:
41+
DROP TABLE t1;
42+
DROP TABLE t2;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--initialize --innodb_page_size=4k
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
--source include/have_debug.inc
2+
--source include/have_debug_sync.inc
3+
4+
CREATE TABLE t1 (
5+
id INT PRIMARY KEY
6+
);
7+
INSERT INTO t1 (id) VALUES (1);
8+
9+
--let $i=0
10+
--let $k=4
11+
while($i < $k){
12+
INSERT INTO t1 (id) SELECT (SELECT MAX(id) FROM t1)+id FROM t1;
13+
--inc $i
14+
}
15+
16+
CREATE TABLE t2 (
17+
id INT PRIMARY KEY,
18+
c1 CHAR(250) NOT NULL DEFAULT 'a',
19+
c2 CHAR(250) NOT NULL DEFAULT 'a',
20+
c3 CHAR(250) NOT NULL DEFAULT 'a',
21+
c4 CHAR(250) NOT NULL DEFAULT 'a',
22+
c5 CHAR(250) NOT NULL DEFAULT 'a',
23+
c6 CHAR(250) NOT NULL DEFAULT 'a',
24+
c7 CHAR(250) NOT NULL DEFAULT 'a',
25+
c8 CHAR(250) NOT NULL DEFAULT 'a',
26+
c9 CHAR(250) NOT NULL DEFAULT 'a'
27+
);
28+
INSERT INTO t2 (id) SELECT id FROM t1;
29+
30+
--let $restart_parameters=restart: --innodb_page_size=4k --innodb_buffer_pool_chunk_size=1048576 --innodb-buffer-pool-instances=1 --innodb-buffer-pool-size=6M
31+
--source include/restart_mysqld.inc
32+
33+
--echo # Populate the buffer pool with pages
34+
SELECT COUNT(*) FROM t2;
35+
36+
--echo # Connect now before updating the global system variable
37+
--connect (conn2,localhost,root,,)
38+
39+
# This is required here because of the `global_system_variables`
40+
# object which is protected using `LOCK_global_system_variables`
41+
# mutex. Ongoing update of the global variable will hold the
42+
# mutex until completion - making the new sessions wait for it
43+
44+
# The code paths where the mutex is acquired:
45+
# 1. A new session is started:
46+
# New session's THD::init (via plugin_thdvar_init) reads object
47+
# 2. A global variable is updated:
48+
# sys_var::update updates the object's members
49+
50+
--connect (conn1,localhost,root,,)
51+
SET DEBUG_SYNC='buf_pool_clear_hash_index_will_process_block SIGNAL found_block WAIT_FOR process_block';
52+
SET GLOBAL DEBUG="+d,buf_pool_clear_hash_index_check_other_blocks";
53+
--send SET GLOBAL innodb_adaptive_hash_index=OFF
54+
55+
--connection conn2
56+
SET DEBUG_SYNC='now WAIT_FOR found_block';
57+
BEGIN;
58+
SELECT COUNT(*) FROM t2 FOR SHARE;
59+
60+
--connection default
61+
--echo # Wait for sometime to allow conn2 to bring new pages to the buffer pool
62+
--sleep 5
63+
SET DEBUG_SYNC='now SIGNAL process_block';
64+
65+
--connection conn1
66+
--reap
67+
68+
--connection conn2
69+
COMMIT;
70+
71+
--connection default
72+
SET GLOBAL DEBUG="-d,buf_pool_clear_hash_index_check_other_blocks";
73+
74+
--let $restart_parameters=restart:
75+
--source include/restart_mysqld.inc
76+
77+
DROP TABLE t1;
78+
DROP TABLE t2;

storage/innobase/buf/buf0buf.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,6 +2652,9 @@ void buf_pool_clear_hash_index(void) {
26522652

26532653
DEBUG_SYNC_C("purge_wait_for_btr_search_latch");
26542654

2655+
bool pause_before_processing = DBUG_EVALUATE_IF(
2656+
"buf_pool_clear_hash_index_check_other_blocks", true, false);
2657+
26552658
for (ulong p = 0; p < srv_buf_pool_instances; p++) {
26562659
buf_pool_t *const buf_pool = buf_pool_from_array(p);
26572660
buf_chunk_t *const chunks = buf_pool->chunks;
@@ -2673,6 +2676,13 @@ void buf_pool_clear_hash_index(void) {
26732676
continue;
26742677
}
26752678

2679+
/* Identify target block and sync with test */
2680+
if (pause_before_processing && block->page.old &&
2681+
!block->page.is_dirty()) {
2682+
DEBUG_SYNC_C("buf_pool_clear_hash_index_will_process_block");
2683+
pause_before_processing = false;
2684+
}
2685+
26762686
/* This latch will prevent block state transitions. It is important for
26772687
us to not change blocks that are kept in private in
26782688
BUF_BLOCK_REMOVE_HASH state by some concurrently executed
@@ -2706,6 +2716,8 @@ void buf_pool_clear_hash_index(void) {
27062716
/* No other state should have AHI */
27072717
ut_ad(block->ahi.index == nullptr);
27082718
ut_ad(block->ahi.n_pointers == 0);
2719+
/* Go to next block as AHI is already nullptr */
2720+
continue;
27092721
}
27102722

27112723
#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG
@@ -2718,6 +2730,13 @@ void buf_pool_clear_hash_index(void) {
27182730
}
27192731
}
27202732
}
2733+
2734+
/* Main intent was to identify target block. Due to rare race conditions, such
2735+
block is not found. To prevent timeout, unblock in case target block is not
2736+
found */
2737+
if (pause_before_processing) {
2738+
DEBUG_SYNC_C("buf_pool_clear_hash_index_will_process_block");
2739+
}
27212740
}
27222741

27232742
/** Relocate a buffer control block. Relocates the block on the LRU list

0 commit comments

Comments
 (0)