Bug #121058 KILL QUERY during an in-place ALTER TABLE leaves a bulk-load page buffer fixed
Submitted: 5 Aug 12:33 Modified: 5 Aug 12:38
Reporter: George Ma (OCA) Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: InnoDB storage engine Severity:S3 (Non-critical)
Version:9.7.2 OS:Any
Assigned to: CPU Architecture:Any

[5 Aug 12:33] George Ma
Description:
On MySQL Server 9.7.2, Linux x86_64, debug build (GCC 13.3.1), a `KILL QUERY`
issued during an InnoDB in-place table rebuild can leave a `Page_load` with an
outstanding buffer fix. The debug server then aborts while destroying the DDL
Builder instead of returning `ER_QUERY_INTERRUPTED` to the client.

The issue is deterministic with Debug Sync; it is not a timing-only failure.

`Page_load::release()` intentionally increments `m_n_blocks_buf_fixed` before
committing its mini-transaction, because the page is expected to be re-pinned
soon. `Page_load::latch()` performs the matching decrement.

The failing path is:

```text
Btree_load::release()
  -> Page_load::release() increments the extra buffer fix
  -> Debug Sync waits
KILL QUERY
  -> Parallel_reader::worker() returns DB_INTERRUPTED after traversal
  -> Parallel_cursor::scan() returns the error without Builder cleanup
  -> Builder::~Builder() directly deletes Btree_load
  -> Page_load::~Page_load() asserts on the outstanding buffer fix
```

There is already cleanup for an error returned directly by
`Builder::add_row()`: `Parallel_cursor::bulk_inserter` iterates over all
Builders and calls `Builder::handle_error(err)`. That method correctly calls
`latch()`, `finish(err)`, and deletes the active `Btree_load`.

However, `DB_INTERRUPTED` detected by `Parallel_reader::worker()` after its
traversal callback bypasses that cleanup. `Parallel_reader::run()` returns the
error, `Parallel_cursor::scan()` returns it, and
`Loader::scan_and_build_indexes()` only calls `Cursor::finish(err)`, which is a
no-op for a non-FTS cursor.

How to repeat:
diff --git a/mysql-test/suite/innodb/t/builder_error_case_kill.test b/mysql-test/suite/innodb/t/builder_error_case_kill.test
new file mode 100644
index 00000000000..8caf0167b8c
--- /dev/null
+++ b/mysql-test/suite/innodb/t/builder_error_case_kill.test
@@ -0,0 +1,57 @@
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+
+# Interrupt a table rebuild after the clustered-index Btree_load has released
+# its page. The interrupted parallel reader must clean up every Builder.
+CREATE TABLE t1 (
+  id INT AUTO_INCREMENT PRIMARY KEY,
+  c1 INT,
+  KEY idx_1(c1)
+) ENGINE=InnoDB;
+
+--disable_query_log
+DELIMITER |;
+CREATE PROCEDURE populate_t1(IN BASE INT, IN SIZE INT)
+BEGIN
+  DECLARE i INT DEFAULT BASE;
+  WHILE (i + 10 <= SIZE) DO
+    INSERT INTO t1(c1) VALUES
+      (i), (i+1), (i+2), (i+3), (i+4),
+      (i+5), (i+6), (i+7), (i+8), (i+9);
+    SET i = i + 10;
+  END WHILE;
+END|
+DELIMITER ;|
+
+CALL populate_t1(0, 11000);
+DROP PROCEDURE populate_t1;
+--enable_query_log
+
+SELECT COUNT(*) FROM t1;
+
+connect (con1,localhost,root,,);
+connection con1;
+let $ID= `SELECT @id := CONNECTION_ID()`;
+SET DEBUG_SYNC=
+  'ddl_btree_load_released SIGNAL btree_load_released WAIT_FOR kill_ddl';
+--send ALTER TABLE t1 ADD COLUMN c2 INT DEFAULT 20, ALGORITHM=INPLACE;
+
+connection default;
+let $ignore= `SELECT @id := $ID`;
+SET DEBUG_SYNC='now WAIT_FOR btree_load_released';
+KILL QUERY @id;
+SET DEBUG_SYNC='now SIGNAL kill_ddl';
+
+connection con1;
+--error ER_QUERY_INTERRUPTED
+--reap
+SET DEBUG_SYNC='RESET';
+
+connection default;
+disconnect con1;
+
+# A debug build detects a buffer-fixed page during DDL teardown.
+--source include/shutdown_mysqld.inc
+--source include/start_mysqld.inc
+
+DROP TABLE t1;
diff --git a/storage/innobase/btr/btr0load.cc b/storage/innobase/btr/btr0load.cc
index 9cdd01463a4..79afb081526 100644
--- a/storage/innobase/btr/btr0load.cc
+++ b/storage/innobase/btr/btr0load.cc
@@ -1009,6 +1009,7 @@ Btree_load::~Btree_load() noexcept {
 void Btree_load::release() noexcept {
   ut_ad(m_n_recs > 0);
   m_page_loaders[0]->release();
+  DEBUG_SYNC_C("ddl_btree_load_released");
 }

 void Btree_load::latch() noexcept {
[5 Aug 12:37] George Ma
Test case

Attachment: ddl_121058.patch (application/octet-stream, text), 2.30 KiB.

[5 Aug 12:38] George Ma
I have reported the bug in https://bugs.mysql.com/bug.php?id=120046, and the developer said fixed in 8.4.11, 9.7.2, and 26.7.0.