[PATCH v6 2/4] nptl: Do not use pthread set_tid_address as state synchronization (BZ #19951)

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Dec 11 14:26:14 GMT 2025



On 08/12/25 19:19, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>> @@ -496,6 +498,19 @@ start_thread (void *arg)
>>       the breakpoint reports TD_THR_RUN state rather than TD_THR_ZOMBIE.  */
>>    atomic_fetch_or_relaxed (&pd->cancelhandling, EXITING_BITMASK);
>>  
>> +  /* CONCURRENCY NOTES:
>> +
>> +     Concurrent pthread_detach() either sets the state to
>> +     THREAD_STATE_DETACHED or waits for the thread to terminate.  The
>> +     THREAD_STATE_EXITING state set here ensures that pthread_join() waits
>> +     until all required cleanup steps are complete.
>> +
>> +     The 'prevstate' variable field will be used to determine who is
>> +     responsible for calling __nptl_free_tcb below.  */
>> +
>> +  unsigned int prevstate = atomic_exchange_acquire (&pd->joinstate,
>> +						    THREAD_STATE_EXITING);
> 
> The acquire seems unnecessary because it does not synchronize with
> anything.

Ack, I will change to atomic_exchange_relaxed.

> 
>> @@ -865,10 +883,11 @@ __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
>>  
>>  	  /* Similar to pthread_join, but since thread creation has failed at
>>  	     startup there is no need to handle all the steps.  */
>> -	  pid_t tid;
>> -	  while ((tid = atomic_load_acquire (&pd->tid)) != 0)
>> -	    __futex_abstimed_wait_cancelable64 ((unsigned int *) &pd->tid,
>> -						tid, 0, NULL, LLL_SHARED);
>> +	  unsigned int state;
>> +	  while ((state = atomic_load_relaxed (&pd->joinstate))
>> +                 != THREAD_STATE_EXITED)
>> +	    __futex_abstimed_wait_cancelable64 (&pd->joinstate, state, 0,
>> +                                                NULL, LLL_SHARED);
>>          }
> 
> Shouldn't use a cancelable wait because pthread_create is declared
> _THROWNL.  Maybe file a separate bug if you don't want to change this
> now.

As you noted I fixed on the last patch.  I will move the fix as the
first patch to avoid further confusion and open a bug report to 
track it.

