LocalKey destroyed in another thread

Hi all,

I have faced with the strange issue ...

LocalKey drops from wrong thread, but I need that LocalKey drops from thread where it was created !!

How to do this ? It is possible ?

I need it because in drop I have JoinHandle and it turns that second thread tries to close LocalKey from main thread which contain JoinHandle to it ... and when I call .join on JoinHandle app crashed because this thread already closed by application ...

use std::thread::JoinHandle;
use std::cell::RefCell;

fn do_nothing() {
    loop {
    }
}

struct LocalStrategy {
    jh: RefCell<Option<JoinHandle<()>>>,
}

impl Drop for LocalStrategy {
    fn drop(&mut self) {
        if let Some(join_handle) = self.jh.borrow_mut().take() {
            join_handle.join();
        }
    }
}

thread_local! {
    static LOCAL_STRATEGY: LocalStrategy = {
        LocalStrategy {
            jh: RefCell::new(Some(std::thread::spawn(|| {
                do_nothing();
            })))
        }
    };
}

fn main() {
    LOCAL_STRATEGY.with(|lc| {
    });
    println!("Hello, crash!");
}

crash backtrace:

    Finished dev [unoptimized + debuginfo] target(s) in 0.51s
     Running `target\debug\untitled3.exe`
Hello, crash!
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib/rustlib/src/rust\src\libstd\thread\mod.rs:1357:18
stack backtrace:
   0: backtrace::backtrace::trace_unsynchronized
             at C:\Users\VssAdministrator\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.46\src\backtrace\mod.rs:66
   1: std::sys_common::backtrace::_print_fmt
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys_common\backtrace.rs:78
   2: std::sys_common::backtrace::_print::{{impl}}::fmt
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys_common\backtrace.rs:59
   3: core::fmt::write
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libcore\fmt\mod.rs:1069
   4: std::io::Write::write_fmt<std::sys::windows::stdio::Stderr>
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\io\mod.rs:1532
   5: std::sys_common::backtrace::_print
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys_common\backtrace.rs:62
   6: std::sys_common::backtrace::print
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys_common\backtrace.rs:49
   7: std::panicking::default_hook::{{closure}}
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\panicking.rs:198
   8: std::panicking::default_hook
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\panicking.rs:218
   9: std::panicking::rust_panic_with_hook
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\panicking.rs:477
  10: std::panicking::begin_panic_handler
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\panicking.rs:385
  11: core::panicking::panic_fmt
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libcore\panicking.rs:89
  12: core::panicking::panic
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libcore\panicking.rs:52
  13: core::option::Option<core::result::Result<(), alloc::boxed::Box<Any>>>::unwrap<core::result::Result<(), alloc::boxed::Box<Any>>>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libcore\macros\mod.rs:10
  14: std::thread::JoinInner<()>::join<()>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libstd\thread\mod.rs:1357
  15: std::thread::JoinHandle<()>::join<()>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libstd\thread\mod.rs:1487
  16: untitled3::{{impl}}::drop
             at .\src\main.rs:16
  17: core::ptr::drop_in_place<untitled3::LocalStrategy>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libcore\ptr\mod.rs:178
  18: core::ptr::drop_in_place<core::option::Option<untitled3::LocalStrategy>>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libcore\ptr\mod.rs:178
  19: core::mem::drop<core::option::Option<untitled3::LocalStrategy>>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libcore\mem\mod.rs:873
  20: std::thread::local::fast::destroy_value<untitled3::LocalStrategy>
             at C:\Users\RedRa\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libstd\thread\local.rs:460
  21: std::sys_common::thread_local::register_dtor_fallback::run_dtors
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys_common\thread_local.rs:260
  22: std::sys::windows::thread_local::run_dtors
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys\windows\thread_local.rs:229
  23: std::sys::windows::thread_local::on_tls_callback
             at /rustc/f05a5240440b3eaef1684a7965860fab40301947\/src\libstd\sys\windows\thread_local.rs:198
  24: RtlActivateActivationContextUnsafeFast
  25: RtlActivateActivationContextUnsafeFast
  26: LdrShutdownProcess
  27: RtlExitUserProcess
  28: ExitProcess
  29: o_free
  30: exit
  31: __scrt_common_main_seh
             at d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:295
  32: BaseThreadInitThunk
  33: RtlUserThreadStart
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

It might be related to this:
EXEs and DLLs both have callbacks for exiting when a process or thread exists. In EXEs it's a TlsCallback and in DLLs it's DllMain. Some executables opt-out of these signals by calling DisableThreadLibraryCalls, or by not setting the TlsCallback in the executable's PE header.
The windows loader has strict rules about synchronization in these functions. Only Dlls are documented, and that same LdrShutdownProcess which can be seen in the trace is from the library these rules are for. Here is a quote from MSDN about DLL best practices:

2 Likes

But I did not get, why 'drop' method called from second thread but it should be called from main thread ... ?

In example I have thread local only for main thread, but destructor is called from second thread, but there is no LocalKey !! There is no thread local object !! And definitely there no JoinHandle because this thread has not spawn any other thread !!?

I don't think that Drop was called from a second thread.

  1. main initializes ThreadLocalStorage with the LocalStrategy struct, which spawns a new thread.
  2. The new thread spins until it's killed. (by Windows, probably)
  3. println!("Hello, crash!"); is called in the main thread.
  4. main returns. stdlib calls ExitProcess. At this point, Windows can kill any thread.
  5. Windows calls stdlibs's on_tls_callback, which as you can see is stdlib's "DllMain", which the above restrictions apply to. This calls Drop for LocalStrategy. The drop implementation then tries to join a thread (which is not allowed, it is consider synchronization), join is probably in an inconsistent state and crashes. Precisely as documented in MSDN.

Panic happens in thread < unnamed > and panic backtrace shows line in join_handle.join() after if Some() ... this mean that main thread spawn the second one in its own local variable, but this local variable drop method called from second < unnamed > thread ... It is easy to check just run code on Windows and check backtrace

I checked with WinDBG, It all happens in the main thread which already called ExitProcess. stdlib already removed the thread's name from memory since it is also stored in ThreadLocalStorage in a linked list, hence <unamed> thread.

Either way, Drop in a thread_local shouldn't do any synchronization on Windows.

Thread locals are initialized eagerly on every threads spawned. And it's not guaranteed to be dropped especially for the main thread, as not every platforms allows it.

@naim @Hyeonu Thank you all for help !!

If you interested what is it for ...

I've been just rewriting from scratch the rust-gc library, just to try strength... )

Here is my implementation:

And also I have the similar project written for C++ with Python :wink:

But writting it in Rust is much more fun :wink:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.