fix(windows): keep the 8 MiB main-thread stack on legacy builds (0xC00000FD) (#13672)

* fix(windows): keep the 8 MiB main-thread stack on legacy builds

Windows 7 / Server 2012 R2 users on 0.6.58 report the WER dialog "DBX 已停止
工作" with APPCRASH signature dbx.exe / faulting module dbx.exe / exception
c00000fd (STATUS_STACK_OVERFLOW) as soon as they click a database connection
(MySQL, Oracle 11 and Dameng 8 all reproduce), while 0.6.55 is fine.

Root cause: .cargo/config.toml raises the main-thread stack to 8 MiB with
-C link-arg=/STACK:8388608, but Cargo ignores target rustflags whenever a
RUSTFLAGS env var is set. The Win7/Server 2012 R2 jobs set RUSTFLAGS for the
static CRT, so the legacy PE kept the 1 MiB linker default while the standard
Windows build kept 8 MiB. The 0.6.56 connect-path rewrite made the DB query
stack deeper, and the legacy 1 MiB stack overflows on connect.

- repeat -C link-arg=/STACK:8388608 in every workflow RUSTFLAGS that links a
  Windows PE (release Win7 app + dbx-web, CI Win7/standard/duckdb jobs, SQL
  Server 2000 Win7 test build)
- add the same flag to the legacy target table in .cargo/config.toml for local
  builds
- fail the Win7 PE audit when SizeOfStackReserve drops below 8 MiB, so this
  cannot silently regress again
- assert the flag stays in the workflows from ci-workflow.test.mjs

* chore(clippy): indent the MariaDB KB doc continuation

main's 7958f7ad9 added an unindented continuation line after a doc list
item, so rust-fmt-clippy now fails on main and on every PR with
clippy::doc_lazy_continuation under -D warnings. Indent the line the way
the lint documents so the build can go green.

---------

Co-authored-by: DBX Agent <agent@dbx.local>
This commit is contained in:
zipgandDBX Agent authored and GitHub committed 2026-10-27 20:19:24 +08:00
1 parent c09662d78e
commit fbb588eecb
8 files changed
+98 -8

No files matched your search

+8
View File
@@ -23,3 +23,11 @@ linker = "rust-lld"
# Large Rust projects in debug mode overflow the 1 MiB stack when DB query
# paths pull in deep serde/driver call stacks. This embeds the size in the PE.
rustflags = ["-C", "link-arg=/STACK:8388608"]
# The legacy Windows 7 / Server 2012 R2 target keeps the same 8 MiB main-thread
# stack. CI builds it with an explicit RUSTFLAGS env (static CRT), which
# replaces this table, so the same link arg is repeated in those workflows and
# audited by .github/scripts/assert-win7-pe-compat.ps1. Without it the legacy PE
# keeps the 1 MiB default and the app aborts with 0xC00000FD ("DBX 已停止工作").
[target.x86_64-win7-windows-msvc]
rustflags = ["-C", "link-arg=/STACK:8388608"]
+35
View File
@@ -69,3 +69,38 @@ if ($violations.Count -gt 0) {
}
Write-Host "Windows 7 PE import audit passed: $BinaryPath"
# Main-thread stack reserve audit. The app needs the 8 MiB stack that
# .cargo/config.toml applies to the standard Windows target; the legacy target is
# built with an explicit env RUSTFLAGS (static CRT), which replaces the config
# rustflags, so the workflow repeats `-C link-arg=/STACK:8388608`. Without it the
# PE keeps the 1 MiB linker default and deep connect/serde call stacks abort with
# 0xC00000FD ("DBX 已停止工作"), which users reported on Windows 7 / Server 2012 R2.
$minimumStackReserve = 8 * 1024 * 1024
$image = [System.IO.File]::ReadAllBytes($BinaryPath)
if ($image.Length -lt 0x100) {
throw "Windows 7 PE audit target is truncated: $BinaryPath"
}
$peOffset = [System.BitConverter]::ToInt32($image, 0x3C)
if ($peOffset -le 0 -or $peOffset + 24 -gt $image.Length) {
throw "Invalid PE header offset in ${BinaryPath}"
}
$peSignature = [System.BitConverter]::ToUInt32($image, $peOffset)
if ($peSignature -ne 0x00004550) {
throw "Invalid PE signature in ${BinaryPath}"
}
$optionalHeaderOffset = $peOffset + 24
$optionalHeaderMagic = [System.BitConverter]::ToUInt16($image, $optionalHeaderOffset)
$stackReserveOffset = $optionalHeaderOffset + 72
$stackReserve = if ($optionalHeaderMagic -eq 0x20B) {
[System.BitConverter]::ToUInt64($image, $stackReserveOffset)
} elseif ($optionalHeaderMagic -eq 0x10B) {
[System.BitConverter]::ToUInt32($image, $stackReserveOffset)
} else {
throw ("Unsupported PE optional header magic 0x{0:X4} in {1}" -f $optionalHeaderMagic, $BinaryPath)
}
if ($stackReserve -lt $minimumStackReserve) {
throw ("Windows 7 main-thread stack reserve is {0} bytes, below the {1}-byte minimum. Add -C link-arg=/STACK:8388608 to the build RUSTFLAGS. Binary: {2}" -f $stackReserve, $minimumStackReserve, $BinaryPath)
}
Write-Host "Windows 7 PE stack reserve audit passed: $stackReserve bytes ($BinaryPath)"
+23
View File
@@ -496,3 +496,26 @@ test("path filtering and the planner use the exact test merge first parent", ()
}
assert.doesNotMatch(changes, /steps\.filter\.outputs\.windows_win7_bundle|github\.event\.pull_request\.base\.sha|WIN7_CURRENT/);
});
test("legacy Windows builds keep the 8 MiB main-thread stack", () => {
const release = readFileSync(new URL("../workflows/release.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
const sqlServerUserTest = readFileSync(new URL("../workflows/sqlserver2000-windows-user-test.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
const stackFlag = /RUSTFLAGS:.*link-arg=\/STACK:8388608/;
// An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so every job
// that links a Windows PE has to repeat the /STACK link argument; otherwise
// the 1 MiB linker default returns and deep DB call stacks abort with
// 0xC00000FD ("DBX 已停止工作").
for (const name of ["windows-standard-check", "windows-win7-bundle", "duckdb-windows-driver"]) {
assert.match(job(name), stackFlag, `${name} must keep the 8 MiB main-thread stack in RUSTFLAGS`);
}
for (const name of ["build-windows-7-offline", "build-windows-7-static-browser"]) {
assert.match(job(name, release), stackFlag, `release job ${name} must keep the 8 MiB main-thread stack in RUSTFLAGS`);
}
assert.match(job("build-windows-7-x64", sqlServerUserTest), stackFlag, "the SQL Server 2000 Win7 test build must keep the 8 MiB main-thread stack");
});
test("the Win7 PE audit fails when the stack reserve regresses", () => {
const audit = readFileSync(new URL("./assert-win7-pe-compat.ps1", import.meta.url), "utf8");
assert.ok(audit.includes("minimumStackReserve"));
assert.ok(audit.includes("link-arg=/STACK:8388608"));
});
+3 -1
View File
@@ -963,7 +963,9 @@ jobs:
shell: bash
run: |
if [ "${{ runner.os }}" = "Windows" ]; then
export RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C target-feature=+crt-static"
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags; keep
# the 8 MiB main-thread stack of the DBX app for the Win7 driver too.
export RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C target-feature=+crt-static -C link-arg=/STACK:8388608"
fi
if [ -n "${{ matrix.manylinux_image }}" ]; then
docker run --rm \
+15 -3
View File
@@ -313,7 +313,11 @@ jobs:
timeout-minutes: 45
env:
CARGO_INCREMENTAL: "0"
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
# 8 MiB main-thread stack must be repeated here: without it the Windows PE
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
RUSTC_WRAPPER: sccache
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
# Linux target caches when thousands of GHA sccache entries pile up.
@@ -388,7 +392,11 @@ jobs:
CARGO_INCREMENTAL: "0"
CARGO_PROFILE_RELEASE_LTO: "off"
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "8"
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
# 8 MiB main-thread stack must be repeated here: without it the Windows PE
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
RUSTC_WRAPPER: sccache
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
# Linux target caches when thousands of GHA sccache entries pile up.
@@ -830,7 +838,11 @@ jobs:
env:
CARGO_INCREMENTAL: "0"
CARGO_TARGET_DIR: ${{ github.workspace }}/target/duckdb-driver
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
# 8 MiB main-thread stack must be repeated here: without it the Windows PE
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
RUSTC_WRAPPER: sccache
# Prefer S3 when available; GitHub's 10 GB cache quota evicts the shared
# Linux target caches when thousands of GHA sccache entries pile up.
+10 -2
View File
@@ -579,7 +579,11 @@ jobs:
# The vendored loader is a tracked Cargo input, so Rust compile caching is safe.
env:
CARGO_INCREMENTAL: "0"
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
# 8 MiB main-thread stack must be repeated here: without it the legacy PE
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}
SCCACHE_GHA_VERSION: win7-webview2-1.0.902.49-v1
@@ -747,7 +751,11 @@ jobs:
arch: x86_64
env:
CARGO_INCREMENTAL: "0"
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
# An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so the
# 8 MiB main-thread stack must be repeated here: without it the legacy PE
# keeps the 1 MiB linker default and deep connect/serde call stacks abort
# with 0xC00000FD ("DBX 已停止工作"). Guarded by assert-win7-pe-compat.ps1.
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
steps:
- uses: actions/checkout@v7
@@ -12,7 +12,9 @@ jobs:
timeout-minutes: 120
env:
CARGO_INCREMENTAL: "0"
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static
# Keep the Win7/Server 2012 R2 main-thread stack at 8 MiB: an env
# RUSTFLAGS replaces the .cargo/config.toml target rustflags.
RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static -C link-arg=/STACK:8388608
steps:
- uses: actions/checkout@v7
+1 -1
View File
@@ -3860,7 +3860,7 @@ fn normalized_foreign_key_action(action: Option<&str>) -> Option<String> {
/// - "NO ACTION: ... For InnoDB, this is equivalent to RESTRICT".
/// - "Specifying RESTRICT (or NO ACTION) is the same as omitting the ON DELETE or ON UPDATE clause."
/// - "For an ON DELETE or ON UPDATE that is not specified, the default action is always NO ACTION."
/// MariaDB KB "Foreign Keys" likewise documents `NO ACTION` as a "Synonym for RESTRICT".
/// MariaDB KB "Foreign Keys" likewise documents `NO ACTION` as a "Synonym for RESTRICT".
///
/// Only the comparison is normalized; generated DDL keeps the source's original action text.
/// Known caveat: on MySQL NDB Cluster `NO ACTION` is a deferred check, but schema metadata does