[PATCH v6] linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558, 27895, 29705, 32833)

Carlos O'Donell carlos@redhat.com
Mon Jul 6 22:09:59 GMT 2026


On 5/14/26 7:14 AM, Adhemerval Zanella wrote:
> The current timer_create SIGEV_THREAD implementation has some
> downsides:

Three issues:

1. timerid_clear is unused but still defined. Please delete.
2. Incomplete sentence "(or force a fresh allocation if)"?
3. Conformance of pthread_cancel vs notification thread. See POC.

>    1. There is no way to report failure at thread creation when a
>       timer triggers.  It means that it might occur unreported and with
>       missed events depending of the system load.

OK. This is indeed the most serious issue. It prevents the use of SIGEV_THREAD
in functional safety contexts.

>    2. The background thread is also kept in the background even when there
>       are no more timers, consuming resources and also misleading memory
>       profile tools (BZ 29705).

OK. When we delete the timer the helper thread is exited.

>    3. There is a lot of metadata that needs to be kept: a control
>       variable for helper thread creation, a list of active SIGEV_THREAD
>       timers, atfork handlers to cleanup the list.

OK. We drop all of that.

>    4. timer_create does not propagate all thread attributes to the new
>       thread (BZ 27895).

OK. We fix that.

>    5. Kernel might deliver in-flight events for a timer after it was
>       destroyed by timer_delete.  The timer_helper_thread mechanism to
>       handle it does not cover all possible issue, which leads to
>       callbacks being wrongly triggered (BZ 32833).

OK.

> This new implementation moves the thread creation to timer_create, so
> any failure is reported to the caller.  Also, the same thread will
> serve multiple timers, thus there are no unreported missed events.
> Avoiding parallel timer activation also avoids possible parallel
> timer invocations seeing the same overrun value.

OK.

> To implement using SIGTIMER internally as SIGCANCEL, it requires to
> mask out SIGCANCEL on thread creation.  It essentially disables async
> thread cancellation, but POSIX requires that SIGEV_THREAD is always
> created in detached mode and cancelling a detached thread is UB (glibc
> checks the internal tid, but the memory referenced by pthread_t might
> not always be valid at the moment of a pthread_cancel call).

Unfortunately it is not UB if another thread can prove the thread is
alive, and you can, which creates a conformance issue (POC included).

For example:

thread A
-> timer_create, SIGEV_THREAD
-> timer fires

	thread S
	-> sigwaitinfo returns.
	-> run handler
		-> Call pthread_self
		-> Pass result to thread B	thread B
		-> enable async cancel		-> Read pthread_t
		-> while (1);			-> pthread_cancel on S

It's possible the handler is passing data to other worker threads
and is sharing enough information to guarnatee that it is alive (and
that pthread_t is valid despite detached state), and needs asynchronous
cancellation, at which point it is valid to act on pthread_cancel().

The implementation prevents asynchronous cancellation from being used
because of the re-used same-signal, with the pthread_cancel() call not
acting.

Do we need another internal signal for this?

Or can we just reject pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, ...)
for the helper thread and document this issue?

A signal needs to be delivered to interrupt the process, but to do that
requires unblocking a signal. We have SIGSETXID unblocked. Could
cancellation requests also be sent via the multiplexed mechansim?

Assisted-by: LLM
~~~
/* POSIX requires SIGEV_THREAD notifications to behave as if a new thread
    was created for each delivery.  Such a thread must honor asynchronous
    cancellation once the notify function enables it.

    glibc's SIGEV_THREAD helper blocks all signals except SIGSETXID at thread
    creation (see timer_create_sigev_thread), which prevents SIGCANCEL from
    being delivered.  This test demonstrates the resulting non-conformance:
    pthread_cancel on the pthread_t obtained from pthread_self inside the
    notify function has no effect and the test times out.  */

#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

#include <support/check.h>

static pthread_t handler_thread;
static sem_t handler_started;

static void
on_timer (union sigval sv)
{
   handler_thread = pthread_self ();
   if (sem_post (&handler_started) != 0)
     FAIL_EXIT1 ("sem_post from handler: %m");

   TEST_COMPARE (pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL), 0);
   TEST_COMPARE (pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL), 0);

   while (1)
     ;
}

static int
do_test (void)
{
   if (sem_init (&handler_started, 0, 0) != 0)
     FAIL_EXIT1 ("sem_init: %m");

   timer_t timerid;
   struct sigevent ev =
     {
       .sigev_notify = SIGEV_THREAD,
       .sigev_notify_function = on_timer,
     };
   if (timer_create (CLOCK_REALTIME, &ev, &timerid) == -1)
     FAIL_EXIT1 ("timer_create: %m");

   struct itimerspec its =
     { .it_value = { .tv_nsec = 10000000 /* 0.01s */ } };
   if (timer_settime (timerid, 0, &its, NULL) == -1)
     FAIL_EXIT1 ("timer_settime: %m");

   if (sem_wait (&handler_started) != 0)
     FAIL_EXIT1 ("sem_wait: %m");

   /* Give the handler time to enter the busy loop.  */
   usleep (10000);

   if (pthread_cancel (handler_thread) != 0)
     FAIL_EXIT1 ("pthread_cancel: %m");

   /* A conforming implementation cancels the notify thread; detect that by
      waiting until pthread_kill no longer finds it.  With the current glibc
      SIGEV_THREAD helper this never happens and the test times out.  */
   for (;;)
     {
       int err = pthread_kill (handler_thread, 0);
       if (err == ESRCH)
	break;
       if (err != 0)
	FAIL_EXIT1 ("pthread_kill: %m");
       usleep (10000);
     }

   struct itimerspec its_stop = { 0 };
   if (timer_settime (timerid, 0, &its_stop, NULL) == -1)
     FAIL_EXIT1 ("timer_settime (disarm): %m");
   if (timer_delete (timerid) == -1)
     FAIL_EXIT1 ("timer_delete: %m");

   if (sem_destroy (&handler_started) != 0)
     FAIL_EXIT1 ("sem_destroy: %m");

   return 0;
}

#define TIMEOUT 3
#include <support/test-driver.c>
~~~


