Repository navigation
Windows shutdown crash: TLS destructor accesses memory released by VirtualVector #885
Description
Summary
On Windows, a Rust process using snmalloc-rs as its global allocator can crash during process shutdown when a thread-local value is dropped. The crash is reproducible with a thread-local Arc and does not require Tokio or any application-specific code.
This appears to be an ordering problem: snmalloc's Windows VirtualVector reservation destructor releases allocator memory before Rust's Windows TLS destructor drops the thread-local value. The TLS drop then performs an atomic decrement through memory that has already been released.
Environment
- snmalloc commit:
e9f7b2ebd11fcadd5913c95d90770902e8126646(snmalloc-rs0.7.5) - Rust:
rustc 1.100.0-nightly (5ceaf6608 2026-09-25) - Target:
x86_64-pc-windows-msvc - MSVC toolchain
- Windows 11 Insider Preview, build
10.0.29671
Minimal reproduction
use std::sync::Arc;
#[global_allocator]
static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;
thread_local! {
static VALUE: Arc<usize> = Arc::new(42);
}
fn main() {
VALUE.with(|value| assert_eq!(**value, 42));
println!("main completed");
}Cargo.toml dependency:
[dependencies]
snmalloc-rs = { git = "https://github.com/microsoft/snmalloc.git", rev = "e9f7b2ebd11fcadd5913c95d90770902e8126646" }Build and run on Windows:
cargo run
Observed output:
main completed
error: process didn't exit successfully (...snmalloc_tls.exe)
(exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
The process exits successfully when the #[global_allocator] declaration is removed.
Debugger evidence
The crash is deterministic in both debug and release builds. The failing instruction is Rust's atomic decrement while dropping the TLS value:
snmalloc_tls!core::sync::atomic::atomic_sub<usize,usize>+0x60
snmalloc_tls!alloc::sync::impl$42::drop<usize,alloc::alloc::Global>
snmalloc_tls!core::ptr::drop_glue<alloc::sync::Arc<usize,alloc::alloc::Global>>
snmalloc_tls!std::sys::thread_local::native::lazy::destroy<alloc::sync::Arc<usize,alloc::alloc::Global>>
ntdll!RtlpFlsDataCleanup
The accessed address is already in a MEM_FREE region. A breakpoint on VirtualFree shows the preceding calls originate from:
snmalloc::VirtualVector::~VirtualVector
snmalloc::`dynamic atexit destructor for 'reservations''
Skipping the VirtualVector destructor in the debugger makes the same binary exit with code 0, which confirms that the shutdown ordering is involved.
Questions
Is this shutdown ordering expected to be supported for Rust TLS values? If so, could the Windows reservation cleanup be deferred until after Rust TLS destructors, or otherwise avoid releasing allocator metadata while TLS destructors can still allocate or deallocate? A documented opt-out for allocator finalization would also be useful for applications where the OS will reclaim memory at process exit.
This is related to #809, but the reproduction here is an immediate use-after-free crash rather than slow post-main cleanup.
Activity
mjp41 commentedon Sep 29, 2026
Interesting. This was added by
#773
Neil Monday (@NeilMonday) the tricks you applied in that PR involved specific segments to give correct teardown ordering. I'm guessing that the Rust compiler is either not using those pragmas, or the Rust routine has different order.
Schrodinger ZHU Yifan (@SchrodingerZhu) do you know much about the Rust teardown ordering.
mjp41 commentedon Sep 30, 2026
#886 is working on this
mjp41 commentedon Sep 30, 2026
The problem is not Rust specific.