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

Florian Weimer fweimer@redhat.com
Mon Dec 8 22:19:16 GMT 2025


* 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.

> @@ -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.

> 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

> 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.

> 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?

> 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?

>  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]

Thanks,
Florian



More information about the Libc-alpha mailing list