> And to avoid the need to recreate the thread for pthread_exit call
> (and having possible unreported missed events due to failed thread
> creation), the SIGEV_THREAD installs a cleanup handler that resets all
> internal thread state.
> 
> It also prevents the re-use issue when a newly-allocated timer has
> in-flight events being delivered by the kernel (BZ 32833).
> 
> Performance-wise it uses less CPU time for multiple thread activations,
> although each thread now requires a sigwaitinfo which generates more
> context-switches/page-faults (check comment 7 from BZ 30558).  I would
> expect that latency should improve, since it avoids a thread creation
> for each timer expiration.
> 
> Checked on aarch64-linux-gnu, x86_64-linux-gnu and i686-linux-gnu.
> --
> Changes from v6:
> - __pthread_init_stack no longer resets getrandom_buf; the per-firing
>    reset was orphaning the vDSO buffer.  Cleanup is left to
>    __getrandom_vdso_release on thread exit.
> - Do not reset timerid on cached-stack reuse: every reader sees a
>    value written by timer_create_sigev_thread before the setup barrier.
> - Document the per-firing cleanup in timer_helper_thread: POSIX
>    requires SIGEV_THREAD to behave as if a fresh thread serviced each
>    notification.
> - timerid_to_kernel_timer uses atomic_load_relaxed; the prior plain
>    load raced with timer_delete's atomic_fetch_or_relaxed.
> - timer_create_sigev_thread switched timer_create from
>    INLINE_SYSCALL_CALL to INTERNAL_SYSCALL_CALL so errno is set only
>    after __pthread_barrier_wait, avoiding clobbering on the failure
>    path.
> 
> Changes from v5:
> - Call kernel timer_delete synchronously in ___timer_delete.  This
>    closes the window where the kernel could generate new timer events
>    after timer_delete returned to the caller.
> - Use relaxed MO for the read of self->timerid.  Both this load and the
>    relaxed OR store in timerid_signal_delete are relaxed; the happens-before
>    ordering between them is provided by the tgkill/sigwaitinfo signal
>    mechanism, not by the atomic operations.
> - __pthread_attr_copy error propagation.
> - Rewrote rt/tst-timer7.c synchronization to avoid possible deadlock
>    if scheduling forces the timer to trigger at same time.
> - Extended some comment and fixed some typos.
> 
> Changes from v4:
> - Proper setup the thread TLS after each iteration and add a new
>    test (tst-timer7.c) to check it.
> - Fix the build against some abi due missing clockid_t definition.
> 
> Changes from v3:
> - Move thread reset state to __pthread_reset_state on pthread_create.c.
> - Fixed pthread_attr_t leak after copy.
> - Fixed struct pthread timerid placement.
> 
> Changes from v2:
> - Fixed some issues with timer_delete due using timeid to signal the
>    thread.
> - Added BZ#32833 as fixed bug.
> - Rebased against master.
> ---
>   nptl/allocatestack.c                          |  23 +-
>   nptl/descr.h                                  |   3 +
>   nptl/pthread_create.c                         |  80 ++++++
>   rt/Makefile                                   |   5 +-
>   rt/tst-timer-sigmask.c                        |   7 +-
>   rt/tst-timer6.c                               |  79 +++++
>   rt/tst-timer7.c                               |  91 ++++++
>   sysdeps/nptl/Makefile                         |   2 -
>   sysdeps/nptl/fork.h                           |   2 -
>   sysdeps/nptl/pthreadP.h                       |  14 +
>   sysdeps/unix/sysv/linux/internal-signals.h    |   8 -
>   .../unix/sysv/linux/kernel-posix-cpu-timers.h |   2 +
>   sysdeps/unix/sysv/linux/kernel-posix-timers.h |  80 +++---
>   sysdeps/unix/sysv/linux/timer_create.c        | 269 ++++++++++++------
>   sysdeps/unix/sysv/linux/timer_delete.c        |  50 ++--
>   sysdeps/unix/sysv/linux/timer_routines.c      | 154 ----------
>   16 files changed, 502 insertions(+), 367 deletions(-)
>   create mode 100644 rt/tst-timer6.c
>   create mode 100644 rt/tst-timer7.c
>   delete mode 100644 sysdeps/unix/sysv/linux/timer_routines.c
> 
> diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
> index b2ecb001136..f4eb62369b1 100644
> --- a/nptl/allocatestack.c
> +++ b/nptl/allocatestack.c
> @@ -124,28 +124,7 @@ get_cached_stack (size_t *sizep, void **memp)
>     *sizep = result->stackblock_size;
>     *memp = result->stackblock;
>   
> -  /* Cancellation handling is back to the default.  */
> -  result->cancelhandling = 0;
> -  result->cleanup = NULL;
> -  result->setup_failed = 0;
> -
> -  /* No pending event.  */
> -  result->nextevent = NULL;
> -
> -  result->exiting = false;
> -  __libc_lock_init (result->exit_lock);
> -  memset (&result->tls_state, 0, sizeof result->tls_state);
> -
> -  result->getrandom_buf = NULL;
> -
> -  /* Clear the DTV.  */
> -  dtv_t *dtv = GET_DTV (TLS_TPADJ (result));
> -  for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
> -    free (dtv[1 + cnt].pointer.to_free);
> -  memset (dtv, '\0', (dtv[-1].counter + 1) * sizeof (dtv_t));
> -
> -  /* Re-initialize the TLS.  */
> -  _dl_allocate_tls_init (TLS_TPADJ (result), false);
> +  __pthread_init_stack (result);
>   
>     return result;
>   }
> diff --git a/nptl/descr.h b/nptl/descr.h
> index 627cc3980f0..520307c9738 100644
> --- a/nptl/descr.h
> +++ b/nptl/descr.h
> @@ -414,6 +414,9 @@ struct pthread
>     /* Used on strsignal.  */
>     struct tls_internal_t tls_state;
>   
> +  /* POSIX per-process timer.  */
> +  int timerid;
> +
>     /* getrandom vDSO per-thread opaque state.  */
>     void *getrandom_buf;
>   
> diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
> index 9a0cefb0f5a..12e5e11a0ba 100644
> --- a/nptl/pthread_create.c
> +++ b/nptl/pthread_create.c
> @@ -92,6 +92,41 @@ late_init (void)
>   			 NULL, __NSIG_BYTES);
>   }
>   
> +static void
> +__pthread_init_stack (struct pthread *result)
> +{
> +  /* Cancellation handling is back to the default.  */
> +  result->cancelhandling = 0;
> +  result->cleanup = NULL;
> +  result->setup_failed = 0;
> +
> +  /* No pending event.  */
> +  result->nextevent = NULL;
> +
> +  result->exiting = false;
> +  __libc_lock_init (result->exit_lock);
> +  memset (&result->tls_state, 0, sizeof result->tls_state);
> +
> +  /* getrandom_buf and timerid are intentionally not reset here:
> +
> +     - getrandom_buf: the prior thread's exit path calls
> +       __getrandom_vdso_release, and resetting it here in would orphan a live
> +       buffer (or force a fresh allocation if).

Missing word after "if"?

> +
> +     - timerid: it is meaningful only for SIGEV_THREAD helper threads, and
> +       its thread setup unconditionally writes before releasing the helper
> +       thread from its setup barrier.  */
> +
> +  /* Clear the DTV.  */
> +  dtv_t *dtv = GET_DTV (TLS_TPADJ (result));
> +  for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
> +    free (dtv[1 + cnt].pointer.to_free);
> +  memset (dtv, '\0', (dtv[-1].counter + 1) * sizeof (dtv_t));
> +
> +  /* Re-initialize the TLS.  */
> +  _dl_allocate_tls_init (TLS_TPADJ (result), false);
> +}
> +
>   /* Code to allocate and deallocate a stack.  */
>   #include "allocatestack.c"
>   
> @@ -644,6 +679,51 @@ report_thread_creation (struct pthread *pd)
>     return false;
>   }
>   
> +/* Reset internal thread state as if the start thread routine was initially
> +   called from pthread_create.  It is used on POSIX timers to reset the
> +   SIGEV_THREAD thread after a timer activation (as required by POSIX in
> +   Realtime Signal Generation and Delivery): each firing must behave as if a
> +   fresh thread was created, so TLS destructors, TSD destructors, libc
> +   per-thread state, the DTV, the signal mask, and cancellation state are all
> +   reset.
> +
> +   The per-thread vDSO getrandom buffer (getrandom_buf) is *not* reset here.
> +   It is internal, opaque state that advances forward-securely on each use, so
> +   no observable data leaks across firings.
> +
> +   The kernel timer id (timerid) is also *not* reset here: it identifies the
> +   active timer for this helper thread and timer_delete signals exit by
> +   setting its MSB, which the helper thread checks after each firing.  */
> +void
> +__pthread_reset_state (void *arg)
> +{
> +  struct pthread *self = THREAD_SELF;
> +
> +  /* Call destructors for the thread_local TLS variables.  */
> +  call_function_static_weak (__call_tls_dtors);
> +
> +  /* Run the destructor for the thread-local data.  */
> +  __nptl_deallocate_tsd ();
> +
> +  /* Clean up any state libc stored in thread-local variables.  */
> +  __libc_thread_freeres ();
> +
> +  /* Reset internal TCB state.  */
> +  struct pthread_reset_cleanup_args_t *args = arg;
> +  self->cleanup_jmp_buf = args->cleanup_jmp_buf;
> +  self->cleanup_jmp_buf->priv.data.prev = NULL;
> +  self->cleanup_jmp_buf->priv.data.cleanup = NULL;
> +  self->cleanup_jmp_buf->priv.data.canceltype = 0;
> +  self->cleanup = NULL;
> +  self->exc = (struct _Unwind_Exception) { 0 };
> +  self->cancelhandling = 0;
> +  self->nextevent = NULL;
> +
> +  __pthread_init_stack (self);
> +
> +  /* Reset to the expected initial signal mask.  */
> +  internal_signal_restore_set (&self->sigmask);
> +}
>   
>   int
>   __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
> diff --git a/rt/Makefile b/rt/Makefile
> index 39a3e5620b3..2fe4d7ba6b1 100644
> --- a/rt/Makefile
> +++ b/rt/Makefile
> @@ -76,7 +76,9 @@ tests := tst-shm tst-timer tst-timer2 \
>   	 tst-cpuclock2 tst-cputimer1 tst-cputimer2 tst-cputimer3 \
>   	 tst-clock_nanosleep2 \
>   	 tst-shm-cancel \
> -	 tst-mqueue10
> +	 tst-mqueue10 \
> +	 tst-timer6 \
> +	 tst-timer7
>   tests-internal := tst-timer-sigmask
>   
>   tests-time64 := \
> @@ -98,6 +100,7 @@ include ../Rules
>   CFLAGS-aio_suspend.c += -fexceptions
>   CFLAGS-mq_timedreceive.c += -fexceptions -fasynchronous-unwind-tables
>   CFLAGS-mq_timedsend.c += -fexceptions -fasynchronous-unwind-tables
> +CFLAGS-timer_create.c += -fexceptions -fasynchronous-unwind-tables
>   
>   # Exclude fortified routines from being built with _FORTIFY_SOURCE
>   routines_no_fortify += \
> diff --git a/rt/tst-timer-sigmask.c b/rt/tst-timer-sigmask.c
> index 869194bde68..52f44bd5666 100644
> --- a/rt/tst-timer-sigmask.c
> +++ b/rt/tst-timer-sigmask.c
> @@ -39,12 +39,9 @@ thread_handler (union sigval sv)
>     for (int sig = 1; sig < NSIG; sig++)
>       {
>         /* POSIX timers threads created to handle SIGEV_THREAD block all
> -	 signals except SIGKILL, SIGSTOP and glibc internals ones.  */
> +	 signals except SIGKILL, SIGSTOP, and SIGSETXID.  */
>         if (sigismember (&ss, sig))
> -	{
> -	  TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
> -	  TEST_VERIFY (!is_internal_signal (sig));
> -	}
> +	TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP && sig != SIGSETXID);
>         if (test_verbose && sigismember (&ss, sig))
>   	printf ("%d, ", sig);
>       }
> diff --git a/rt/tst-timer6.c b/rt/tst-timer6.c
> new file mode 100644
> index 00000000000..545bfa6695b
> --- /dev/null
> +++ b/rt/tst-timer6.c
> @@ -0,0 +1,79 @@
> +/* Check re-use timer id for SIGEV_THREAD (BZ 32833)
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public License as
> +   published by the Free Software Foundation; either version 2.1 of the
> +   License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; see the file COPYING.LIB.  If
> +   not, see <https://www.gnu.org/licenses/>.  */
> +
> +#include <signal.h>
> +#include <time.h>
> +#include <support/check.h>
> +
> +/* The test depends of the system load and scheduler pressure, so the
> +   number of iteration is arbitrary to not take too much time.  */
> +enum { niters = 1<<13 };
> +
> +static void
> +on_good_timer (union sigval sv)
> +{
> +}
> +
> +static void
> +on_bad_timer (union sigval sv)
> +{
> +  FAIL_EXIT1 ("triggered bad timer");
> +}
> +
> +static int
> +do_test (void)
> +{
> +  struct itimerspec its_long =  { .it_value = { .tv_sec = 180 } };
> +  struct itimerspec its_short = { .it_value = { .tv_nsec = 1000 } };
> +  struct itimerspec its_zero =  { .it_interval = { .tv_sec = 0 } };
> +
> +  struct sigevent ev_short =
> +    {
> +      .sigev_notify = SIGEV_THREAD,
> +      .sigev_notify_function = on_good_timer,
> +    };
> +
> +  struct sigevent ev_long =
> +    {
> +      .sigev_notify = SIGEV_THREAD,
> +      .sigev_notify_function = on_bad_timer,
> +    };
> +
> +  for (int which = 0; which < niters; which++)
> +    {
> +      struct sigevent *ev = which & 0x1 ? &ev_short : &ev_long;
> +      struct itimerspec *its = which & 0x1 ? &its_short : &its_long;
> +
> +      timer_t timerid;
> +      if (timer_create (CLOCK_REALTIME, ev, &timerid) == -1)
> +	FAIL_EXIT1 ("timer_create: %m");
> +
> +      if (timer_settime (timerid, 0, its, NULL) == -1)
> +	FAIL_EXIT1 ("timer_settime: %m");
> +
> +      if (timer_settime (timerid, 0, &its_zero, NULL) == -1)
> +	FAIL_EXIT1 ("timer_settime: %m");
> +
> +      if (timer_delete (timerid) == -1)
> +	FAIL_EXIT1 ("timer_delete: %m");
> +    }
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/rt/tst-timer7.c b/rt/tst-timer7.c
> new file mode 100644
> index 00000000000..80663c1c222
> --- /dev/null
> +++ b/rt/tst-timer7.c
> @@ -0,0 +1,91 @@
> +/* Check if thread local storage is reset on each SIGEV_THREAD trigger.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public License as
> +   published by the Free Software Foundation; either version 2.1 of the
> +   License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; see the file COPYING.LIB.  If
> +   not, see <https://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <semaphore.h>
> +#include <signal.h>
> +#include <string.h>
> +#include <time.h>
> +
> +#include <support/check.h>
> +
> +static sem_t sem;
> +
> +static __thread int var1;
> +#define VAR2_LEN 32
> +static __thread char var2[] = { [0 ... VAR2_LEN] = 0xcc };
> +
> +static const char var2_expected[] = { [0 ... VAR2_LEN] = 0xcc };
> +
> +static void
> +on_timer (union sigval sv)
> +{
> +  TEST_COMPARE (var1, 0);
> +  TEST_COMPARE_BLOB (var2, array_length (var2),
> +		     var2_expected, array_length (var2_expected));
> +
> +  var1 = 1;
> +  memset (var2, 0x00, array_length (var2));
> +
> +  sem_post (&sem);
> +}
> +
> +#define NITERS 10
> +
> +static int
> +do_test (void)
> +{
> +  const struct itimerspec its =
> +    { .it_value    = { .tv_nsec = 10000000 /* 0.01s */ },
> +      .it_interval = { .tv_nsec = 10000000 /* 0.01s */ } };
> +  const struct itimerspec its_stop = { 0 };
> +
> +  sem_init (&sem, 0, 0);
> +
> +  timer_t timerid;
> +  struct sigevent ev =
> +    {
> +      .sigev_notify = SIGEV_THREAD,
> +      .sigev_notify_function = on_timer,
> +    };
> +  if (timer_create (CLOCK_REALTIME, &ev, &timerid) == -1)
> +    FAIL_EXIT1 ("timer_create: %m");
> +
> +  if (timer_settime (timerid, 0, &its, NULL) == -1)
> +    FAIL_EXIT1 ("timer_settime: %m");
> +
> +  for (int i = 0; i < NITERS; i++)
> +    {
> +      if (sem_wait (&sem) != 0)
> +	FAIL_EXIT1 ("sem_wait: %m");
> +    }
> +
> +  /* Disarm before deleting to minimise the chance of an in-flight
> +     invocation racing with timer_delete.  */
> +  if (timer_settime (timerid, 0, &its_stop, NULL) == -1)
> +    FAIL_EXIT1 ("timer_settime: %m");
> +
> +  if (timer_delete (timerid) == -1)
> +    FAIL_EXIT1 ("timer_delete: %m");
> +
> +  sem_destroy (&sem);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/nptl/Makefile b/sysdeps/nptl/Makefile
> index 03c9c056401..47f5ba5824f 100644
> --- a/sysdeps/nptl/Makefile
> +++ b/sysdeps/nptl/Makefile
> @@ -16,8 +16,6 @@
>   # <https://www.gnu.org/licenses/>.
>   
>   ifeq ($(subdir),rt)
> -sysdep_routines += timer_routines
> -
>   tests += tst-mqueue8x
>   CFLAGS-tst-mqueue8x.c += -fexceptions
>   endif
> diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h
> index c09e57c5abd..31ac12812f3 100644
> --- a/sysdeps/nptl/fork.h
> +++ b/sysdeps/nptl/fork.h
> @@ -20,7 +20,6 @@
>   #define _FORK_H
>   
>   #include <assert.h>
> -#include <kernel-posix-timers.h>
>   #include <ldsodefs.h>
>   #include <list.h>
>   #include <mqueue.h>
> @@ -46,7 +45,6 @@ fork_system_setup_after_fork (void)
>     __default_pthread_attr_lock = LLL_LOCK_INITIALIZER;
>   
>     call_function_static_weak (__mq_notify_fork_subprocess);
> -  call_function_static_weak (__timer_fork_subprocess);
>     call_function_static_weak (__getrandom_fork_subprocess);
>   }
>   
> diff --git a/sysdeps/nptl/pthreadP.h b/sysdeps/nptl/pthreadP.h
> index de432d40324..1470aaf4856 100644
> --- a/sysdeps/nptl/pthreadP.h
> +++ b/sysdeps/nptl/pthreadP.h
> @@ -667,6 +667,20 @@ int __pthread_attr_extension (struct pthread_attr *attr) attribute_hidden
>   # define PTHREAD_STATIC_FN_REQUIRE(name) __asm (".globl " #name);
>   #endif
>   
> +struct pthread_reset_cleanup_args_t
> +{
> +  struct pthread_unwind_buf *cleanup_jmp_buf;
> +  jmp_buf jb;
> +};
> +
> +/* Reset the thread's internal state to a point as close to the initial call
> +   to pthread_create.   It is designed to be used as the argument to
> +   pthread_cleanup_push along with a struct pthread_reset_cleanup_args_t
> +   pointer in args with a valid cleanup_jmp_buf used to reset the threads own
> +   copy.  */
> +void __pthread_reset_state (void *arg) attribute_hidden;
> +
> +
>   /* Make a deep copy of the attribute *SOURCE in *TARGET.  *TARGET is
>      not assumed to have been initialized.  Returns 0 on success, or a
>      positive error code otherwise.  */
> diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h
> index a8f1c87b463..02a086f7d5d 100644
> --- a/sysdeps/unix/sysv/linux/internal-signals.h
> +++ b/sysdeps/unix/sysv/linux/internal-signals.h
> @@ -108,12 +108,4 @@ static const sigset_t sigtimer_set = {
>     }
>   };
>   
> -/* Unblock only SIGTIMER.  */
> -static inline void
> -signal_unblock_sigtimer (void)
> -{
> -  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_UNBLOCK, &sigtimer_set, NULL,
> -			 __NSIG_BYTES);
> -}
> -
>   #endif
> diff --git a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
> index bea1e0e62da..c95b504d5f6 100644
> --- a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
> +++ b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
> @@ -20,6 +20,8 @@
>   #define CPUCLOCK_SCHED		2
>   #define CPUCLOCK_MAX		3
>   
> +#include <sys/types.h>
> +
>   static inline clockid_t
>   make_process_cpuclock (unsigned int pid, clockid_t clock)
>   {
> diff --git a/sysdeps/unix/sysv/linux/kernel-posix-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-timers.h
> index 9b7859b1c65..ac94fbd601d 100644
> --- a/sysdeps/unix/sysv/linux/kernel-posix-timers.h
> +++ b/sysdeps/unix/sysv/linux/kernel-posix-timers.h
> @@ -19,29 +19,7 @@
>   #include <setjmp.h>
>   #include <signal.h>
>   #include <sys/types.h>
> -
> -
> -/* Nonzero if the system calls are not available.  */
> -extern int __no_posix_timers attribute_hidden;
> -
> -/* Callback to start helper thread.  */
> -extern void __timer_start_helper_thread (void) attribute_hidden;
> -
> -/* Control variable for helper thread creation.  */
> -extern pthread_once_t __timer_helper_once attribute_hidden;
> -
> -/* Called from fork so that the new subprocess re-creates the
> -   notification thread if necessary.  */
> -void __timer_fork_subprocess (void) attribute_hidden;
> -
> -/* TID of the helper thread.  */
> -extern pid_t __timer_helper_tid attribute_hidden;
> -
> -/* List of active SIGEV_THREAD timers.  */
> -extern struct timer *__timer_active_sigev_thread attribute_hidden;
> -
> -/* Lock for __timer_active_sigev_thread.  */
> -extern pthread_mutex_t __timer_active_sigev_thread_lock attribute_hidden;
> +#include <nptl/descr.h>
>   
>   extern __typeof (timer_create) __timer_create;
>   libc_hidden_proto (__timer_create)
> @@ -53,25 +31,12 @@ libc_hidden_proto (__timer_getoverrun)
>   /* Type of timers in the kernel.  */
>   typedef int kernel_timer_t;
>   
> -/* Internal representation of SIGEV_THREAD timer.  */
> -struct timer
> -{
> -  kernel_timer_t ktimerid;
> -
> -  void (*thrfunc) (sigval_t);
> -  sigval_t sival;
> -  pthread_attr_t attr;
> -
> -  /* Next element in list of active SIGEV_THREAD timers.  */
> -  struct timer *next;
> -};
> -
> -
>   /* For !SIGEV_THREAD, the resulting 'timer_t' is the returned kernel timer
> -   identifier (kernel_timer_t), while for SIGEV_THREAD it uses the fact malloc
> -   returns at least _Alignof (max_align_t) pointers plus that valid
> -   kernel_timer_t are always positive to set the MSB bit of the returned
> -   'timer_t' to indicate the timer handles a SIGEV_THREAD.  */
> +   identifier (kernel_timer_t), while for SIGEV_THREAD it assumes the
> +   pthread_t at least 8-bytes aligned.
> +
> +   For SIGEV_THREAD, the sign bit (INT_MIN) is set on timer_delete to
> +   signal the helper thread to exit its sigwaitinfo loop.  */
>   
>   static inline timer_t
>   kernel_timer_to_timerid (kernel_timer_t ktimerid)
> @@ -80,7 +45,7 @@ kernel_timer_to_timerid (kernel_timer_t ktimerid)
>   }
>   
>   static inline timer_t
> -timer_to_timerid (struct timer *ptr)
> +pthread_to_timerid (pthread_t ptr)
>   {
>     return (timer_t) (INTPTR_MIN | (uintptr_t) ptr >> 1);
>   }
> @@ -91,19 +56,38 @@ timer_is_sigev_thread (timer_t timerid)
>     return (intptr_t) timerid < 0;
>   }
>   
> -static inline struct timer *
> -timerid_to_timer (timer_t timerid)
> +static inline struct pthread *
> +timerid_to_pthread (timer_t timerid)
>   {
> -  return (struct timer *)((uintptr_t) timerid << 1);
> +  return (struct pthread *)((uintptr_t) timerid << 1);
>   }
>   
>   static inline kernel_timer_t
>   timerid_to_kernel_timer (timer_t timerid)
>   {
>     if (timer_is_sigev_thread (timerid))
> -    return timerid_to_timer (timerid)->ktimerid;
> -  else
> -    return (kernel_timer_t) ((uintptr_t) timerid);
> +    {
> +      /* The load is concurrent with timer_delete's atomic OR of INT_MIN (see
> +	 timerid_signal_delete) when another thread is deleting the same
> +	 timer.  */
> +      struct pthread *pthr = timerid_to_pthread (timerid);
> +      return atomic_load_relaxed (&pthr->timerid) & INT_MAX;
> +    }
> +  return (uintptr_t) timerid;
> +}
> +
> +static inline void
> +timerid_signal_delete (kernel_timer_t *timerid)
> +{
> +  /* Relaxed MO is sufficient because this is followed by a signal which is a
> +     full memory barrier.  */
> +  atomic_fetch_or_relaxed (timerid, INT_MIN);
> +}
> +
> +static inline kernel_timer_t
> +timerid_clear (kernel_timer_t timerid)
> +{
> +  return timerid & INT_MAX;
>   }
>   
>   /* New targets use int instead of timer_t.  The difference only
> diff --git a/sysdeps/unix/sysv/linux/timer_create.c b/sysdeps/unix/sysv/linux/timer_create.c
> index 0889ce66f5e..d293892ecf8 100644
> --- a/sysdeps/unix/sysv/linux/timer_create.c
> +++ b/sysdeps/unix/sysv/linux/timer_create.c
> @@ -15,46 +15,174 @@
>      License along with the GNU C Library; see the file COPYING.LIB.  If
>      not, see <https://www.gnu.org/licenses/>.  */
>   
> -#include <errno.h>
> -#include <pthread.h>
> -#include <signal.h>
> -#include <stdlib.h>
> -#include <string.h>
> -#include <time.h>
> -#include <sysdep.h>
> -#include <internaltypes.h>
> +#include <jmpbuf-unwind.h>
> +#include <kernel-posix-cpu-timers.h>
> +#include <kernel-posix-timers.h>
> +#include <ldsodefs.h>
> +#include <libc-internal.h>
> +#include <libc-lock.h>
>   #include <pthreadP.h>
> -#include "kernel-posix-timers.h"
> -#include "kernel-posix-cpu-timers.h"
>   #include <shlib-compat.h>
>   
> +struct timer_helper_thread_args_t
> +{
> +  /* The barrier is used to synchronize the arguments copy from timer_create
> +     and the SIGEV_THREAD thread and to instruct the thread to exit if the
> +     timer_create syscall fails.  */
> +  pthread_barrier_t barrier;
> +  struct sigevent *evp;
> +};
> +
> +static void *
> +timer_helper_thread (void *arg)
> +{
> +  struct pthread *self = THREAD_SELF;
> +  struct timer_helper_thread_args_t *args = arg;
> +  struct pthread_reset_cleanup_args_t clargs = {
> +    .cleanup_jmp_buf = self->cleanup_jmp_buf
> +  };
> +
> +  void (*thrfunc) (sigval_t) = args->evp->sigev_notify_function;
> +  sigval_t sival = args->evp->sigev_value;
> +
> +  __pthread_barrier_wait (&args->barrier);
> +  /* timer_create syscall failed.  */
> +  if (self->exiting)
> +    return 0;
> +
> +  while (1)
> +    {
> +      siginfo_t si;
> +      while (__sigwaitinfo (&sigtimer_set, &si) < 0);
> +
> +      if (si.si_code == SI_TIMER && !setjmp (clargs.jb))
> +	{
> +	  /* POSIX requires SIGEV_THREAD notifications to behave as if a new
> +	     thread was created for each delivery.  Since the same helper
> +	     thread serves multiple firings (and is also reused after a
> +	     pthread_exit from thrfunc), the cleanup handler resets all
> +	     observable per-thread state - TLS destructors, TSD, libc
> +	     thread-local state, DTV, signal mask, and cancellation state -
> +	     on the way out.  State that must survive across firings is
> +	     intentionally preserved: the vDSO getrandom buffer (opaque,
> +	     forward-secure) and timerid (used to detect timer_delete via
> +	     its MSB after each firing).  */
> +	  pthread_cleanup_push (__pthread_reset_state, &clargs);
> +	  thrfunc (sival);
> +	  pthread_cleanup_pop (1);
> +	}
> +
> +      /* timer_delete sets the MSB and sends SIGTIMER to wake this thread.
> +         Relaxed MO is sufficient because signaling this thread is a memory
> +	 barrier.  */
> +      if (atomic_load_relaxed (&self->timerid) < 0)
> +	break;
> +    }
> +
> +  return NULL;
> +}
> +
> +/* Set up a SIGEV_THREAD timer: spawn the helper thread that will run the
> +   user's notify function on each firing, then create the kernel timer bound
> +   to that thread's TID.  Returns 0 on success and stores the resulting
> +   timer_t in *TIMERID; returns -1 with errno set on failure (from
> +   __pthread_attr_setsigmask_internal, __pthread_create, or the timer_create
> +   syscall).  ATTR is consumed but not destroyed by this function.  */
> +static int
> +timer_create_sigev_thread (clockid_t clockid, struct sigevent *evp,
> +			   timer_t *timerid, pthread_attr_t *attr)
> +{
> +  /* Block all signals in the helper thread but SIGSETXID.  */
> +  sigset_t ss;
> +  __sigfillset (&ss);
> +  __sigdelset (&ss, SIGSETXID);
> +  if (__pthread_attr_setsigmask_internal (attr, &ss) < 0)
> +    return -1;
> +
> +  struct timer_helper_thread_args_t args = { .evp = evp };
> +  __pthread_barrier_init (&args.barrier, NULL, 2);
> +
> +  pthread_t th;
> +  int r = __pthread_create (&th, attr, timer_helper_thread, &args);
> +  if (r != 0)
> +    {
> +      __set_errno (r);
> +      return -1;
> +    }
> +
> +  struct pthread *pthr = (struct pthread *)th;
> +  /* SIGEV_THREAD_ID delivers the signal to a specific thread by TID.
> +     SIGEV_SIGNAL is not combined here because SIGEV_THREAD_ID already
> +     implies signal delivery; the kernel treats them as orthogonal bits
> +     and the TID field alone is sufficient to route SIGTIMER correctly.  */
> +  struct sigevent kevp =
> +    {
> +      .sigev_value.sival_ptr = NULL,
> +      .sigev_signo = SIGTIMER,
> +      .sigev_notify = SIGEV_THREAD_ID,
> +      ._sigev_un = { ._tid = pthr->tid },
> +    };
> +
> +  /* Use INTERNAL_SYSCALL_CALL so the error code can be captured directly
> +     without going through errno; errno is set only after the barrier
> +     wait below, which would otherwise be free to clobber it.  */
> +  kernel_timer_t ktimerid;
> +  long int sc = INTERNAL_SYSCALL_CALL (timer_create, clockid, &kevp,
> +				       &ktimerid);
> +  if (INTERNAL_SYSCALL_ERROR_P (sc))
> +    {
> +      ktimerid = -1;
> +      /* On timer creation failure we need to signal the helper thread to
> +	 exit and we cannot use a negative timerid value after the
> +	 pthread_barrier_wait because we cannot distinguish between a timer
> +	 creation failure and a request to delete a timer if it happens to
> +	 arrive quickly (e.g. two timers are created in sequence, where the
> +	 first succeeds).
> +
> +	 We re-use the 'exiting' member to signal the failure, it is set only
> +	 at pthread_create to prevent pthread_kill from sending further
> +	 signals.  Since the thread should not be user-visible, signals are
> +	 only sent during timer_delete.  */
> +      pthr->exiting = true;
> +    }
> +  pthr->timerid = ktimerid;
> +  /* Signal the thread to continue execution after it copies the arguments
> +     or exit if the timer can not be created.  */
> +  __pthread_barrier_wait (&args.barrier);
> +
> +  if (ktimerid < 0)
> +    {
> +      __set_errno (INTERNAL_SYSCALL_ERRNO (sc));
> +      return -1;
> +    }
> +
> +  *timerid = pthread_to_timerid (th);
> +
> +  return 0;
> +}
> +
>   int
>   ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
>   {
> -  {
> -    clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
> -				 ? PROCESS_CLOCK
> -				 : clock_id == CLOCK_THREAD_CPUTIME_ID
> -				 ? THREAD_CLOCK
> -				 : clock_id);
> +  clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
> +			       ? PROCESS_CLOCK
> +			       : clock_id == CLOCK_THREAD_CPUTIME_ID
> +			       ? THREAD_CLOCK
> +			       : clock_id);
>   
> -    /* If the user wants notification via a thread we need to handle
> -       this special.  */
> -    if (evp == NULL
> -	|| __builtin_expect (evp->sigev_notify != SIGEV_THREAD, 1))
> +  switch (evp != NULL ? evp->sigev_notify : SIGEV_SIGNAL)
> +    {
> +    case SIGEV_NONE:
> +    case SIGEV_SIGNAL:
> +    case SIGEV_THREAD_ID:
>         {
> -	struct sigevent local_evp;
> -
> +	struct sigevent kevp;
>   	if (evp == NULL)
>   	  {
> -	    /* The kernel has to pass up the timer ID which is a
> -	       userlevel object.  Therefore we cannot leave it up to
> -	       the kernel to determine it.  */
> -	    local_evp.sigev_notify = SIGEV_SIGNAL;
> -	    local_evp.sigev_signo = SIGALRM;
> -	    local_evp.sigev_value.sival_ptr = NULL;
> -
> -	    evp = &local_evp;
> +	    kevp.sigev_notify = SIGEV_SIGNAL;
> +	    kevp.sigev_signo = SIGALRM;
> +	    kevp.sigev_value.sival_ptr = NULL;
> +	    evp = &kevp;
>   	  }
>   
>   	kernel_timer_t ktimerid;
> @@ -64,75 +192,34 @@ ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
>   
>   	*timerid = kernel_timer_to_timerid (ktimerid);
>         }
> -    else
> +      break;
> +    case SIGEV_THREAD:
>         {
> -	/* Create the helper thread.  */
> -	__pthread_once (&__timer_helper_once, __timer_start_helper_thread);
> -	if (__timer_helper_tid == 0)
> -	  {
> -	    /* No resources to start the helper thread.  */
> -	    __set_errno (EAGAIN);
> -	    return -1;
> -	  }
> -
> -	struct timer *newp = malloc (sizeof (struct timer));
> -	if (newp == NULL)
> -	  return -1;
> -
> -	/* Copy the thread parameters the user provided.  */
> -	newp->sival = evp->sigev_value;
> -	newp->thrfunc = evp->sigev_notify_function;
> -
> -	/* We cannot simply copy the thread attributes since the
> -	   implementation might keep internal information for
> -	   each instance.  */
> -	__pthread_attr_init (&newp->attr);
> +	pthread_attr_t attr;
>   	if (evp->sigev_notify_attributes != NULL)
>   	  {
> -	    struct pthread_attr *nattr;
> -	    struct pthread_attr *oattr;
> -
> -	    nattr = (struct pthread_attr *) &newp->attr;
> -	    oattr = (struct pthread_attr *) evp->sigev_notify_attributes;
> -
> -	    nattr->schedparam = oattr->schedparam;
> -	    nattr->schedpolicy = oattr->schedpolicy;
> -	    nattr->flags = oattr->flags;
> -	    nattr->guardsize = oattr->guardsize;
> -	    nattr->stackaddr = oattr->stackaddr;
> -	    nattr->stacksize = oattr->stacksize;
> +	    int r = __pthread_attr_copy (&attr, evp->sigev_notify_attributes);
> +	    if (r != 0)
> +	      {
> +		__set_errno (r);
> +		return -1;
> +	      }
>   	  }
> +	else
> +	  __pthread_attr_init (&attr);
> +	__pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
>   
> -	/* In any case set the detach flag.  */
> -	__pthread_attr_setdetachstate (&newp->attr, PTHREAD_CREATE_DETACHED);
> +        int r = timer_create_sigev_thread (syscall_clockid, evp, timerid,
> +					   &attr);
>   
> -	/* Create the event structure for the kernel timer.  */
> -	struct sigevent sev =
> -	  { .sigev_value.sival_ptr = newp,
> -	    .sigev_signo = SIGTIMER,
> -	    .sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID,
> -	    ._sigev_un = { ._pad = { [0] = __timer_helper_tid } } };
> +	__pthread_attr_destroy (&attr);
>   
> -	/* Create the timer.  */
> -	int res;
> -	res = INTERNAL_SYSCALL_CALL (timer_create, syscall_clockid, &sev,
> -				     &newp->ktimerid);
> -	if (INTERNAL_SYSCALL_ERROR_P (res))
> -	  {
> -	    free (newp);
> -	    __set_errno (INTERNAL_SYSCALL_ERRNO (res));
> -	    return -1;
> -	  }
> -
> -	/* Add to the queue of active timers with thread delivery.  */
> -	__pthread_mutex_lock (&__timer_active_sigev_thread_lock);
> -	newp->next = __timer_active_sigev_thread;
> -	__timer_active_sigev_thread = newp;
> -	__pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
> -
> -	*timerid = timer_to_timerid (newp);
> +	return r;
>         }
> -  }
> +    default:
> +      __set_errno (EINVAL);
> +      return -1;
> +    }
>   
>     return 0;
>   }
> diff --git a/sysdeps/unix/sysv/linux/timer_delete.c b/sysdeps/unix/sysv/linux/timer_delete.c
> index 9a6e74328e7..3422342b5ca 100644
> --- a/sysdeps/unix/sysv/linux/timer_delete.c
> +++ b/sysdeps/unix/sysv/linux/timer_delete.c
> @@ -15,10 +15,8 @@
>      License along with the GNU C Library; see the file COPYING.LIB.  If
>      not, see <https://www.gnu.org/licenses/>.  */
>   
> -#include <errno.h>
> -#include <stdlib.h>
> +#include <unistd.h>
>   #include <time.h>
> -#include <sysdep.h>
>   #include "kernel-posix-timers.h"
>   #include <pthreadP.h>
>   #include <shlib-compat.h>
> @@ -26,42 +24,26 @@
>   int
>   ___timer_delete (timer_t timerid)
>   {
> -  kernel_timer_t ktimerid = timerid_to_kernel_timer (timerid);
> -  int res = INLINE_SYSCALL_CALL (timer_delete, ktimerid);
> -
> -  if (res == 0)
> +  if (timer_is_sigev_thread (timerid))
>       {
> -      if (timer_is_sigev_thread (timerid))
> -	{
> -	  struct timer *kt = timerid_to_timer (timerid);
> +      struct pthread *th = timerid_to_pthread (timerid);
> +      kernel_timer_t ktimerid = timerid_to_kernel_timer (timerid);
>   
> -	  /* Remove the timer from the list.  */
> -	  __pthread_mutex_lock (&__timer_active_sigev_thread_lock);
> -	  if (__timer_active_sigev_thread == kt)
> -	    __timer_active_sigev_thread = kt->next;
> -	  else
> -	    {
> -	      struct timer *prevp = __timer_active_sigev_thread;
> -	      while (prevp->next != NULL)
> -		if (prevp->next == kt)
> -		  {
> -		    prevp->next = kt->next;
> -		    break;
> -		  }
> -		else
> -		  prevp = prevp->next;
> -	    }
> -	  __pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
> -
> -	  free (kt);
> -	}
> +      /* Delete the kernel timer first so no new events are generated
> +	 after this function returns.  */
> +      int ret = INLINE_SYSCALL_CALL (timer_delete, ktimerid);
> +      if (ret != 0)
> +	return ret;
>   
> +      /* Signal the helper thread to exit its sigwaitinfo loop.  */
> +      timerid_signal_delete (&th->timerid);
> +      /* We can send the signal directly instead of through
> +	 __pthread_kill_internal because the thread is not user-visible
> +	 and it blocks SIGTIMER.  */
> +      INTERNAL_SYSCALL_CALL (tgkill, __getpid (), th->tid, SIGTIMER);
>         return 0;
>       }
> -
> -  /* The kernel timer is not known or something else bad happened.
> -     Return the error.  */
> -  return -1;
> +  return INLINE_SYSCALL_CALL (timer_delete, timerid);
>   }
>   versioned_symbol (libc, ___timer_delete, timer_delete, GLIBC_2_34);
>   libc_hidden_ver (___timer_delete, __timer_delete)
> diff --git a/sysdeps/unix/sysv/linux/timer_routines.c b/sysdeps/unix/sysv/linux/timer_routines.c
> deleted file mode 100644
> index 7ba6dd78ba7..00000000000
> --- a/sysdeps/unix/sysv/linux/timer_routines.c
> +++ /dev/null
> @@ -1,154 +0,0 @@
> -/* Copyright (C) 2003-2026 Free Software Foundation, Inc.
> -   This file is part of the GNU C Library.
> -
> -   The GNU C Library is free software; you can redistribute it and/or
> -   modify it under the terms of the GNU Lesser General Public License as
> -   published by the Free Software Foundation; either version 2.1 of the
> -   License, or (at your option) any later version.
> -
> -   The GNU C Library is distributed in the hope that it will be useful,
> -   but WITHOUT ANY WARRANTY; without even the implied warranty of
> -   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> -   Lesser General Public License for more details.
> -
> -   You should have received a copy of the GNU Lesser General Public
> -   License along with the GNU C Library; see the file COPYING.LIB.  If
> -   not, see <https://www.gnu.org/licenses/>.  */
> -
> -#include <errno.h>
> -#include <setjmp.h>
> -#include <signal.h>
> -#include <stdbool.h>
> -#include <sysdep-cancel.h>
> -#include <pthreadP.h>
> -#include "kernel-posix-timers.h"
> -
> -
> -/* List of active SIGEV_THREAD timers.  */
> -struct timer *__timer_active_sigev_thread;
> -
> -/* Lock for _timer_active_sigev_thread.  */
> -pthread_mutex_t __timer_active_sigev_thread_lock = PTHREAD_MUTEX_INITIALIZER;
> -
> -struct thread_start_data
> -{
> -  void (*thrfunc) (sigval_t);
> -  sigval_t sival;
> -};
> -
> -
> -/* Helper thread to call the user-provided function.  */
> -static void *
> -timer_sigev_thread (void *arg)
> -{
> -  signal_unblock_sigtimer ();
> -
> -  struct thread_start_data *td = (struct thread_start_data *) arg;
> -  void (*thrfunc) (sigval_t) = td->thrfunc;
> -  sigval_t sival = td->sival;
> -
> -  /* The TD object was allocated in timer_helper_thread.  */
> -  free (td);
> -
> -  /* Call the user-provided function.  */
> -  thrfunc (sival);
> -
> -  return NULL;
> -}
> -
> -
> -/* Helper function to support starting threads for SIGEV_THREAD.  */
> -static _Noreturn void *
> -timer_helper_thread (void *arg)
> -{
> -  /* Endless loop of waiting for signals.  The loop is only ended when
> -     the thread is canceled.  */
> -  while (1)
> -    {
> -      siginfo_t si;
> -
> -      while (__sigwaitinfo (&sigtimer_set, &si) < 0);
> -      if (si.si_code == SI_TIMER)
> -	{
> -	  struct timer *tk = (struct timer *) si.si_ptr;
> -
> -	  /* Check the timer is still used and will not go away
> -	     while we are reading the values here.  */
> -	  __pthread_mutex_lock (&__timer_active_sigev_thread_lock);
> -
> -	  struct timer *runp = __timer_active_sigev_thread;
> -	  while (runp != NULL)
> -	    if (runp == tk)
> -	      break;
> -	  else
> -	    runp = runp->next;
> -
> -	  if (runp != NULL)
> -	    {
> -	      struct thread_start_data *td = malloc (sizeof (*td));
> -
> -	      /* There is not much we can do if the allocation fails.  */
> -	      if (td != NULL)
> -		{
> -		  /* This is the signal we are waiting for.  */
> -		  td->thrfunc = tk->thrfunc;
> -		  td->sival = tk->sival;
> -
> -		  pthread_t th;
> -		  __pthread_create (&th, &tk->attr, timer_sigev_thread, td);
> -		}
> -	    }
> -
> -	  __pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
> -	}
> -    }
> -}
> -
> -
> -/* Control variable for helper thread creation.  */
> -pthread_once_t __timer_helper_once = PTHREAD_ONCE_INIT;
> -
> -
> -/* TID of the helper thread.  */
> -pid_t __timer_helper_tid;
> -
> -
> -/* Reset variables so that after a fork a new helper thread gets started.  */
> -void
> -__timer_fork_subprocess (void)
> -{
> -  __timer_helper_once = PTHREAD_ONCE_INIT;
> -  __timer_helper_tid = 0;
> -}
> -
> -
> -void
> -__timer_start_helper_thread (void)
> -{
> -  /* The helper thread needs only very little resources
> -     and should go away automatically when canceled.  */
> -  pthread_attr_t attr;
> -  __pthread_attr_init (&attr);
> -  __pthread_attr_setstacksize (&attr, __pthread_get_minstack (&attr));
> -
> -  /* Block all signals in the helper thread but SIGSETXID.  */
> -  sigset_t ss;
> -  __sigfillset (&ss);
> -  __sigdelset (&ss, SIGSETXID);
> -  int res = __pthread_attr_setsigmask_internal (&attr, &ss);
> -  if (res != 0)
> -    {
> -      __pthread_attr_destroy (&attr);
> -      return;
> -    }
> -
> -  /* Create the helper thread for this timer.  */
> -  pthread_t th;
> -  res = __pthread_create (&th, &attr, timer_helper_thread, NULL);
> -  if (res == 0)
> -    /* We managed to start the helper thread.  */
> -    __timer_helper_tid = ((struct pthread *) th)->tid;
> -
> -  /* No need for the attribute anymore.  */
> -  __pthread_attr_destroy (&attr);
> -}


-- 
Cheers,
Carlos.



More information about the Libc-alpha mailing list