On Thu, 30 Oct 2025 11:47:05 +0000
Tomohiro Kashiwada wrote:
> Hello,
>
>
> I found a deadlock in cygwin1.dll that occurs during process cleanup.
>
> It happens when the following conditions are met:
> - A thread is launched that will initialize the thread_local slot for a
> variable whose destructor is non-trivial.
> - The main thread waits to join that thread during global destructor calls,
> before the thread has initialized the thread_local slot.
>
> The former condition refers to a function-scope static thread_local
> variable, or a global thread_local variable whose name has not yet been
> referenced by any thread.
>
> Here is a reproducer (the same file is attached), compile with g++ 13.4.0
> regardless of optimization, and run under cygwin 3.6.5-1
> ---------------------------------------------
>
> #include <thread>
>
> struct the_type {
> ~the_type() {}
> };
> struct myjthread {
> template <typename F>
> myjthread(F f): thr(f) {}
> ~myjthread() { thr.join(); }
> std::thread thr;
> };
>
> thread_local the_type g_v;
>
>
> int main() {
> // if main thread accesses the thread_local variable first, pattern2
> doesn't matter
> //g_v = {};
> static myjthread t([] {
> //std::this_thread::sleep_for(std::chrono::seconds(1)); //< this sleep
> might increase reproducibility
>
> // pattern1: static thread_local
> static thread_local the_type s_v;
>
> // pattern2: global thread_local; its slot is allocated in this thread
> //g_v = {};
> });
> }
>
> ---------------------------------------------
>
> This issue was observed as a random hang in the LLVM test suite.
>
> Although the triggering thread_local variable in LLVM can be removed, I
> hope the runtime can be fixed.
Thanks for the report. I looked into the issue and found the
cause is in newlib. I have confirmed that the following patch
fixes the issue, but I am not very sure unlocking mutex here
is safe. Let me consider a bit more.
diff --git a/newlib/libc/stdlib/__call_atexit.c
b/newlib/libc/stdlib/__call_atexit.c
index 710440389..44f1f6acc 100644
--- a/newlib/libc/stdlib/__call_atexit.c
+++ b/newlib/libc/stdlib/__call_atexit.c
@@ -114,6 +114,11 @@ __call_exitprocs (int code, void *d)
ind = p->_ind;
+#ifndef __SINGLE_THREAD__
+ /* Unlock __atexit_recursive_mutex; otherwise, the function fn() may
+ deadlock if it waits for another thread which calls atexit(). */
+ __lock_release_recursive(__atexit_recursive_mutex);
+#endif
/* Call the function. */
if (!args || (args->_fntypes & i) == 0)
fn ();
@@ -121,6 +126,9 @@ __call_exitprocs (int code, void *d)
(*((void (*)(int, void *)) fn))(code, args->_fnargs[n]);
else
(*((void (*)(void *)) fn))(args->_fnargs[n]);
+#ifndef __SINGLE_THREAD__
+ __lock_acquire_recursive(__atexit_recursive_mutex);
+#endif
/* The function we called call atexit and registered another
function (or functions). Call these new functions before
--
Takashi Yano <[email protected]>
--
Problem reports: https://cygwin.com/problems.html
FAQ: https://cygwin.com/faq/
Documentation: https://cygwin.com/docs.html
Unsubscribe info: https://cygwin.com/ml/#unsubscribe-simple