Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAHLDejxVZvvXLd15z1EP6weF+52J4-LyL6Gysy24dje4Evyckg@mail.gmail.com>
Date: Sat, 7 Nov 2026 13:09:03 +0100
From: Pepper Gray <hello@...pergray.xyz>
To: llvm-bugs@...ts.llvm.org, gcc-bugs@....gnu.org, musl@...ts.openwall.com
Subject: pthread self join hangs on musl

According to POSIX, self-joining a thread is undefined behavior:

> The behavior is undefined if the value specified by the thread argument to
> pthread_join() refers to the calling thread.

```C
#include <pthread.h>
#include <stdio.h>
#include <string.h>

int main(void) {
  int err = pthread_join(pthread_self(), NULL); // glibc: EDEADLK,
musl: never returns
  printf("returned: %s\n", strerror(err));
}
```

but the rationale recommends detecting it:

> If an implementation detects that the value specified by the thread argument
> to pthread_join() refers to the calling thread, it is recommended that the
> function should fail and report an [EDEADLK] error.

This is the case in `glibc`, which reports `EDEADLK` while musl just hangs.

While hanging is technically correct, the behavior is undesirable in practice,
and a behavior like returning `EDEADLK` or even crashing would make it easier to
deal with such an app than when it just hangs.

In C++ this case is defined for `std::thread::join()`
([thread.thread.member](https://eel.is/c++draft/thread.thread.member)):

> *Throws:* `system_error` when an exception is required ([thread.req.exception]).
>
> *Error conditions:*
> - `resource_deadlock_would_occur` — if deadlock is detected or `get_id() == this_thread::get_id()`.

However this doesn't work on `libstdc++` nor `libc++` because they both wrap
`pthread_join`, which just hangs on musl.

```C++
#include <cstdio>
#include <future>
#include <mutex>
#include <system_error>
#include <thread>

int main() {
  std::mutex m;
  std::promise<void> done;
  std::thread t;
  {
    std::lock_guard<std::mutex> lock(m);
    t = std::thread([&] {
      std::lock_guard<std::mutex> lock(m); // wait until t is assigned
      try {
        t.join(); // glibc: throws resource_deadlock_would_occur,
musl: never returns
      } catch (const std::system_error &e) {
        std::printf("threw: %s\n", e.what());
      }
      done.set_value();
    });
  }
  done.get_future().wait();
  t.join();
}
```

I ran these tests with a 5s timeout on `debian` and `alpine` to
confirm the results:

```
FROM debian:trixie-slim AS glibc
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \
    apt-get install -y --no-install-recommends gcc g++ clang libc6-dev && \
    apt-get clean

FROM alpine:3.24 AS musl
RUN apk add --no-cache build-base clang libc++-dev llvm-libunwind-dev
```

```
gcc-glibc                returned: Resource deadlock avoided
gcc-musl                 killed after 5s (hung)
clang-glibc              returned: Resource deadlock avoided
clang-musl               killed after 5s (hung)
g++-glibc-libstdc++      threw: Resource deadlock avoided
clang++-glibc-libstdc++  threw: Resource deadlock avoided
g++-musl-libstdc++       killed after 5s (hung)
clang++-musl-libc++      killed after 5s (hung)
```

I discovered this issue while running the llvm test on Gentoo musl/llvm in
`libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp`

I'd propose to change the behavior in all three places:
as a usability improvement in musl and bugfixes in the C++ libraries.

```diff
pthread_join: return EDEADLK when joining the calling thread

POSIX leaves joining the calling thread undefined, but its rationale
recommends failing with EDEADLK when this is detected. musl blocks
forever instead. Detecting it is a single pointer comparison and lets
C++ std::thread::join throw resource_deadlock_would_occur, which the
C++ standard requires.

Signed-off-by: Pepper Gray <hello@...pergray.xyz>
---
--- a/src/thread/pthread_join.c
+++ b/src/thread/pthread_join.c
@@ -11,6 +11,7 @@
 {
  int state, cs, r = 0;
  __pthread_testcancel();
+ if (t == __pthread_self()) return EDEADLK;
  __pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
  if (cs == PTHREAD_CANCEL_ENABLE) __pthread_setcancelstate(cs, 0);
  while ((state = t->detach_state) && r != ETIMEDOUT && r != EINVAL) {
```

```diff
[libc++] Detect self-join in std::thread::join

[thread.thread.member] requires join() to throw resource_deadlock_would_occur
if get_id() == this_thread::get_id(). libc++ relies on pthread_join to detect
this, but POSIX leaves self-join undefined and musl blocks forever, so
thread.jthread/join.deadlock.pass.cpp hangs on musl.

Check for self-join before calling pthread_join.

Signed-off-by: Pepper Gray <hello@...pergray.xyz>
---
--- a/libcxx/src/thread.cpp
+++ b/libcxx/src/thread.cpp
@@ -43,9 +43,13 @@
 void thread::join() {
   int ec = EINVAL;
   if (!__libcpp_thread_isnull(&__t_)) {
-    ec = __libcpp_thread_join(&__t_);
-    if (ec == 0)
-      __t_ = _LIBCPP_NULL_THREAD;
+    if (__libcpp_thread_id_equal(__libcpp_thread_get_id(&__t_),
__libcpp_thread_get_current_id())) {
+      ec = EDEADLK;
+    } else {
+      ec = __libcpp_thread_join(&__t_);
+      if (ec == 0)
+        __t_ = _LIBCPP_NULL_THREAD;
+    }
   }

   if (ec)
```

```diff
libstdc++: Detect self-join in std::thread::join

[thread.thread.member] requires join() to throw resource_deadlock_would_occur
if get_id() == this_thread::get_id(). libc++ relies on pthread_join to detect
this, but POSIX leaves self-join undefined and musl blocks forever.

Check for self-join before calling pthread_join.

Signed-off-by: Pepper Gray <hello@...pergray.xyz>
---
--- a/libstdc++-v3/src/c++11/thread.cc
+++ b/libstdc++-v3/src/c++11/thread.cc
@@ -132,7 +132,12 @@
     int __e = EINVAL;

     if (_M_id != id())
-      __e = __gthread_join(_M_id._M_thread, 0);
+    {
+      if (_M_id == this_thread::get_id())
+        __e = EDEADLK;
+      else
+        __e = __gthread_join(_M_id._M_thread, 0);
+    }

     if (__e)
       __throw_system_error(__e);
```

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.