From 976978687e56ecd56d1a52ec1a62f0fc07032a56 Mon Sep 17 00:00:00 2001
From: Alessandro Vetere <iminelink@gmail.com>
Date: Tue, 14 Apr 2026 15:38:10 +0200
Subject: [PATCH] MDEV-37070 Expose AHI disable bug

---
 .../innodb_buffer_pool_resize_ahi_race.result | 27 ++++++++
 .../t/innodb_buffer_pool_resize_ahi_race.opt  |  1 +
 .../t/innodb_buffer_pool_resize_ahi_race.test | 63 +++++++++++++++++++
 storage/innobase/buf/buf0buf.cc               |  1 +
 4 files changed, 92 insertions(+)
 create mode 100644 mysql-test/suite/innodb/r/innodb_buffer_pool_resize_ahi_race.result
 create mode 100644 mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.opt
 create mode 100644 mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.test

diff --git a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_ahi_race.result b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_ahi_race.result
new file mode 100644
index 00000000000..c06474d0049
--- /dev/null
+++ b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_ahi_race.result
@@ -0,0 +1,27 @@
+SET @save_size= @@GLOBAL.innodb_buffer_pool_size;
+SET @save_ahi= @@GLOBAL.innodb_adaptive_hash_index;
+SET GLOBAL innodb_adaptive_hash_index= ON;
+SET GLOBAL innodb_buffer_pool_size= 16777216;
+SET GLOBAL innodb_max_purge_lag_wait= 0;
+SET @save_pct= @@GLOBAL.innodb_max_dirty_pages_pct;
+SET @save_pct_lwm= @@GLOBAL.innodb_max_dirty_pages_pct_lwm;
+SET GLOBAL innodb_max_dirty_pages_pct_lwm= 0.0;
+SET GLOBAL innodb_max_dirty_pages_pct= 0.0;
+connect con1,localhost,root;
+SET DEBUG_SYNC='buf_pool_resize_before_ahi_reenable SIGNAL shrink_done WAIT_FOR continue_resize';
+SET GLOBAL innodb_buffer_pool_size= 8388608;
+connection default;
+SET DEBUG_SYNC= 'now WAIT_FOR shrink_done';
+SET GLOBAL innodb_adaptive_hash_index= OFF;
+SET DEBUG_SYNC= 'now SIGNAL continue_resize';
+connection con1;
+disconnect con1;
+connection default;
+SELECT @@GLOBAL.innodb_adaptive_hash_index;
+@@GLOBAL.innodb_adaptive_hash_index
+1
+SET DEBUG_SYNC= RESET;
+SET GLOBAL innodb_max_dirty_pages_pct= @save_pct;
+SET GLOBAL innodb_max_dirty_pages_pct_lwm= @save_pct_lwm;
+SET GLOBAL innodb_adaptive_hash_index= @save_ahi;
+SET GLOBAL innodb_buffer_pool_size= @save_size;
diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.opt b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.opt
new file mode 100644
index 00000000000..95f86e59920
--- /dev/null
+++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.opt
@@ -0,0 +1 @@
+--innodb-buffer-pool-size-max=16m
diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.test b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.test
new file mode 100644
index 00000000000..a137d60171c
--- /dev/null
+++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_ahi_race.test
@@ -0,0 +1,63 @@
+--source include/have_innodb.inc
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+
+#
+# Test race condition: user disabling AHI while buffer pool shrink is
+# completing should be respected.
+#
+# Sequence:
+#  1. Thread 1 (resize): disables AHI, shrinks buffer pool successfully
+#  2. Thread 2 (user):   SET GLOBAL innodb_adaptive_hash_index = OFF
+#  3. Thread 1 (resize): re-enables AHI unconditionally (ignoring user)
+#
+# Bug: resize captures ahi_disabled=true before the shrink and calls
+# btr_search.enable(true) after it completes. If the user concurrently
+# sets AHI=OFF, btr_search.disable() is a no-op (already disabled by
+# resize), but enable(true) still fires, overriding the user's choice.
+#
+
+SET @save_size= @@GLOBAL.innodb_buffer_pool_size;
+SET @save_ahi= @@GLOBAL.innodb_adaptive_hash_index;
+
+SET GLOBAL innodb_adaptive_hash_index= ON;
+SET GLOBAL innodb_buffer_pool_size= 16777216;
+
+# Flush dirty pages so the shrink can succeed
+SET GLOBAL innodb_max_purge_lag_wait= 0;
+SET @save_pct= @@GLOBAL.innodb_max_dirty_pages_pct;
+SET @save_pct_lwm= @@GLOBAL.innodb_max_dirty_pages_pct_lwm;
+SET GLOBAL innodb_max_dirty_pages_pct_lwm= 0.0;
+SET GLOBAL innodb_max_dirty_pages_pct= 0.0;
+
+# Thread 1: begin buffer pool shrink; pause just before AHI re-enable
+connect con1,localhost,root;
+SET DEBUG_SYNC='buf_pool_resize_before_ahi_reenable SIGNAL shrink_done WAIT_FOR continue_resize';
+send SET GLOBAL innodb_buffer_pool_size= 8388608;
+
+connection default;
+SET DEBUG_SYNC= 'now WAIT_FOR shrink_done';
+
+# Thread 2: user explicitly disables AHI while resize is in progress.
+# At this point AHI is already internally disabled by the resize thread,
+# so btr_search.disable() is a no-op — but the user's intent is OFF.
+SET GLOBAL innodb_adaptive_hash_index= OFF;
+
+# Let resize thread continue — it will call btr_search.enable(true)
+SET DEBUG_SYNC= 'now SIGNAL continue_resize';
+
+connection con1;
+reap;
+disconnect con1;
+
+connection default;
+# Bug: the user set AHI to OFF, but resize re-enabled it.
+# Expected (after fix): 0  (OFF)
+# Current  (bug):       1  (ON)
+SELECT @@GLOBAL.innodb_adaptive_hash_index;
+
+SET DEBUG_SYNC= RESET;
+SET GLOBAL innodb_max_dirty_pages_pct= @save_pct;
+SET GLOBAL innodb_max_dirty_pages_pct_lwm= @save_pct_lwm;
+SET GLOBAL innodb_adaptive_hash_index= @save_ahi;
+SET GLOBAL innodb_buffer_pool_size= @save_size;
diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc
index ed078d91bed..83c367c4c3d 100644
--- a/storage/innobase/buf/buf0buf.cc
+++ b/storage/innobase/buf/buf0buf.cc
@@ -2068,6 +2068,7 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept
     }
 
 #ifdef BTR_CUR_HASH_ADAPT
+    DEBUG_SYNC_C("buf_pool_resize_before_ahi_reenable");
     if (ahi_disabled)
       btr_search.enable(true);
 #endif
-- 
2.53.0

