Problem
The windows_x64_asan / build_x64 pipeline runs the entire onnxruntime_test_all gtest suite in a single process. In cmake/onnxruntime_unittests.cmake each unit-test binary is registered as one add_test entry (no gtest_discover_tests), so ctest --parallel <N> parallelizes across distinct binaries but never within onnxruntime_test_all. Because AddressSanitizer retains freed allocations (quarantine + per-size-class regions), one long-lived process accumulates a monotonically rising high-water mark across the whole suite.
That high-water periodically hits AddressSanitizer's per-size-class region ceiling:
AddressSanitizer: Out of memory. The process has exhausted 8192MB for size class 8192
This 8192 MB is architectural, not a tunable: it is the fixed per-size-class region of compiler-rt's SizeClassAllocator used by the MSVC ASan runtime. Two consequences worth stating up front:
- It is not raisable via
ASAN_OPTIONS — there is no runtime knob to enlarge a size-class region.
- It is independent of physical RAM — it is an address-space/region cap, so a larger runner does not raise it. The failure means too many ~8 KB allocations are simultaneously live or quarantined in that one class, not that the machine ran out of memory.
This is recurring, and the suite is sitting right at the edge:
We hit it again while validating a small, behavior-correct change (#29084): the job flipped green→red on this exact size-class exhaustion in an unrelated SessionState test — i.e. the suite tipped over from cumulative footprint, not from anything specific to the change. The recurring pattern suggests the single-process model, rather than any individual test, is the structural cause.
Proposal
Primary (durable) — shard onnxruntime_test_all under ASan into a few fresh processes.
Use gtest's built-in sharding (GTEST_TOTAL_SHARDS / GTEST_SHARD_INDEX) to register a small number of ctest entries (e.g. 4–8) for onnxruntime_test_all, gated to the ASan configuration to contain blast radius. Each shard runs in its own process, so ASan's high-water resets between shards and no single process approaches the size-class ceiling. This removes the failure mode structurally instead of chasing it per-PR. (Avoid gtest_discover_tests, which runs the instrumented binary at configure time and explodes the ctest entry count.)
Secondary (cheap stopgap) — set ASAN_OPTIONS in the ASan job. e.g. ASAN_OPTIONS=quarantine_size_mb=64:allocator_release_to_os_interval_ms=0. Lowering quarantine releases freed chunks sooner and may buy headroom. Caveat: this reduces retention but does not raise the size-class cap, so it helps only if the pressure is quarantine-held freed chunks — it is a mitigation, not a guaranteed fix, and it slightly narrows use-after-free coverage.
Explicitly not recommended — a bigger runner. As noted above, the 8192 MB is a per-size-class region cap independent of physical RAM; more memory will not raise it. Flagging this to preempt the intuitive "just use a larger box."
Current practice — per-case test trimming (#28797, #28783) keeps unblocking individual PRs, but it is a recurring tax: every heavy new test re-tips a suite that already runs at the edge.
Suggested next step
If the sharding approach sounds reasonable, we're happy to prototype the ASan-gated GTEST_TOTAL_SHARDS wiring in cmake/onnxruntime_unittests.cmake + tools/ci_build/build.py and open a PR for review. We wanted to align on direction first rather than change shared CI execution unilaterally — input from the build-CI maintainers on the preferred approach would be very welcome.
(Environment: --config Debug --enable_address_sanitizer → MSVC /fsanitize=address; runner 1ES.Pool=onnxruntime-github-vs2022-mms; no ASAN_OPTIONS currently set anywhere in CI/build config.)
/cc Dmitri Smirnov (@yuslepukhin) Xavier Dupré (@xadupre) — you reviewed the same-signature OOM fixes (#28797 / #28783).
FYI Hariharan Seshadri (@hariharans29) (context: #29072).
(Suggested assignee: Tianlei Wu (@tianleiwu) — author/merger of #28675 and #28797.)
Problem
The
windows_x64_asan / build_x64pipeline runs the entireonnxruntime_test_allgtest suite in a single process. Incmake/onnxruntime_unittests.cmakeeach unit-test binary is registered as oneadd_testentry (nogtest_discover_tests), soctest --parallel <N>parallelizes across distinct binaries but never withinonnxruntime_test_all. Because AddressSanitizer retains freed allocations (quarantine + per-size-class regions), one long-lived process accumulates a monotonically rising high-water mark across the whole suite.That high-water periodically hits AddressSanitizer's per-size-class region ceiling:
This 8192 MB is architectural, not a tunable: it is the fixed per-size-class region of compiler-rt's
SizeClassAllocatorused by the MSVC ASan runtime. Two consequences worth stating up front:ASAN_OPTIONS— there is no runtime knob to enlarge a size-class region.This is recurring, and the suite is sitting right at the edge:
--test_parallel 4to cap ctest concurrency specifically because the ASan job was OOMing at full-CPU parallelism.8192MB / size class 8192signature; resolved by splitting the QDQ Gemm tests to lower peak per-test memory.We hit it again while validating a small, behavior-correct change (#29084): the job flipped green→red on this exact size-class exhaustion in an unrelated
SessionStatetest — i.e. the suite tipped over from cumulative footprint, not from anything specific to the change. The recurring pattern suggests the single-process model, rather than any individual test, is the structural cause.Proposal
Primary (durable) — shard
onnxruntime_test_allunder ASan into a few fresh processes.Use gtest's built-in sharding (
GTEST_TOTAL_SHARDS/GTEST_SHARD_INDEX) to register a small number of ctest entries (e.g. 4–8) foronnxruntime_test_all, gated to the ASan configuration to contain blast radius. Each shard runs in its own process, so ASan's high-water resets between shards and no single process approaches the size-class ceiling. This removes the failure mode structurally instead of chasing it per-PR. (Avoidgtest_discover_tests, which runs the instrumented binary at configure time and explodes the ctest entry count.)Secondary (cheap stopgap) — set
ASAN_OPTIONSin the ASan job. e.g.ASAN_OPTIONS=quarantine_size_mb=64:allocator_release_to_os_interval_ms=0. Lowering quarantine releases freed chunks sooner and may buy headroom. Caveat: this reduces retention but does not raise the size-class cap, so it helps only if the pressure is quarantine-held freed chunks — it is a mitigation, not a guaranteed fix, and it slightly narrows use-after-free coverage.Explicitly not recommended — a bigger runner. As noted above, the 8192 MB is a per-size-class region cap independent of physical RAM; more memory will not raise it. Flagging this to preempt the intuitive "just use a larger box."
Current practice — per-case test trimming (#28797, #28783) keeps unblocking individual PRs, but it is a recurring tax: every heavy new test re-tips a suite that already runs at the edge.
Suggested next step
If the sharding approach sounds reasonable, we're happy to prototype the ASan-gated
GTEST_TOTAL_SHARDSwiring incmake/onnxruntime_unittests.cmake+tools/ci_build/build.pyand open a PR for review. We wanted to align on direction first rather than change shared CI execution unilaterally — input from the build-CI maintainers on the preferred approach would be very welcome.(Environment:
--config Debug --enable_address_sanitizer→ MSVC/fsanitize=address; runner1ES.Pool=onnxruntime-github-vs2022-mms; noASAN_OPTIONScurrently set anywhere in CI/build config.)/cc Dmitri Smirnov (@yuslepukhin) Xavier Dupré (@xadupre) — you reviewed the same-signature OOM fixes (#28797 / #28783).
FYI Hariharan Seshadri (@hariharans29) (context: #29072).
(Suggested assignee: Tianlei Wu (@tianleiwu) — author/merger of #28675 and #28797.)