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

Reply via email to