Change:
I've only seen this , even though the test should be covered by miri-test-libstd every day: error: memory leaked: alloc26870878 (Rust heap, size: 8, align: 4), allocated here: Error: -- | 100 | rustalloc((), ()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: BACKTRACE: = note: inside at 100:52 = note: inside at 183:86 = note: inside at 243:39 = note: inside at 332:34 = note: inside at 218:20 = note: inside at 145:94 = note: inside at 127:43 note: inside -- | 11 | pub macro threadlocalinner { | ---------------------------- | in this expansion of () | in this expansion of () ... 28 { 29 if let $crate::option::Option::Some(init) = init { 30 if let $crate::option::Option::Some(value) = () { 31 return value; ... | 36 init() 37 }) __^ ... 82 | t, init)); | ------------------------------------------------------------------- in this macro invocation () | ::: | 318 | threadlocal! { static X: u8 = const { 0 } } | -------------------------------------------- in this macro invocation () | ::: | 183 | / macrorules! threadlocal { 184 // empty (base case for the recursion) 185 () ={}; ... | 193 (#[vis t, const $init);
---------- in this macro invocation () ... | 205 ); 206 } - in this expansion of () = note: inside at 283:50 = note: inside at 260:25 note: inside -- | 319 <*const ::addr(x)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: inside -- | 184 | let thisthread = currentthreaduniqueptr(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: inside -- | 10 | let a = l.lock(); | ^^^^^^^^ note: inside closure -- | 6 | #[test] | ------- in this procedural macro expansion 7 | fn smoke() { | ^ | ::: | 1636 | pub macro test($item:item) { | -------------- in this expansion of Cc
Or maybe this is caused by Miri not properly running the on-thread-exit hook on Windows? The support for that is a bit fragile as Miri does not support these magic linker sections we rely on. Instead Miri hard-codes the path to and calls it. But with the test crate we may have that static twice (once in std and once in the test crate) and only one of them gets called... However I would expect that means that the threadlocal impl from the real std is used, so the one from the test crate should not matter. Maybe we should stop using this hack and implement support for this magic linker section instead. I just don't know if there's a good way to ask the compiler for "all in a particular linksection across all crates".
I tried to figure out how TLS is currently implemented on Windows, but I got lost in the dark forest that is .^^ I can't figure out where () would go on Windows; nothing in the Windows folder declares a function. EDIT: Oh, looks like is Unix-only. I got fooled by the "common" in the name and it not being in .
The Windows specific bits happen here: though that the (and related) bits aren't actually used if .
The bug was reported on i686-pc-windows-gnu. Not sure if that's currently or not... I think it is? Only the 32bit MSVC target is not? But I keep forgetting which one exactly it is so I may be wrong.
However I think even with , dtors are handled the same way -- via .
32-bit msvc is currently not . However, that may change soon. is used when is enabled, otherwise is used. Both ultimately use the special linker magic.
Ah, is actually . This is such a maze...^^
You're not the first to notice that:
All tier 1 targets have disabled. There are similar tier 2 targets which have working TLS and have enabled.
Okay, thanks for clarifying. Is there an issue tracking why the windows-gnu targets can't use targetthreadlocal?
Some of binaries produced by tests inside crash with some code that I can'it recall That could be discrepancy between our default LLVM backend which uses Windows native TLS and GNU tools which use emuTLS but I didn't dedicated enough time to it and I'm not planning currently.
I tried enabling native TLS in , the result being access violations when running rustc. Things may have changed since then, though.
I've tried it multiple times over the years and the result was always the same, this time it could work if emutls is enabled as well: but I don't have high hopes here and I think it'd still require some fixes. EDIT: As I anticipated it needs more work: <details<summaryDetails</summary</details
Seems to randomly happen in CI as well https://rust-
So, it was not some issue with the global destructor after all. And it seems to always hit only the reentrant lock, even though that is not the only thread-local we have. Maybe there's an actual race condition leading to occasional memory leaks here?
Minimal reproducer so far: The leak only occurs on the windows-gnu targets.
Presumably this is due to the TLS dtor when not . All the msvc targets currently use the code.
Meanwhile, should work around the CI issue.
FWIW the issue reproduces with and without .
Hm, that's very strange... supposedly there's being called here to create a Box with content : ... there's no call to there that I can see?!? The span specifically points at the .
Oh never mind, I copied the span from the closure not the stacktrace.^^ It's this Box:
One key ingredient to the leak seems to be not loading the latest value here: Cc
Ah, I think I got it. :)

Discussion:
panic!("out of TLS indexes"); } <del> self.key.store(key + 1, Release); </del> register_dtor(self); <ins> // Release-storing the key needs to be the last thing we do. // This is because in `fn key()`, other threads will do an acquire load of the key, // and if that sees this write then it will entirely bypass the `InitOnce`. We thus // need to establish synchronization through `key`. In particular that acquire load // must happen-after the register_dtor above, to ensure the dtor actually runs! self.key.store(key + 1, Release); </ins> let r = c::InitOnceComplete(self.once.get(), 0, ptr::null_mut()); debug_assert_eq!(r, c::TRUE);

// Use acquire ordering to observe key initialization. let mut cur = DTORS.load(Acquire); while !cur.is_null() { <del> let key = (*cur).key.load(Relaxed) - 1; </del> <ins> let pre_key = (*cur).key.load(Acquire); </ins> let dtor = (*cur).dtor.unwrap(); <ins> cur = (*cur).next.load(Relaxed); // In StaticKey::init, we register the dtor before setting `key`. // So if one thread's `run_dtors` races with another thread executing `init` on the same // `StaticKey`, we can encounter a key of 0 here. That means this key was never // initialized in this thread so we can safely skip it. if pre_key == 0 { continue; } // If this is non-zero, then via the `Acquire` load above we synchronized with // everything relevant for this key. (It's not clear that this is needed, since the // release-acquire pair on DTORS also establishes synchronization, but better safe than // sorry.) let key = pre_key - 1; </ins> let ptr = c::TlsGetValue(key); if !ptr.is_null() {

dtor(ptr as *mut _); any_run = true; } <del> cur = (*cur).next.load(Relaxed); </del> } if !any_run {