Both hanging and crashing can be diagnosed under a debugger, but I
think crashing has a practical advantage in CI: a crash is detected
promptly, whereas a hang typically results in a timeout and requires
further investigation.

I checked how other pthread implementations handle self-join
(https://github.com/peppergrayxyz/pthread_join_self), and the
overwhelming majority detects it rather than wait indefinitely:
- Return `EDEADLK`: glibc, uClibc-ng, Bionic, LLVM libc, the BSDs,
Apple, Solaris, GNU Hurd, etc.
- Assert (unless compiled with `NDEBUG`): CloudABI cloudlibc
- Hang: musl, mlibc, threaded wasi-libc

Explicitly detecting a self-join is a widely adopted choice, although
that doesn't necessarily argue for returning `EDEADLK` in musl. You
also make a good point that a hang allows the thread to be canceled,
whereas a crash terminates the entire process.

Since self-join doesn't necessarily leave the application permanently
deadlocked as long as another thread can cancel the joiner, I wondered
whether another alternative would be possible: let the self-joining
thread block while other threads exist, but crash once it becomes the
last remaining thread. Musl already tracks the number of live threads,
although this would presumably require some mechanism to notify the
blocked thread when the others exit.

I'm not sure though, if the benefit would justify the implementation
and maintenance complexity. Simplicity seems like a good reason to
prefer an immediate crash over more elaborate handling, at least
without a concrete use case where preserving cancellation matters.

On Mon, 9 Nov 2026 at 02:44, Rich Felker <[email protected]> wrote:
>
> On Sun, Nov 08, 2026 at 06:47:52PM +0100, Pepper Gray wrote:
> > I understand the reasoning against returning an error on UB, but I
> > think hanging indefinitely is worse because it's much harder to
> > diagnose. There's already a precedent in musl's pthread_join()
> > implementation where it explicitly calls a_crash() when it detects
> > certain invalid thread states. For example, Box64's tests/test11.c
> > joins the same thread twice. glibc returns an error on the second
> > join, which the test ignores, while musl crashes because the thread's
> > resources have already been unmapped.
> >
> > So why not deliberately crash on self-join as well, rather than hang
> > indefinitely?
> > That would allow for both: avoid allowing execution on UB and making
> > it easier to diagnose.
>
> Yes, it's something I'd considered, but I'm not sure whether it's
> better. It's a matter of whether it's easier to attach a debugger and
> see what's happening in a stuck process or re-run under debugger and
> wait for crash (or enable core files).
>
> The cases where it crashes now are UAF bugs (memory unsafety) but
> self-join is a case where nothing inherently unsafe has happened;
> semantically a self-join could be well-defined as an infinite hang (or
> hang until cancel) for the thread. So there's not really a safety
> mandate to prefer crash.
>
> If crash is deemed the better behavior though I see no reason it
> couldn't be changed.
>
>
>
> > On Sat, 7 Nov 2026 at 14:41, Rich Felker <[email protected]> wrote:
> > >
> > > On Sat, Nov 07, 2026 at 01:09:03PM +0100, Pepper Gray wrote:
> > > > 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.
> > >
> > > It's very intentional that we do not facilitate forward progress when
> > > a program has invoked undefined behavior. There is no good reason to
> > > expect that a programmer has ignored the fact that the operation is UB
> > > will have taken the effort to check the return value, so just
> > > returning an error is likely to result in the flow of execution
> > > continuing to run in an inconsistent and possibly dangerous state.
> > >
> > > Self-join suggests a confusion about who owns resources, so this may
> > > involve data races, etc. But this is a pretty broad principle in musl,
> > > not specific to pthread_join. At one point my write-up of the
> > > principle was included in some glibc guidelines too; I can't find the
> > > link to that at the moment, but FWIW there was consensus that "return
> > > an error on UB" is bad behavior.
> > >
> > > > 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.
> > >
> > > If the C++ std::thread::join has a contract to throw on self-join then
> > > it needs to call pthread_equal before pthread_join to check that
> > > condition.

Reply via email to