Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20260729012651.GH27423@brightrain.aerifal.cx>
Date: Tue, 28 Jul 2026 21:26:51 -0400
From: Rich Felker <dalias@...c.org>
To: ShengYi Hung <aokblast@...eBSD.org>
Cc: musl@...ts.openwall.com
Subject: Re: [PATCH] detect deadlock when waiting itself

On Tue, Jul 28, 2026 at 01:02:28AM -0400, Rich Felker wrote:
> On Tue, Jul 28, 2026 at 12:49:30PM +0800, ShengYi Hung wrote:
> > It is possible to call pthread_join(pthread_self()) from user. In the
> > original implementation, it causes deadlock and never returns. In both
> > glibc and FreeBSD libc implementation. It returns a EDEADLK immediately.
> > 
> > This is found when running libcxx, which is built upon musl, testsuite.
> > In thread.jthread/join.deadlock test, jthread::join will throw an
> > exception if EDEADLK is returned. Thus can jump out from the deadlock.
> > ---
> >  src/thread/pthread_join.c | 5 +++++
> >  1 file changed, 5 insertions(+)
> > 
> > diff --git a/src/thread/pthread_join.c b/src/thread/pthread_join.c
> > index 17dae85d..b66c318c 100644
> > --- a/src/thread/pthread_join.c
> > +++ b/src/thread/pthread_join.c
> > @@ -10,9 +10,14 @@ weak_alias(dummy1, __tl_sync);
> >  static int __pthread_timedjoin_np(pthread_t t, void **res, const struct timespec *at)
> >  {
> >  	int state, cs, r = 0;
> > +	pthread_t self = __pthread_self();
> >  	__pthread_testcancel();
> >  	__pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
> >  	if (cs == PTHREAD_CANCEL_ENABLE) __pthread_setcancelstate(cs, 0);
> > +	if (self == t) {
> > +		__pthread_setcancelstate(cs, 0);
> > +		return EDEADLK;
> > +	}
> >  	while ((state = t->detach_state) && r != ETIMEDOUT && r != EINVAL) {
> >  		if (state >= DT_DETACHED) a_crash();
> >  		r = __timedwait_cp(&t->detach_state, state, CLOCK_REALTIME, at, 1);
> > -- 
> > 2.55.0
> 
> EDEADLK is a "may fail", not a "shall fail" condition. There should
> not be a test asserting that it behaves this way. If it's part of the
> contract of jthread::join that it detect attempts at self-join, it
> should be checking this condition itself, not making nonportable
> assumptions about pthread_join.

To give some context: Issue 6 of POSIX (2001) placed a number of
"shall fails" on conditions that were fundamentally UB, like using
uninitialized objects or using object handles after the end of their
lifetime.

These were obviously wrong, and were fixed in Issue 7 to be "may fail"
conditions, usually described as "if the implementation detects..."

The case of EDEADLK for pthread_join is that it "may fail" under the
broad condition of where "a deadlock was detected". This could include
complicated conditions like two threads mutually waiting on each
other, or a thread waiting on a lock owned by the caller. Self-join is
one of the most obvious directly detectable conditions, but it's not
specified as special.

Generally in musl, we aim not to provide extensions that aren't a
portable assumption to make unless there's a compelling reason and
usefulness. In the case of self-join, it's a programming error, and we
generally prefer to handle programming errors that aren't a "shall
fail" in ways that don't let the program silently continue after doing
something wrong (i.e. not just by returning an error code). For UB,
this means allowing crashing dereferences to proceed or even
intentionally crashing. In the case of pthread_join, it's just not
treating it special and letting the deadlock happen; this is behaving
as specified still, but prevents erroneous forward progress.

Rich

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.