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.

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.050 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.