> 
>> diff --git a/nptl/pthread_detach.c b/nptl/pthread_detach.c
>> index 3bbc037bdc..9b776ba9b0 100644
>> --- a/nptl/pthread_detach.c
>> +++ b/nptl/pthread_detach.c
>> @@ -25,32 +25,32 @@ ___pthread_detach (pthread_t th)
>>  {
>>    struct pthread *pd = (struct pthread *) th;
>>  
>> +  /* CONCURRENCY NOTES:
>>  
>> +     Concurrent pthread_detach will return EINVAL for the case where the
>> +     thread is already detached (THREAD_STATE_DETACHED).  POSIX states it is
>> +     undefined to call pthread_detach if TH refers to a non-joinable thread.
>>  
>> +     In the case the thread is being terminated (THREAD_STATE_EXITING),
>> +     pthread_detach will be responsible for cleaning up the stack.  */
>> +
>> +  unsigned int prevstate = atomic_load_relaxed (&pd->joinstate);
>> +  do
>>      {
>> +      if (prevstate != THREAD_STATE_JOINABLE)
>> +	{
>> +	  if (prevstate == THREAD_STATE_DETACHED)
>> +	    return EINVAL;
>> +	  /* pthread_detach is declared _THROW so it need to call a
>> +	     pthread_join variant that is not an cancellation entrypoint.  */
> 
> typo: not a[] cancellation entrypoint

Ack.

> 
>> diff --git a/nptl/pthread_getattr_np.c b/nptl/pthread_getattr_np.c
>> index 43dd16d59c..f6d7526041 100644
>> --- a/nptl/pthread_getattr_np.c
>> +++ b/nptl/pthread_getattr_np.c
>> @@ -52,7 +52,7 @@ __pthread_getattr_np (pthread_t thread_id, pthread_attr_t *attr)
>>    iattr->flags = thread->flags;
>>  
>>    /* The thread might be detached by now.  */
>> -  if (IS_DETACHED (thread))
>> +  if (atomic_load_acquire (&thread->joinstate) == THREAD_STATE_DETACHED)
>>      iattr->flags |= ATTR_FLAG_DETACHSTATE;
> 
> Should use relaxed, really.

Ack.

> 
>> diff --git a/nptl/pthread_tryjoin.c b/nptl/pthread_tryjoin.c
>> index 54b528fd19..ff5f1e7faa 100644
>> --- a/nptl/pthread_tryjoin.c
>> +++ b/nptl/pthread_tryjoin.c
>> @@ -21,15 +21,24 @@
>>  int
>>  __pthread_tryjoin_np (pthread_t threadid, void **thread_return)
>>  {
>> +  /* The joinable state (THREAD_STATE_JOINABLE) is straightforward: the thread
>> +     hasn't finished yet, so trying to join might block.
>>  
>> +     The exiting thread (THREAD_STATE_EXITING) also might result in a blocking
>> +     call: a detached thread might change its state to exiting, and an exiting
>> +     thread might take some time to exit (and thus let the kernel set the
>> +     state to THREAD_STATE_EXITED).
>> +
>> +     The ‘joinstate’ does not change during the thread lifetime once the
>> +     kernel sets it to THREAD_STATE_EXITED.  The __pthread_clockjoin_ex will
>> +     only call the cancellable futex if state is not THREAD_STATE_EXITED, so
>> +     calling it should be safe wrt not making pthread_tryjoin_np a
>> +     cancellable entrypoint (since it is marked as __THROW).  */
>> +
>> +  struct pthread *pd = (struct pthread *) threadid;
>> +  return atomic_load_acquire (&pd->joinstate) != THREAD_STATE_EXITED
>> +	 ? EBUSY
>> +	 : __pthread_clockjoin_ex (threadid, thread_return, 0, NULL, false);
>>  }
>>  versioned_symbol (libc, __pthread_tryjoin_np, pthread_tryjoin_np, GLIBC_2_34);
> 
> Huh.  Isn't that a bug on its own because pthread_join is required to be
> a cancellation point, so it needs to act on the cancellation whether or
> not it blocks or not?

I think this is pre-existent issue, on current code pthread_join will *not*
on cancellation if some other thread is already waiting on the 'joinid':

 80   else if (__glibc_unlikely (atomic_compare_exchange_weak_acquire (&pd->joinid,
 81                                                                    &self,
 82                                                                    NULL)))
 83     /* There is already somebody waiting for the thread.  */
 84     return EINVAL;

And if the thread has already exited (pd->tid equal to 0):

 99       pid_t tid;
100       while ((tid = atomic_load_acquire (&pd->tid)) != 0)
101         {

The easiest solution would to add an __pthread_testcancel () on
__pthread_clockjoin_ex (). I will open a bug and add a fix for this.

> 
>> diff --git a/sysdeps/nptl/pthreadP.h b/sysdeps/nptl/pthreadP.h
>> index 881a37cedd..88dea3c1e2 100644
>> --- a/sysdeps/nptl/pthreadP.h
>> +++ b/sysdeps/nptl/pthreadP.h
> 
>> @@ -518,8 +517,10 @@ extern int __pthread_setcanceltype (int type, int *oldtype);
>>  libc_hidden_proto (__pthread_setcanceltype)
>>  extern void __pthread_testcancel (void);
>>  libc_hidden_proto (__pthread_testcancel)
>> +
>>  extern int __pthread_clockjoin_ex (pthread_t, void **, clockid_t,
>> -				   const struct __timespec64 *, bool)
>> +				   const struct __timespec64 *,
>> +				   bool)
>>    attribute_hidden;
> 
> Spurious change?
> 

Indeed.

>>  extern int __pthread_sigmask (int, const sigset_t *, sigset_t *);
>>  libc_hidden_proto (__pthread_sigmask);
>> diff --git a/sysdeps/pthread/tst-thrd-detach.c b/sysdeps/pthread/tst-thrd-detach.c
>> index 966e7c1289..fa8d4181f3 100644
>> --- a/sysdeps/pthread/tst-thrd-detach.c
>> +++ b/sysdeps/pthread/tst-thrd-detach.c
> 
>> @@ -43,6 +46,7 @@ do_test (void)
>>    /* Give some time so the thread can finish.  */
>>    thrd_sleep (&(struct timespec) {.tv_sec = 2}, NULL);
>>  
>> +  /* Calling thrd_join on a detached thread is UB... */
>>    if (thrd_join (id, NULL) == thrd_success)
>>      FAIL_EXIT1 ("thrd_join succeed where it should fail");
> 
> typo: succeed[ed]
> 

It is pre-existent type, I will add a fix.

> Thanks,
> Florian
> 



More information about the Libc-alpha mailing list