Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-40925

[Draft] corrupted double-linked list in mem_heap_create_block_func

    XMLWordPrintable

Details

    Description

      Note: The failure was observed during tests on bb-main-atomic (MDEV-25292), however according to Claude it is unlikely to be specific to the feature branch, so I'm putting it into the backlog

      bb-main-atomic 2c0f583a065bdfcaf20b451922fb6caeadd72aa4

      2026-08-20  8:05:34 18 [Note] InnoDB: Cannot close file ./mysql/innodb_table_stats.ibd because of pending fsync
      corrupted double-linked list
      260820  8:06:04 [ERROR] /server/sql/mysqld got signal 6 ;
       
      #10 0x00007f11e4eadc4e in malloc () from /lib/x86_64-linux-gnu/libc.so.6
      #11 0x00005607de3055f7 in ut_allocator<unsigned char, true>::allocate (this=<optimized out>, n_elements=8096, autoevent_idx=38, set_to_zero=false, throw_on_error=false) at /server/storage/innobase/include/ut0new.h:376
      #12 mem_heap_create_block_func (heap=heap@entry=0x7711c0000c68, n=<optimized out>, type=0) at /server/storage/innobase/mem/mem0mem.cc:276
      #13 0x00005607de3057a2 in mem_heap_add_block (heap=0x7711c0000c68, n=n@entry=520) at /server/storage/innobase/mem/mem0mem.cc:362
      #14 0x00005607de4083a2 in mem_heap_alloc (n=520, heap=<optimized out>) at /server/storage/innobase/include/mem0mem.inl:179
      #15 mtr_buf_t::add_block (this=0x7f11e0db5998) at /server/storage/innobase/include/dyn0buf.h:323
      #16 mtr_buf_t::push<unsigned char*> (this=0x7f11e0db5998, size=33) at /server/storage/innobase/include/dyn0buf.h:234
      #17 mtr_buf_t::push (this=0x7f11e0db5998, ptr=0x77116cef1fb8 "./partition_db/thash_8_8#P#p8.ibd", len=33) at /server/storage/innobase/include/dyn0buf.h:250
      #18 mtr_t::log_file_op (this=this@entry=0x7f11e0db5868, type=type@entry=FILE_MODIFY, space_id=<optimized out>, path=path@entry=0x77116cef1fb8 "./partition_db/thash_8_8#P#p8.ibd", new_path=new_path@entry=0x0) at /server/storage/innobase/fil/fil0fil.cc:1616
      #19 0x00005607ddadf740 in fil_names_clear (lsn=lsn@entry=31307280) at /server/storage/innobase/fil/fil0fil.cc:3193
      #20 0x00005607ddad7c12 in log_checkpoint_low (oldest_lsn=oldest_lsn@entry=31307280, end_lsn=end_lsn@entry=49357253) at /server/storage/innobase/buf/buf0flu.cc:2239
      #21 0x00005607de3d3cd8 in log_checkpoint () at /server/storage/innobase/buf/buf0flu.cc:2314
      #22 buf_flush_page_cleaner () at /server/storage/innobase/buf/buf0flu.cc:2875
      

      Claude's pondering (quoted without verification):

      ## Verdict
       
      The crash signature (glibc `corrupted double-linked list`, signal 6, in the
      background `buf_flush_page_cleaner` thread while walking
      `fil_system.named_spaces` during a checkpoint) is the same failure class as
      **MDEV-37755** ("Mysqld crash in fil_names_clear" — a stale/dangling
      `fil_space_t` left reachable from `fil_system.named_spaces` after the
      tablespace it belongs to has been freed). That ticket is *Closed/Fixed*, and
      the fix (GitHub commit `7301fbac576b2d8630cdaea029e6a682c25d8412`,
      "`fil_space_t::drop()` doesn't remove space from `fil_system.named_spaces`")
      **is present** in the exact revision that crashed
      (`2c0f583a065bdfcaf20b451922fb6caeadd72aa4`) — confirmed both by
      `git merge-base --is-ancestor` and by direct inspection of the checked-out
      source at that commit (the `named_spaces.remove(*space)` call added by the fix
      is there, storage/innobase/fil/fil0fil.cc:1733).
       
      So **this specific trial is not a recurrence of MDEV-37755 as filed** — it is
      either a distinct, still-open variant of the same class of race (a tablespace
      being freed while the checkpoint thread iterates `named_spaces`), most likely
      exposed by a code path the MDEV-37755 fix didn't cover, or a much narrower
      residual window in the fixed code. I could not pin down the exact defect or
      produce a standalone reproducer within the time/resource budget available
      here (see "Reproduction attempts" below); this write-up hands off the
      evidence chain and a concrete, actionable lead for whoever picks it up next.
       
      ## Evidence chain
       
      ### 1. The crash itself (`trial.log`, `vardir96/s1/mysql.err`)
       
      ```
      corrupted double-linked list
      260820  8:06:04 [ERROR] .../sql/mysqld got signal 6 ;
      ```
       
      Coredump backtrace (`vardir96/threads_s1_core.169482_1124106240.txt`):
       
      ```
      #10 malloc () from libc.so.6
      #11 ut_allocator<unsigned char, true>::allocate (...) at .../ut0new.h:376
      #12 mem_heap_create_block_func (...) at .../mem0mem.cc:276
      #13 mem_heap_add_block (...) at .../mem0mem.cc:362
      #14 mem_heap_alloc (...) at .../mem0mem.inl:179
      #15 mtr_buf_t::add_block (...) at .../dyn0buf.h:323
      #16 mtr_buf_t::push<unsigned char*> (...) at .../dyn0buf.h:234
      #17 mtr_buf_t::push (..., path=0x77116cef1fb8 "./partition_db/thash_8_8#P#p8.ibd", ...)
             at .../dyn0buf.h:250
      #18 mtr_t::log_file_op (..., type=FILE_MODIFY, ...) at fil0fil.cc:1616
      #19 fil_names_clear (lsn=31307280) at fil0fil.cc:3193
      #20 log_checkpoint_low (...) at buf0flu.cc:2239
      #21 log_checkpoint () at buf0flu.cc:2314
      #22 buf_flush_page_cleaner () at buf0flu.cc:2875
      ```
       
      This is the classic "detected late" signature: the actual out-of-bounds
      write/use-after-free happens somewhere else; glibc's malloc only notices the
      corrupted heap metadata later, on an unrelated allocation — here, the
      `mem_heap` block that `fil_names_clear()` allocates while writing a
      `FILE_MODIFY` record for `./partition_db/thash_8_8#P#p8.ibd` during a routine
      checkpoint. **The named table/partition (`thash_8_8`, partition `p8`) is the
      one whose file-name string ends up in the corrupted heap block** — i.e. the
      checkpoint was in the middle of processing exactly the table that had just
      been rebuilt (see below), which is what points at a `named_spaces`
      lifetime bug rather than something unrelated like page-compression buffer
      sizing.
       
      ### 2. What was happening to `thash_8_8` right before the crash (`vardir96/s1/mysql.log`)
       
      The general query log shows connection 16 (`WRK-1`) issuing:
       
      ```
      260820  8:05:50   ... (last timestamp before the ALTER)
          16 Query  /* WRK-1 QNO 89 */  ALTER TABLE `thash_8_8` ORDER BY `col_int_nokey` /* Transformed by DisableOptimizations */
      ```
       
      `thash_8_8` is an InnoDB table created with `PARTITIONS 98` (98 hash
      partitions, see `conf/yy/partition_by_hash.yy`). `ALTER TABLE ... ORDER BY`
      on InnoDB always uses `ALGORITHM=COPY`: it builds a whole new table (98 new
      partition tablespaces), copies every row, then drops the 98 old partition
      tablespaces as part of the same statement's commit. **Connection 16 never
      issues another query after this line** — the ALTER was still in flight (columns
      in-progress dropping ~98 tablespaces back-to-back) when the server crashed at
      8:06:04, roughly 12-14 seconds later. That is a much bigger, tighter burst of
      tablespace-drop operations than a single `DROP TABLE` would produce, and it
      lines up with the checkpoint thread choking on `thash_8_8`'s own files.
       
      The trial also had `SET GLOBAL INNODB_COMPRESSION_ALGORITHM=...` firing
      throughout via `conf/yy/innodb_compression_algorithms.yy`, and
      `innodb_compression_default=ON` was set for the whole trial, so every InnoDB
      table (including `thash_8_8`) was page-compressed with whatever algorithm was
      globally active at write time. This does not appear to be the trigger (see
      "Ruled out" below) but it does mean every one of the 98 dropped/recreated
      tablespaces was going through the page-compression write path as well, adding
      extra background I/O/flush activity concurrent with the ALTER.
       
      ## Ruled out
       
      Two candidate known bugs were checked and **both excluded** — not by
      guesswork, but by confirming their fixes are already present in the code that
      actually crashed:
       
      * **MDEV-37755** (`fil_space_t::drop()` doesn't remove space from
        `fil_system.named_spaces`) — same crashing thread, same function
        (`fil_names_clear` via `log_checkpoint`), same underlying defect class
        (freed tablespace still reachable from `named_spaces`). Fix commit
        `7301fbac576b2d8630cdaea029e6a682c25d8412` is an ancestor of
        `2c0f583a065bdfcaf20b451922fb6caeadd72aa4` (`git merge-base
        --is-ancestor` confirms it), and the fixed code
        (`fil0fil.cc:1725-1733`, the `named_spaces.remove(*space)` re-check
        after waiting on pending references) is present verbatim in the crashing
        revision. **This trial cannot be a plain recurrence of the bug as filed and
        fixed** — see "Recommendation" for why it may still be a related, uncovered
        variant.
       
      * **MDEV-39532** (ASAN heap-buffer-overflow in `lzo1x_1_15_compress` /
        `fil_page_compress_low`, still *Open*) — same background thread
        (`buf_flush_page_cleaner`), and the trial's grammar (
        `conf/yy/innodb_compression_algorithms.yy`) does drive
        `SET GLOBAL INNODB_COMPRESSION_ALGORITHM=lzo` at runtime even though the
        server was started with `lzma`, so this looked like a strong candidate.
        Ruled out **both by code inspection and empirically**:
        * Code: `buf_tmp_reserve_compression_buf()` in `storage/innobase/buf/buf0flu.cc`
          already sizes the scratch buffer to `LZO1X_1_15_MEM_COMPRESS` (not just
          `srv_page_size`) whenever the LZO provider is loaded — identical at
          `2c0f583a065b...`, its parent `34b9dc4f...`, and the unrelated ASAN build
          `b26a` (revision `2b127d5fb919f91bccf2402c222baeb66a73349e`, 2026-08-09)
          used below. This is exactly the fix MDEV-39532 needs; it's already there.
        * Empirical: `lzo_repro_driver.sh` (this directory) drives a `PAGE_COMPRESSED=1`
          InnoDB table through `provider_lzo`/`innodb_compression_algorithm=lzo`
          under the ASAN build `b26a`, forces ~2400 buffer-pool flushes via a
          shrunk buffer pool, and shuts down cleanly with **no ASAN report**.
       
      ## Reproduction attempts (all negative)
       
      | # | Grammar | Build | Seed | Duration | Result |
      |---|---|---|---|---|---|
      | 1 | full original mix (5 grammars) | bb-main-atomic-rel (Release-like) | 1787213082 (original) | 180s | `STATUS_OK`, clean |
      | 2 | full original mix (5 grammars) | bb-main-atomic-rel (Release-like) | 1787213082 (rerun, thread interleaving differs) | 180s | `STATUS_OK`, clean |
      | 3 | full original mix (5 grammars) | b26a (ASAN) | 1787213083 | 180s (much lower effective query count: ASAN + `max_statement_time=20` combine to time out most multi-row INSERTs) | `STATUS_OK`, clean |
      | 4 | narrowed: `partition_by_hash.yy` + `innodb_compression_algorithms.yy` only, `gendata=simple` | bb-main-atomic-rel (Release-like) | 1787213200 | 240s | `STATUS_OK`, clean |
       
      Total: **4 full-duration trials, ~0 hits**, roughly 20 minutes of cumulative
      RQG wall-clock. This is a small sample for a race this narrow (the original
      CI run needed ~82s of an 8-thread, higher-throughput Jenkins host to hit it
      once), so a clean run here is weak evidence of absence, not evidence the bug
      is gone. Scripts for attempts 1/2 and attempt 4 are `repro_original.sh` and
      `repro_narrowed.sh` in this directory (`VARDIR=... PORT=... SEED=... bash
      repro_original.sh`); attempt 3 is the same as `repro_original.sh` with
      `BASEDIR=/data/bld/b26a`.
       
      ## Is this specific to the `bb-main-atomic` branch?
       
      Almost certainly not — best guess is that **main reproduces this too**,
      based on code identity rather than an actual hit on main (none was obtained,
      see above):
       
      * The crashing commit's branch ("bb-main-atomic") diverges from mainline
        `main` at `b8dbc264f03ad4ebb483f6380715d81b67cc2a42` (2026-08-03). From
        there, the atomic branch carries only 4 commits of its own (the "Atomic
        CREATE OR REPLACE TABLE" work, MDEV-25292 + MDEV-40776), while `main` has
        191 commits on top of the same base by 2026-08-19.
      * Those 4 atomic-branch commits touch almost nothing outside `mysql-test/`;
        the only non-test change is a one-line whitespace fix to an error string in
        `handler0alter.cc`.
      * `storage/innobase/fil/fil0fil.cc`, `storage/innobase/buf/buf0flu.cc`, and
        `storage/innobase/handler/handler0alter.cc` — everything this crash's call
        stack runs through — are **byte-identical** between the divergence point,
        the crashing commit, and current `main` (fetched fresh, HEAD `bab03b0f`,
        2026-08-19): zero commits touched any of them on either side of the split.
       
      So the atomic-DDL work neither introduces nor fixes anything in the
      tablespace-lifecycle/checkpoint code this bug lives in; it's plain inherited
      InnoDB code. Worth confirming with an actual `main` build if one becomes
      available, but there is no code-level reason to expect it to be
      atomic-branch-specific.
       
      ## Recommendation for whoever continues this
       
      1. **File a new MDEV** referencing MDEV-37755 as related-but-distinct,
         including this signature and the evidence above. Do not assume it's a
         duplicate — the fix commit is confirmably present.
      2. The most promising lead is the **volume/burst of tablespace drops**:
         MDEV-37755's own reproduction (per its JIRA comments) appears to have been
         single/few-tablespace scenarios; this trial's trigger candidate is an
         `ALTER TABLE ... ORDER BY` on a **98-partition** table, i.e. ~98
         tablespace drops fired back-to-back at ALTER commit time, racing a
         concurrent checkpoint. A targeted reproducer should drive repeated
         full-rebuild ALTERs (`ORDER BY`, or any other `ALGORITHM=COPY` ALTER) on
         wide-partition InnoDB tables with a background thread continuously forcing
         checkpoints (e.g. tight `innodb_flush_log_at_trx_commit`/small log file,
         or explicit periodic `FLUSH TABLES` / small `innodb_log_file_size`), and
         should not need `full_text_search`/`all_selects`/`cor.yy` at all — those
         appear to be noise, not part of the trigger.
      3. Given how rare this race is even under conditions close to the original,
         **`rr` chaos-mode recording** (as MariaDB's own QA used for MDEV-37755) is
         almost certainly a better investment than more blind RQG stress runs,
         especially in a disk-constrained sandbox like this one.
      4. If a hit is ever obtained, get the `gdb` backtrace of *all* threads at
         crash time (not just the one that aborted) — the actual corrupting write
         is presumably in a DDL/purge thread that had already returned by the time
         the corruption was noticed, so a single-thread backtrace from the crash
         point will not show it; an `rr` replay or a watchpoint on the freed
         `fil_space_t`'s `named_spaces` list-node pointers (as MDEV-37755's
         diagnosis did) is the way to actually catch the write.
      

      Attachments

        1. trial96.log
          65 kB
        2. mysql.err
          4 kB
        3. mysql.log
          4.37 MB
        4. threads_s1_core.169482_1124106240.txt
          106 kB

        Activity

          People

            elenst Elena Stepanova
            elenst Elena Stepanova
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.