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-rs 0.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:
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.
Summary
On Windows, a Rust process using
snmalloc-rsas its global allocator can crash during process shutdown when a thread-local value is dropped. The crash is reproducible with a thread-localArcand does not require Tokio or any application-specific code.This appears to be an ordering problem: snmalloc's Windows
VirtualVectorreservation 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
e9f7b2ebd11fcadd5913c95d90770902e8126646(snmalloc-rs0.7.5)rustc 1.100.0-nightly (5ceaf6608 2026-09-25)x86_64-pc-windows-msvc10.0.29671Minimal reproduction
Cargo.tomldependency:Build and run on Windows:
Observed output:
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:
The accessed address is already in a
MEM_FREEregion. A breakpoint onVirtualFreeshows the preceding calls originate from:Skipping the
VirtualVectordestructor 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.