Skip to content

Miri sometimes reports memory leak in thread-local storage on *-pc-windows-gnu #123583

Description

@RalfJung

I've only seen this once, 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:     --> /checkout/library/alloc/src/alloc.rs:100:9
       |
  100  |         __rust_alloc(layout.size(), layout.align())
       |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: BACKTRACE:
       = note: inside `realstd::alloc::alloc` at /checkout/library/alloc/src/alloc.rs:100:9: 100:52
       = note: inside `realstd::alloc::Global::alloc_impl` at /checkout/library/alloc/src/alloc.rs:183:73: 183:86
       = note: inside `<realstd::alloc::Global as core::alloc::Allocator>::allocate` at /checkout/library/alloc/src/alloc.rs:243:9: 243:39
       = note: inside `alloc_crate::alloc::exchange_malloc` at /checkout/library/alloc/src/alloc.rs:332:11: 332:34
       = note: inside `realstd::boxed::Box::<realstd::sys::thread_local::os_local::Value<u8>>::new` at /checkout/library/alloc/src/boxed.rs:218:9: 218:20
       = note: inside `realstd::thread::local_impl::Key::<u8>::try_initialize::<{closure@/checkout/library/std/src/sys/thread_local/os_local.rs:28:27: 28:34}>` at /checkout/library/std/src/sys/thread_local/os_local.rs:145:37: 145:94
       = note: inside `realstd::thread::local_impl::Key::<u8>::get::<{closure@/checkout/library/std/src/sys/thread_local/os_local.rs:28:27: 28:34}>` at /checkout/library/std/src/sys/thread_local/os_local.rs:127:18: 127:43
  note: inside `sync::reentrant_lock::current_thread_unique_ptr::X::__getit`
      --> /checkout/library/std/src/sys/thread_local/os_local.rs:28:17
       |
  11   |   pub macro thread_local_inner {
       |   ----------------------------
       |   |
       |   in this expansion of `$crate::thread::local_impl::thread_local_inner!` (#2)
       |   in this expansion of `$crate::thread::local_impl::thread_local_inner!` (#3)
  ...
  28   | /                 __KEY.get(move || {
  29   | |                     if let $crate::option::Option::Some(init) = _init {
  30   | |                         if let $crate::option::Option::Some(value) = init.take() {
  31   | |                             return value;
  ...    |
  36   | |                     __init()
  37   | |                 })
       | |__________________^
  ...
  82   |               $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
       |               ------------------------------------------------------------------- in this macro invocation (#3)
       |
      ::: library/std/src/sync/reentrant_lock.rs:318:5
       |
  318  |       thread_local! { static X: u8 = const { 0 } }
       |       -------------------------------------------- in this macro invocation (#1)
       |
      ::: library/std/src/thread/local.rs:183:1
       |
  183  | / macro_rules! thread_local {
  184  | |     // empty (base case for the recursion)
  185  | |     () => {};
  ...    |
  193  | |         $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, const $init);
       | |         ----------------------------------------------------------------------------------------- in this macro invocation (#2)
  ...    |
  205  | |     );
  206  | | }
       | |_- in this expansion of `thread_local!` (#1)
       = note: inside `realstd::thread::LocalKey::<u8>::try_with::<{closure@library/std/src/sync/reentrant_lock.rs:319:12: 319:15}, usize>` at /checkout/library/std/src/thread/local.rs:283:32: 283:50
       = note: inside `realstd::thread::LocalKey::<u8>::with::<{closure@library/std/src/sync/reentrant_lock.rs:319:12: 319:15}, usize>` at /checkout/library/std/src/thread/local.rs:260:9: 260:25
  note: inside `sync::reentrant_lock::current_thread_unique_ptr`
      --> library/std/src/sync/reentrant_lock.rs:319:5
       |
  319  |     X.with(|x| <*const _>::addr(x))
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  note: inside `sync::reentrant_lock::ReentrantLock::<()>::lock`
      --> library/std/src/sync/reentrant_lock.rs:184:27
       |
  184  |         let this_thread = current_thread_unique_ptr();
       |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  note: inside `sync::reentrant_lock::tests::smoke`
      --> library/std/src/sync/reentrant_lock/tests.rs:10:17
       |
  10   |         let a = l.lock();
       |                 ^^^^^^^^
  note: inside closure
      --> library/std/src/sync/reentrant_lock/tests.rs:7:11
       |
  6    | #[test]
       | ------- in this procedural macro expansion
  7    | fn smoke() {
       |           ^
       |
      ::: /checkout/library/core/src/macros/mod.rs:1636:5
       |
  1636 |     pub macro test($item:item) {
       |     -------------- in this expansion of `#[test]`

Cc @joboet @m-ou-se @Amanieu

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Apr 7, 2024
  2. RalfJung commented on Apr 7, 2024

    @RalfJung
    MemberAuthor

    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 this static 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 this logic means that the thread_local 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 static in a particular link_section across all crates".

  3. RalfJung commented on Apr 7, 2024

    @RalfJung
    MemberAuthor

    I tried to figure out how TLS is currently implemented on Windows, but I got lost in the dark forest that is std::sys.^^ I can't figure out where this call (imp::create) would go on Windows; nothing in the Windows folder declares a create function.

    EDIT: Oh, looks like sys_common::thread_local_key is Unix-only. I got fooled by the "common" in the name and it not being in pal.

  4. ChrisDenton commented on Apr 7, 2024

    @ChrisDenton
    Member

    The Windows specific bits happen here: https://github.com/rust-lang/rust/blob/e43aef0ef9cc9d2d12c138b36fd817f7c41d0152/library/std/src/sys/pal/windows/thread_local_key.rs

    Note though that the StaticKey (and related) bits aren't actually used if #[cfg(target_thread_local)].

  5. RalfJung commented on Apr 7, 2024

    @RalfJung
    MemberAuthor

    The bug was reported on i686-pc-windows-gnu. Not sure if that's currently target_thread_local 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.

  6. RalfJung commented on Apr 7, 2024

    @RalfJung
    MemberAuthor

    However I think even with target_thread_local, dtors are handled the same way -- via thread::local_impl::Key::<T>::register_dtor.

  7. ChrisDenton commented on Apr 7, 2024

    @ChrisDenton
    Member

    32-bit msvc is currently not target_thread_local. However, that may change soon.

    register_keyless_dtor is used when target_thread_local is enabled, otherwise register_dtor is used. Both ultimately use the special .CRT$XLB linker magic.

  8. RalfJung commented on Apr 7, 2024

    @RalfJung
    MemberAuthor

    Ah, register_dtor is actually register_keyless_dtor.

    pub use super::thread_local_key::register_keyless_dtor as register_dtor;

    This is such a maze...^^

  9. ChrisDenton commented on Apr 7, 2024

    @ChrisDenton
    Member

    You're not the first to notice that: #110897

  10. added
    O-windowsOperating system: Windows
    A-thread-localsArea: Thread local storage (TLS)
    C-bugCategory: This is a bug.
    A-miriArea: The miri tool
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Apr 7, 2024
  11. changed the title [-]Miri sometimes reports memory leak in reentrant lock on Windows[/-] [+]Miri sometimes reports memory leak in thread-local storage on Windows[/+] on Apr 15, 2024
  12. 14 remaining items

  13. ChrisDenton commented on Apr 22, 2024

    @ChrisDenton
    Member

    Presumably this is due to the TLS dtor when not has_thread_local.

    All the msvc targets currently use the #[cfg(target_thread_local)] code.

  14. RalfJung commented on Apr 22, 2024

    @RalfJung
    MemberAuthor

    Meanwhile, #124270 should work around the CI issue.

  15. RalfJung commented on Apr 22, 2024

    @RalfJung
    MemberAuthor

    FWIW the issue reproduces with and without const { ... }.

  16. RalfJung commented on Apr 22, 2024

    @RalfJung
    Author
  17. RalfJung commented on Apr 22, 2024

    @RalfJung
    MemberAuthor

    Oh never mind, I copied the span from the closure not the stacktrace.^^

    It's this Box:

    let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self }));

  18. RalfJung commented on Apr 22, 2024

    @RalfJung
    MemberAuthor

    One key ingredient to the leak seems to be not loading the latest value here:

    let mut cur = DTORS.load(Acquire);

    Cc @joboet

  19. added a commit that references this issue on Apr 22, 2024
  20. RalfJung commented on Apr 23, 2024

    @RalfJung
    MemberAuthor

    Ah, I think I got it. :)
    #124281

  21. added a commit that references this issue on Apr 24, 2024
  22. added a commit that references this issue on Apr 24, 2024
  23. matthiaskrgr commented on Apr 24, 2024

    @matthiaskrgr
    Member

    🏅

  24. added a commit that references this issue on Apr 25, 2024
  25. added a commit that references this issue on Apr 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-miriArea: The miri toolA-thread-localsArea: Thread local storage (TLS)C-bugCategory: This is a bug.O-windowsOperating system: Windows

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions