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

Message ID 20260707160026.2555820-1-adhemerval.zanella@linaro.org (mailing list archive)
State Superseded
Headers
Series [v8] linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558, 27895, 29705, 32833) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed

Commit Message

Adhemerval Zanella Netto July 7, 2026, 3:59 p.m. UTC
  The current timer_create SIGEV_THREAD implementation has some
downsides:

  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.

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

  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.

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

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

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.

SIGTIMER is used internally and aliases SIGCANCEL (__SIGRTMIN).  The
helper thread keeps it blocked while waiting for expirations with
sigwaitinfo, and unblocks it only around the notification function so
that an asynchronous pthread_cancel targeting the pthread_t obtained
from pthread_self within the notification is delivered and acted upon
(POSIX requires SIGEV_THREAD to behave as if a fresh thread serviced
each notification).  Because the same signal number is unblocked
there, the SIGCANCEL handler is installed eagerly from timer_create:
otherwise a timer re-fire or a timer_delete wake reaching the
notification thread would hit the default disposition of __SIGRTMIN
and terminate the process.  For the same reason timer_delete wakes the
helper with SIGTIMER queued with SI_QUEUE instead of tgkill, so the
wake is not mistaken for a cancellation by the handler.

The notification runs under a cancellation landing pad rooted at the
wait loop: a pthread_cancel or a pthread_exit from the notification
function unwinds back into the loop, a cleanup handler resets all
internal thread state, and the helper serves the next firing instead
of terminating.  This also avoids the need to recreate the thread for
a pthread_exit call (and the possible unreported missed events from a
failed thread creation).

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 v7:
- SIGEV_THREAD now honors cancellation: the helper unblocks SIGTIMER
  (== SIGCANCEL) only around the notification function, wired to a
  cancellation landing pad so a pthread_cancel or pthread_exit unwinds
  back to the wait loop, resets the thread, and serves the next firing.
- Moved the SIGCANCEL handler and its install to a new file
  nptl/pthread_cancel_signal.c; timer_create installs it eagerly, since
  a re-fire or delete wake during a notification would otherwise hit
  __SIGRTMIN's default disposition and terminate the process.
- ___timer_delete wakes the helper with SIGTIMER via rt_tgsigqueueinfo
  (SI_QUEUE) instead of tgkill, so the wake is not mistaken for a
  cancellation.
- Used a proper struct pthread_unwind_buf as the landing pad, fixing
  pthread_exit from the notification function.
- Added rt/tst-timer8.c.

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/Makefile                                 |   1 +
 nptl/allocatestack.c                          |  23 +-
 nptl/descr.h                                  |   3 +
 nptl/pthread_cancel.c                         |  51 +--
 nptl/pthread_cancel_signal.c                  |  75 +++++
 nptl/pthread_create.c                         |  80 +++++
 rt/Makefile                                   |   8 +-
 rt/tst-timer-sigmask.c                        |  15 +-
 rt/tst-timer6.c                               |  79 +++++
 rt/tst-timer7.c                               |  91 ++++++
 rt/tst-timer8.c                               | 139 ++++++++
 sysdeps/nptl/Makefile                         |   2 -
 sysdeps/nptl/fork.h                           |   2 -
 sysdeps/nptl/pthreadP.h                       |  22 ++
 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        | 300 ++++++++++++------
 sysdeps/unix/sysv/linux/timer_delete.c        |  57 ++--
 sysdeps/unix/sysv/linux/timer_routines.c      | 154 ---------
 20 files changed, 775 insertions(+), 417 deletions(-)
 create mode 100644 nptl/pthread_cancel_signal.c
 create mode 100644 rt/tst-timer6.c
 create mode 100644 rt/tst-timer7.c
 create mode 100644 rt/tst-timer8.c
 delete mode 100644 sysdeps/unix/sysv/linux/timer_routines.c
  

Comments

Carlos O'Donell July 8, 2026, 6:05 p.m. UTC | #1
On 7/7/26 11:59 AM, Adhemerval Zanella wrote:
> The current timer_create SIGEV_THREAD implementation has some
> downsides:

Test rt/tst-timer8.c fails if pause() is removed indicating something is
wrong with the asynchronous cancellation.

Note that pause() should be removed because it's a cancellation point
which causes the thread to test for cancellation, and it will see the
bits set by pthread_cancel and cancel.

I don't know why the newe implementation fails to cancel the thread, I just
know that without pause it should still cancel but doesn't.

> 
>    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.
> 
>    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).
> 
>    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.
> 
>    4. timer_create does not propagate all thread attributes to the new
>       thread (BZ 27895).
> 
>    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).
> 
> 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.
> 
> SIGTIMER is used internally and aliases SIGCANCEL (__SIGRTMIN).  The
> helper thread keeps it blocked while waiting for expirations with
> sigwaitinfo, and unblocks it only around the notification function so
> that an asynchronous pthread_cancel targeting the pthread_t obtained
> from pthread_self within the notification is delivered and acted upon
> (POSIX requires SIGEV_THREAD to behave as if a fresh thread serviced
> each notification).  Because the same signal number is unblocked
> there, the SIGCANCEL handler is installed eagerly from timer_create:
> otherwise a timer re-fire or a timer_delete wake reaching the
> notification thread would hit the default disposition of __SIGRTMIN
> and terminate the process.  For the same reason timer_delete wakes the
> helper with SIGTIMER queued with SI_QUEUE instead of tgkill, so the
> wake is not mistaken for a cancellation by the handler.
> 
> The notification runs under a cancellation landing pad rooted at the
> wait loop: a pthread_cancel or a pthread_exit from the notification
> function unwinds back into the loop, a cleanup handler resets all
> internal thread state, and the helper serves the next firing instead
> of terminating.  This also avoids the need to recreate the thread for
> a pthread_exit call (and the possible unreported missed events from a
> failed thread creation).
> 
> 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 v7:
> - SIGEV_THREAD now honors cancellation: the helper unblocks SIGTIMER
>    (== SIGCANCEL) only around the notification function, wired to a
>    cancellation landing pad so a pthread_cancel or pthread_exit unwinds
>    back to the wait loop, resets the thread, and serves the next firing.
> - Moved the SIGCANCEL handler and its install to a new file
>    nptl/pthread_cancel_signal.c; timer_create installs it eagerly, since
>    a re-fire or delete wake during a notification would otherwise hit
>    __SIGRTMIN's default disposition and terminate the process.
> - ___timer_delete wakes the helper with SIGTIMER via rt_tgsigqueueinfo
>    (SI_QUEUE) instead of tgkill, so the wake is not mistaken for a
>    cancellation.
> - Used a proper struct pthread_unwind_buf as the landing pad, fixing
>    pthread_exit from the notification function.
> - Added rt/tst-timer8.c.
> 
> 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/Makefile                                 |   1 +
>   nptl/allocatestack.c                          |  23 +-
>   nptl/descr.h                                  |   3 +
>   nptl/pthread_cancel.c                         |  51 +--
>   nptl/pthread_cancel_signal.c                  |  75 +++++
>   nptl/pthread_create.c                         |  80 +++++
>   rt/Makefile                                   |   8 +-
>   rt/tst-timer-sigmask.c                        |  15 +-
>   rt/tst-timer6.c                               |  79 +++++
>   rt/tst-timer7.c                               |  91 ++++++
>   rt/tst-timer8.c                               | 139 ++++++++
>   sysdeps/nptl/Makefile                         |   2 -
>   sysdeps/nptl/fork.h                           |   2 -
>   sysdeps/nptl/pthreadP.h                       |  22 ++
>   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        | 300 ++++++++++++------
>   sysdeps/unix/sysv/linux/timer_delete.c        |  57 ++--
>   sysdeps/unix/sysv/linux/timer_routines.c      | 154 ---------
>   20 files changed, 775 insertions(+), 417 deletions(-)
>   create mode 100644 nptl/pthread_cancel_signal.c
>   create mode 100644 rt/tst-timer6.c
>   create mode 100644 rt/tst-timer7.c
>   create mode 100644 rt/tst-timer8.c
>   delete mode 100644 sysdeps/unix/sysv/linux/timer_routines.c
> 
> diff --git a/nptl/Makefile b/nptl/Makefile
> index 97a0fb7f658..e3aa715f68a 100644
> --- a/nptl/Makefile
> +++ b/nptl/Makefile
> @@ -93,6 +93,7 @@ routines = \
>     pthread_barrierattr_init \
>     pthread_barrierattr_setpshared \
>     pthread_cancel \
> +  pthread_cancel_signal \
>     pthread_cleanup_upto \
>     pthread_clockjoin \
>     pthread_cond_broadcast \
> 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_cancel.c b/nptl/pthread_cancel.c
> index 5a5a637d7c6..dfe7d5643fe 100644
> --- a/nptl/pthread_cancel.c
> +++ b/nptl/pthread_cancel.c
> @@ -15,45 +15,11 @@
>      License along with the GNU C Library; if not, see
>      <https://www.gnu.org/licenses/>.  */
>   
> -#include <errno.h>
> -#include <signal.h>
> -#include <stdlib.h>
>   #include "pthreadP.h"
> -#include <atomic.h>
> -#include <sysdep.h>
> -#include <unistd.h>
>   #include <unwind-link.h>
> -#include <cancellation-pc-check.h>
>   #include <stdio.h>
>   #include <gnu/lib-names.h>
> -#include <sys/single_threaded.h>
> -
> -/* For asynchronous cancellation we use a signal.  */
> -static void
> -sigcancel_handler (int sig, siginfo_t *si, void *ctx)
> -{
> -  /* Safety check.  It would be possible to call this function for
> -     other signals and send a signal from another process.  This is not
> -     correct and might even be a security problem.  Try to catch as
> -     many incorrect invocations as possible.  */
> -  if (sig != SIGCANCEL
> -      || si->si_pid != __getpid()
> -      || si->si_code != SI_TKILL)
> -    return;
> -
> -  /* Check if asynchronous cancellation mode is set and cancellation is not
> -     already in progress, or if interrupted instruction pointer falls within
> -     the cancellable syscall bridge.
> -     For interruptable syscalls with external side-effects (i.e. partial
> -     reads), the kernel will set the IP to after __syscall_cancel_arch_end,
> -     thus disabling the cancellation and allowing the process to handle such
> -     conditions.  */
> -  struct pthread *self = THREAD_SELF;
> -  int oldval = atomic_load_relaxed (&self->cancelhandling);
> -  if (cancel_enabled_and_canceled_and_async (oldval)
> -      || cancellation_pc_check (ctx))
> -    __syscall_do_cancel ();
> -}
> +#include <shlib-compat.h>
>   
>   int
>   __pthread_cancel (pthread_t th)
> @@ -67,20 +33,7 @@ __pthread_cancel (pthread_t th)
>          determined.  */
>       return 0;
>   
> -  static int init_sigcancel = 0;
> -  if (atomic_load_relaxed (&init_sigcancel) == 0)
> -    {
> -      struct sigaction sa;
> -      sa.sa_sigaction = sigcancel_handler;
> -      /* The signal handle should be non-interruptible to avoid the risk of
> -	 spurious EINTR caused by SIGCANCEL sent to process or if
> -	 pthread_cancel() is called while cancellation is disabled in the
> -	 target thread.  */
> -      sa.sa_flags = SA_SIGINFO | SA_RESTART;
> -      __sigemptyset (&sa.sa_mask);
> -      __libc_sigaction (SIGCANCEL, &sa, NULL);
> -      atomic_store_relaxed (&init_sigcancel, 1);
> -    }
> +  __pthread_install_sigcancel_handler ();
>   
>   #ifdef SHARED
>     /* Trigger an error if libgcc_s cannot be loaded.  */
> diff --git a/nptl/pthread_cancel_signal.c b/nptl/pthread_cancel_signal.c
> new file mode 100644
> index 00000000000..5b230cde5b0
> --- /dev/null
> +++ b/nptl/pthread_cancel_signal.c
> @@ -0,0 +1,75 @@
> +/* Signal handling for pthread cancellation.
> +   Copyright (C) 2002-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; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <sys/ucontext.h>
> +#include <cancellation-pc-check.h>
> +#include "pthreadP.h"
> +#include <unistd.h>
> +
> +/* For asynchronous cancellation we use a signal.  */
> +static void
> +sigcancel_handler (int sig, siginfo_t *si, void *ctx)
> +{
> +  /* Safety check.  It would be possible to call this function for
> +     other signals and send a signal from another process.  This is not
> +     correct and might even be a security problem.  Try to catch as
> +     many incorrect invocations as possible.  */
> +  if (sig != SIGCANCEL
> +      || si->si_pid != __getpid()
> +      || si->si_code != SI_TKILL)
> +    return;
> +
> +  /* Check if asynchronous cancellation mode is set and cancellation is not
> +     already in progress, or if interrupted instruction pointer falls within
> +     the cancellable syscall bridge.
> +     For interruptable syscalls with external side-effects (i.e. partial
> +     reads), the kernel will set the IP to after __syscall_cancel_arch_end,
> +     thus disabling the cancellation and allowing the process to handle such
> +     conditions.  */
> +  struct pthread *self = THREAD_SELF;
> +  int oldval = atomic_load_relaxed (&self->cancelhandling);
> +  if (cancel_enabled_and_canceled_and_async (oldval)
> +      || cancellation_pc_check (ctx))
> +    __syscall_do_cancel ();
> +}
> +
> +/* Install the SIGCANCEL handler if it has not been installed yet.  This is
> +   done lazily from __pthread_cancel, and eagerly from the POSIX timer code
> +   (which unblocks SIGCANCEL/SIGTIMER in the SIGEV_THREAD helper thread and
> +   therefore needs the handler in place before any signal can be delivered).  */
> +void
> +__pthread_install_sigcancel_handler (void)
> +{
> +  static int init_sigcancel = 0;
> +  if (atomic_load_relaxed (&init_sigcancel) == 0)
> +    {
> +      struct sigaction sa;
> +      sa.sa_sigaction = sigcancel_handler;
> +      /* The signal handle should be non-interruptible to avoid the risk of
> +	 spurious EINTR caused by SIGCANCEL sent to process or if
> +	 pthread_cancel() is called while cancellation is disabled in the
> +	 target thread.  */
> +      sa.sa_flags = SA_SIGINFO | SA_RESTART;
> +      __sigemptyset (&sa.sa_mask);
> +      __libc_sigaction (SIGCANCEL, &sa, NULL);
> +      atomic_store_relaxed (&init_sigcancel, 1);
> +    }
> +}
> +
> diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
> index fcb06c68c71..2891c9b1af2 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).
> +
> +     - 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 f39e9674e58..fd377107fe6 100644
> --- a/rt/Makefile
> +++ b/rt/Makefile
> @@ -76,7 +76,10 @@ 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 \
> +	 tst-timer8
>   tests-internal := tst-timer-sigmask
>   
>   tests-time64 := \
> @@ -98,6 +101,9 @@ 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
> +
> +CFLAGS-tst-timer8.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..4ce4927c9e1 100644
> --- a/rt/tst-timer-sigmask.c
> +++ b/rt/tst-timer-sigmask.c
> @@ -38,19 +38,22 @@ thread_handler (union sigval sv)
>       printf ("%s: blocked signal mask = { ", __func__);
>     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.  */
> +      /* While the notification function runs, the SIGEV_THREAD helper blocks
> +	 all signals except SIGKILL, SIGSTOP, SIGSETXID, and SIGCANCEL (the
> +	 last, which aliases SIGTIMER, is unblocked around the notification
> +	 function so that it can be cancelled).  */
>         if (sigismember (&ss, sig))
> -	{
> -	  TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
> -	  TEST_VERIFY (!is_internal_signal (sig));
> -	}
> +	TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP && sig != SIGSETXID
> +		     && sig != SIGCANCEL);
>         if (test_verbose && sigismember (&ss, sig))
>   	printf ("%d, ", sig);
>       }
>     if (test_verbose > 0)
>       printf ("}\n");
>   
> +  /* SIGCANCEL must be unblocked here so pthread_cancel is honored.  */
> +  TEST_VERIFY (!sigismember (&ss, SIGCANCEL));
> +
>     xpthread_barrier_wait (&barrier);
>   }
>   
> 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/rt/tst-timer8.c b/rt/tst-timer8.c
> new file mode 100644
> index 00000000000..742cb69de4e
> --- /dev/null
> +++ b/rt/tst-timer8.c
> @@ -0,0 +1,139 @@
> +/* Check that SIGEV_THREAD notification functions honor cancellation

Calling pause (); introduces a cancellation point in the code, and means we
don't test the asynchronous cancellation.

We permit the UB use of semi_post in this test.

Move to set AC state first then enable.

diff --git a/rt/tst-timer8.c b/rt/tst-timer8.c
index 742cb69de4..3b7c478b81 100644
--- a/rt/tst-timer8.c
+++ b/rt/tst-timer8.c
@@ -65,17 +65,15 @@ on_timer (union sigval sv)
      {
        handler_thread = pthread_self ();
  
-      TEST_COMPARE (pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL), 0);
+      pthread_cleanup_push (on_cancel, NULL);
        TEST_COMPARE (pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL),
                     0);
-
-      pthread_cleanup_push (on_cancel, NULL);
+      TEST_COMPARE (pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL), 0);
  
        TEST_VERIFY_EXIT (sem_post (&handler_started) == 0);
  
        /* Block at a cancellation point until cancelled.  */
-      while (1)
-       pause ();
+      while (1);
  
        pthread_cleanup_pop (0);
      }


make[2]: Leaving directory '/home/codonell/src/glibc/rt'
FAIL: rt/tst-timer8
original exit status 1
Timed out: killed the child process
Termination time: 2026-07-08T17:59:44.149759475
Last write to standard output: 2026-07-08T17:59:41.147878148

Without pause() the test fails.


> +   (BZ 30558).
> +   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/>.  */
> +
> +
> +/* POSIX requires SIGEV_THREAD notifications to behave as if a new thread was
> +   created for each delivery and thus it must support cancellation.  */
> +
> +#include <errno.h>
> +#include <pthread.h>
> +#include <semaphore.h>
> +#include <signal.h>
> +#include <stdatomic.h>
> +#include <time.h>
> +#include <unistd.h>
> +
> +#include <support/check.h>
> +
> +static sem_t handler_started;
> +static sem_t handler_cancelled;
> +static sem_t handler_reused;
> +
> +static pthread_t handler_thread;
> +
> +static void
> +sem_wait_retry (sem_t *sem)
> +{
> +  int r;
> +  do
> +    r = sem_wait (sem);
> +  while (r != 0 && errno == EINTR);
> +  if (r != 0)
> +    FAIL_EXIT1 ("sem_wait: %m");
> +}
> +
> +static void
> +on_cancel (void *arg)
> +{
> +  sem_post (&handler_cancelled);
> +}
> +
> +static void
> +on_timer (union sigval sv)
> +{
> +  /* Plain static (not thread-local) storage, so it survives the per-firing
> +     thread state reset and lets us distinguish the first firing.  */
> +  static atomic_int firings;
> +
> +  if (atomic_fetch_add (&firings, 1) == 0)
> +    {
> +      handler_thread = pthread_self ();
> +
> +      TEST_COMPARE (pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL), 0);
> +      TEST_COMPARE (pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL),
> +		    0);
> +
> +      pthread_cleanup_push (on_cancel, NULL);
> +
> +      TEST_VERIFY_EXIT (sem_post (&handler_started) == 0);
> +
> +      /* Block at a cancellation point until cancelled.  */
> +      while (1)
> +	pause ();
> +
> +      pthread_cleanup_pop (0);
> +    }
> +  else
> +    /* A firing after the cancelled one: the helper thread was reused.  */
> +    sem_post (&handler_reused);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  if (sem_init (&handler_started, 0, 0) != 0)
> +    FAIL_EXIT1 ("sem_init: %m");
> +  if (sem_init (&handler_cancelled, 0, 0) != 0)
> +    FAIL_EXIT1 ("sem_init: %m");
> +  if (sem_init (&handler_reused, 0, 0) != 0)
> +    FAIL_EXIT1 ("sem_init: %m");
> +
> +  timer_t timerid;
> +  struct sigevent ev =
> +    {
> +      .sigev_notify = SIGEV_THREAD,
> +      .sigev_notify_function = on_timer,
> +    };
> +  TEST_COMPARE (timer_create (CLOCK_REALTIME, &ev, &timerid), 0);
> +
> +  /* Periodic so that a firing keeps arriving after the first one is
> +     cancelled.  */
> +  struct itimerspec its =
> +    { .it_value    = { .tv_nsec = 10000000 /* 0.01s */ },
> +      .it_interval = { .tv_nsec = 10000000 /* 0.01s */ } };
> +  TEST_COMPARE (timer_settime (timerid, 0, &its, NULL), 0);
> +
> +  /* The notification function is now blocked with cancellation enabled.  */
> +  sem_wait_retry (&handler_started);
> +
> +  TEST_COMPARE (pthread_cancel (handler_thread), 0);
> +
> +  /* The cleanup handler runs only if the cancellation was honored.  */
> +  sem_wait_retry (&handler_cancelled);
> +
> +  /* The helper thread must have survived the cancellation and resumed
> +     servicing the periodic timer.  */
> +  sem_wait_retry (&handler_reused);
> +
> +  /* And it is indeed still alive.  */
> +  TEST_COMPARE (pthread_kill (handler_thread, 0), 0);
> +
> +  struct itimerspec its_stop = { 0 };
> +  TEST_COMPARE (timer_settime (timerid, 0, &its_stop, NULL), 0);
> +  TEST_COMPARE (timer_delete (timerid), 0);
> +
> +  sem_destroy (&handler_started);
> +  sem_destroy (&handler_cancelled);
> +  sem_destroy (&handler_reused);
> +
> +  return 0;
> +}
> +
> +#define TIMEOUT 3
> +#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..b6362717395 100644
> --- a/sysdeps/nptl/pthreadP.h
> +++ b/sysdeps/nptl/pthreadP.h
> @@ -667,6 +667,28 @@ 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
> +{
> +  /* The thread's original (start_thread) cancellation landing pad.  It is
> +     restored into self->cleanup_jmp_buf so the reused helper thread is left
> +     in a ristine state after the notification function returns, calls
> +     pthread_exit, or is cancelled.  */
> +  struct pthread_unwind_buf *cleanup_jmp_buf;
> +};
> +
> +/* 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;
> +
> +/* Install the process-wide SIGCANCEL handler if it is not already installed.
> +   Used lazily by pthread_cancel and eagerly by the POSIX timer SIGEV_THREAD
> +   support.  */
> +void __pthread_install_sigcancel_handler (void) 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..141de9f837f 100644
> --- a/sysdeps/unix/sysv/linux/timer_create.c
> +++ b/sysdeps/unix/sysv/linux/timer_create.c
> @@ -15,46 +15,205 @@
>      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-diag.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)
> +	{
> +	  /* 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, install a cancellation landing
> +	     pad rooted at this loop: if the notification function returns
> +	     normally, calls pthread_exit, or is cancelled, control unwinds
> +	     back here, the cleanup handler resets all observable per-thread
> +	     state - TLS destructors, TSD, libc thread-local state, DTV,
> +	     signal mask, and cancellation state - and the helper serves the
> +	     next firing instead of terminating.  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).  */
> +	  struct pthread_unwind_buf cancel_buf;
> +	  DIAG_PUSH_NEEDS_COMMENT;
> +	  /* Same false-positive -Wstringop-overflow as in start_thread.  */
> +	  DIAG_IGNORE_NEEDS_COMMENT (11, "-Wstringop-overflow=");
> +	  int not_first_call
> +	    = setjmp ((struct __jmp_buf_tag *) cancel_buf.cancel_jmp_buf);
> +	  DIAG_POP_NEEDS_COMMENT;
> +	  cancel_buf.priv.data.prev = NULL;
> +	  cancel_buf.priv.data.cleanup = NULL;
> +
> +	  if (__glibc_likely (! not_first_call))
> +	    {
> +	      self->cleanup_jmp_buf = &cancel_buf;
> +	      pthread_cleanup_push (__pthread_reset_state, &clargs);
> +
> +	      /* Enable asynchronous cancellation.  A timer re-fire (SI_TIMER)
> +		 or the timer_delete wake (SI_QUEUE) reaching the handler is
> +		 ignored, so it neither cancels nor is queued.  */
> +	      internal_signal_unblock_signal (SIGTIMER);
> +
> +	      thrfunc (sival);
> +
> +	      pthread_cleanup_pop (1);
> +	    }
> +	}
> +
> +      /* timer_delete sets the MSB and wakes 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)
> +{
> +  /* The helper thread unblocks SIGTIMER/SIGCANCEL only while running the
> +     notification function; there an interval re-fire or a pthread_cancel can
> +     be delivered to the SIGCANCEL handler, which must therefore already be
> +     installed.  */
> +  __pthread_install_sigcancel_handler ();
> +
> +  /* Block all signals in the helper thread but SIGSETXID.  SIGTIMER stays
> +     blocked so it can be consumed synchronously with sigwaitinfo between
> +     firings; it is unblocked only around the notification function.  */
> +  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 +223,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..a215e213b9e 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,33 @@
>   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);
> +      /* The helper threads leaves SIGTIMER/SIGCANCEL unblocked while running
> +	 the notification function, and a tgkill would  arrive as SI_TKILL
> +	 and could be mistaken for a cancellation by the SIGCANCEL handler.
> +	  With SI_QUEUE the handler ignores it (only SI_TKILL cancels), while
> +	 a helper blocked in sigwaitinfo still wakes up, observes the MSB set
> +	 above (si_code != SI_TIMER), and exits.  */
> +      siginfo_t info = { 0 };
> +      info.si_signo = SIGTIMER;
> +      info.si_code = SI_QUEUE;
> +      INTERNAL_SYSCALL_CALL (rt_tgsigqueueinfo, __getpid (), th->tid,
> +			     SIGTIMER, &info);
>         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);
> -}
  
Adhemerval Zanella Netto July 8, 2026, 7:45 p.m. UTC | #2
On 08/07/26 15:05, Carlos O'Donell wrote:
> On 7/7/26 11:59 AM, Adhemerval Zanella wrote:
>> The current timer_create SIGEV_THREAD implementation has some
>> downsides:
> 
> Test rt/tst-timer8.c fails if pause() is removed indicating something is
> wrong with the asynchronous cancellation.
> 
> Note that pause() should be removed because it's a cancellation point
> which causes the thread to test for cancellation, and it will see the
> bits set by pthread_cancel and cancel.
> 
> I don't know why the newe implementation fails to cancel the thread, I just
> know that without pause it should still cancel but doesn't.
It is because the test is built with -fexception and gcc gives the notification
function a DWARF/LSDA unwind whose cleanup actions exist only at call sites.
Asynchronous cancellation that interrupts a PC not inside any call-site region
finds no landing pad there (the while (1)), so the personality routine performs 
no cleanup for that frame (the on_cancel registered with pthread_cleanup_push)
and the unwind proceeds straight to thread termination.

And as you noted privately the sem_post called by on_cancel is strictly UB,
I will change to use a different synchronization mechanism to check whether
the cancellation was acted upon.
  
Carlos O'Donell July 8, 2026, 8:04 p.m. UTC | #3
On 7/8/26 3:45 PM, Adhemerval Zanella Netto wrote:
> 
> 
> On 08/07/26 15:05, Carlos O'Donell wrote:
>> On 7/7/26 11:59 AM, Adhemerval Zanella wrote:
>>> The current timer_create SIGEV_THREAD implementation has some
>>> downsides:
>>
>> Test rt/tst-timer8.c fails if pause() is removed indicating something is
>> wrong with the asynchronous cancellation.
>>
>> Note that pause() should be removed because it's a cancellation point
>> which causes the thread to test for cancellation, and it will see the
>> bits set by pthread_cancel and cancel.
>>
>> I don't know why the newe implementation fails to cancel the thread, I just
>> know that without pause it should still cancel but doesn't.
> It is because the test is built with -fexception and gcc gives the notification
> function a DWARF/LSDA unwind whose cleanup actions exist only at call sites.
> Asynchronous cancellation that interrupts a PC not inside any call-site region
> finds no landing pad there (the while (1)), so the personality routine performs
> no cleanup for that frame (the on_cancel registered with pthread_cleanup_push)
> and the unwind proceeds straight to thread termination.

Agreed, this means the test needs -fasynchronous-unwind-tables.

$ grep -r 'fasynchronous' * | grep tst
debug/Makefile:CFLAGS-tst-longjmp_chk.c += -fexceptions -fasynchronous-unwind-tables
debug/Makefile:CFLAGS-tst-longjmp_chk2.c += -fexceptions -fasynchronous-unwind-tables
debug/Makefile:CFLAGS-tst-longjmp_chk3.c += -fexceptions -fasynchronous-unwind-tables
rt/Makefile:CFLAGS-tst-timer8.c += -fexceptions -fasynchronous-unwind-tables
sysdeps/pthread/Makefile:CFLAGS-tst-cancelx20.c += -fexceptions -fasynchronous-unwind-tables
sysdeps/pthread/Makefile:CFLAGS-tst-cancelx21.c += -fexceptions -fasynchronous-unwind-tables
sysdeps/pthread/Makefile:CFLAGS-tst-cleanupx0.c += -fexceptions -fasynchronous-unwind-tables
sysdeps/pthread/Makefile:CFLAGS-tst-cleanupx1.c += -fexceptions -fasynchronous-unwind-tables
sysdeps/unix/sysv/linux/Makefile:CFLAGS-tst-sigcontext-get_pc.c = -fasynchronous-unwind-tables

Which we already use for other similar tests.
  
> And as you noted privately the sem_post called by on_cancel is strictly UB,
> I will change to use a different synchronization mechanism to check whether
> the cancellation was acted upon.

Right, I think we could use atomics here just to avoid futex too and make the
regression test simpler and conforming.
  
Carlos O'Donell July 8, 2026, 8:09 p.m. UTC | #4
On 7/8/26 4:04 PM, Carlos O'Donell wrote:
> On 7/8/26 3:45 PM, Adhemerval Zanella Netto wrote:
>>
>>
>> On 08/07/26 15:05, Carlos O'Donell wrote:
>>> On 7/7/26 11:59 AM, Adhemerval Zanella wrote:
>>>> The current timer_create SIGEV_THREAD implementation has some
>>>> downsides:
>>>
>>> Test rt/tst-timer8.c fails if pause() is removed indicating something is
>>> wrong with the asynchronous cancellation.
>>>
>>> Note that pause() should be removed because it's a cancellation point
>>> which causes the thread to test for cancellation, and it will see the
>>> bits set by pthread_cancel and cancel.
>>>
>>> I don't know why the newe implementation fails to cancel the thread, I just
>>> know that without pause it should still cancel but doesn't.
>> It is because the test is built with -fexception and gcc gives the notification
>> function a DWARF/LSDA unwind whose cleanup actions exist only at call sites.
>> Asynchronous cancellation that interrupts a PC not inside any call-site region
>> finds no landing pad there (the while (1)), so the personality routine performs
>> no cleanup for that frame (the on_cancel registered with pthread_cleanup_push)
>> and the unwind proceeds straight to thread termination.
> 
> Agreed, this means the test needs -fasynchronous-unwind-tables.

The test already has "-fasynchronous-unwind-tables"?
  
Florian Weimer July 8, 2026, 8:28 p.m. UTC | #5
* Carlos O'Donell:

> On 7/8/26 4:04 PM, Carlos O'Donell wrote:
>> On 7/8/26 3:45 PM, Adhemerval Zanella Netto wrote:
>>>
>>>
>>> On 08/07/26 15:05, Carlos O'Donell wrote:
>>>> On 7/7/26 11:59 AM, Adhemerval Zanella wrote:
>>>>> The current timer_create SIGEV_THREAD implementation has some
>>>>> downsides:
>>>>
>>>> Test rt/tst-timer8.c fails if pause() is removed indicating something is
>>>> wrong with the asynchronous cancellation.
>>>>
>>>> Note that pause() should be removed because it's a cancellation point
>>>> which causes the thread to test for cancellation, and it will see the
>>>> bits set by pthread_cancel and cancel.
>>>>
>>>> I don't know why the newe implementation fails to cancel the thread, I just
>>>> know that without pause it should still cancel but doesn't.
>>> It is because the test is built with -fexception and gcc gives the notification
>>> function a DWARF/LSDA unwind whose cleanup actions exist only at call sites.
>>> Asynchronous cancellation that interrupts a PC not inside any call-site region
>>> finds no landing pad there (the while (1)), so the personality routine performs
>>> no cleanup for that frame (the on_cancel registered with pthread_cleanup_push)
>>> and the unwind proceeds straight to thread termination.
>> 
>> Agreed, this means the test needs -fasynchronous-unwind-tables.
>
> The test already has "-fasynchronous-unwind-tables"?

It's the GCC default.

Maybe -fnon-call-exceptions fixes it?  But I think Andreas Schwab
mentioned that GCC does not always put the unwind regions correctly,
so a compiler barrier might be necessary, too.
  

Patch

diff --git a/nptl/Makefile b/nptl/Makefile
index 97a0fb7f658..e3aa715f68a 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -93,6 +93,7 @@  routines = \
   pthread_barrierattr_init \
   pthread_barrierattr_setpshared \
   pthread_cancel \
+  pthread_cancel_signal \
   pthread_cleanup_upto \
   pthread_clockjoin \
   pthread_cond_broadcast \
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_cancel.c b/nptl/pthread_cancel.c
index 5a5a637d7c6..dfe7d5643fe 100644
--- a/nptl/pthread_cancel.c
+++ b/nptl/pthread_cancel.c
@@ -15,45 +15,11 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <errno.h>
-#include <signal.h>
-#include <stdlib.h>
 #include "pthreadP.h"
-#include <atomic.h>
-#include <sysdep.h>
-#include <unistd.h>
 #include <unwind-link.h>
-#include <cancellation-pc-check.h>
 #include <stdio.h>
 #include <gnu/lib-names.h>
-#include <sys/single_threaded.h>
-
-/* For asynchronous cancellation we use a signal.  */
-static void
-sigcancel_handler (int sig, siginfo_t *si, void *ctx)
-{
-  /* Safety check.  It would be possible to call this function for
-     other signals and send a signal from another process.  This is not
-     correct and might even be a security problem.  Try to catch as
-     many incorrect invocations as possible.  */
-  if (sig != SIGCANCEL
-      || si->si_pid != __getpid()
-      || si->si_code != SI_TKILL)
-    return;
-
-  /* Check if asynchronous cancellation mode is set and cancellation is not
-     already in progress, or if interrupted instruction pointer falls within
-     the cancellable syscall bridge.
-     For interruptable syscalls with external side-effects (i.e. partial
-     reads), the kernel will set the IP to after __syscall_cancel_arch_end,
-     thus disabling the cancellation and allowing the process to handle such
-     conditions.  */
-  struct pthread *self = THREAD_SELF;
-  int oldval = atomic_load_relaxed (&self->cancelhandling);
-  if (cancel_enabled_and_canceled_and_async (oldval)
-      || cancellation_pc_check (ctx))
-    __syscall_do_cancel ();
-}
+#include <shlib-compat.h>
 
 int
 __pthread_cancel (pthread_t th)
@@ -67,20 +33,7 @@  __pthread_cancel (pthread_t th)
        determined.  */
     return 0;
 
-  static int init_sigcancel = 0;
-  if (atomic_load_relaxed (&init_sigcancel) == 0)
-    {
-      struct sigaction sa;
-      sa.sa_sigaction = sigcancel_handler;
-      /* The signal handle should be non-interruptible to avoid the risk of
-	 spurious EINTR caused by SIGCANCEL sent to process or if
-	 pthread_cancel() is called while cancellation is disabled in the
-	 target thread.  */
-      sa.sa_flags = SA_SIGINFO | SA_RESTART;
-      __sigemptyset (&sa.sa_mask);
-      __libc_sigaction (SIGCANCEL, &sa, NULL);
-      atomic_store_relaxed (&init_sigcancel, 1);
-    }
+  __pthread_install_sigcancel_handler ();
 
 #ifdef SHARED
   /* Trigger an error if libgcc_s cannot be loaded.  */
diff --git a/nptl/pthread_cancel_signal.c b/nptl/pthread_cancel_signal.c
new file mode 100644
index 00000000000..5b230cde5b0
--- /dev/null
+++ b/nptl/pthread_cancel_signal.c
@@ -0,0 +1,75 @@ 
+/* Signal handling for pthread cancellation.
+   Copyright (C) 2002-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; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/ucontext.h>
+#include <cancellation-pc-check.h>
+#include "pthreadP.h"
+#include <unistd.h>
+
+/* For asynchronous cancellation we use a signal.  */
+static void
+sigcancel_handler (int sig, siginfo_t *si, void *ctx)
+{
+  /* Safety check.  It would be possible to call this function for
+     other signals and send a signal from another process.  This is not
+     correct and might even be a security problem.  Try to catch as
+     many incorrect invocations as possible.  */
+  if (sig != SIGCANCEL
+      || si->si_pid != __getpid()
+      || si->si_code != SI_TKILL)
+    return;
+
+  /* Check if asynchronous cancellation mode is set and cancellation is not
+     already in progress, or if interrupted instruction pointer falls within
+     the cancellable syscall bridge.
+     For interruptable syscalls with external side-effects (i.e. partial
+     reads), the kernel will set the IP to after __syscall_cancel_arch_end,
+     thus disabling the cancellation and allowing the process to handle such
+     conditions.  */
+  struct pthread *self = THREAD_SELF;
+  int oldval = atomic_load_relaxed (&self->cancelhandling);
+  if (cancel_enabled_and_canceled_and_async (oldval)
+      || cancellation_pc_check (ctx))
+    __syscall_do_cancel ();
+}
+
+/* Install the SIGCANCEL handler if it has not been installed yet.  This is
+   done lazily from __pthread_cancel, and eagerly from the POSIX timer code
+   (which unblocks SIGCANCEL/SIGTIMER in the SIGEV_THREAD helper thread and
+   therefore needs the handler in place before any signal can be delivered).  */
+void
+__pthread_install_sigcancel_handler (void)
+{
+  static int init_sigcancel = 0;
+  if (atomic_load_relaxed (&init_sigcancel) == 0)
+    {
+      struct sigaction sa;
+      sa.sa_sigaction = sigcancel_handler;
+      /* The signal handle should be non-interruptible to avoid the risk of
+	 spurious EINTR caused by SIGCANCEL sent to process or if
+	 pthread_cancel() is called while cancellation is disabled in the
+	 target thread.  */
+      sa.sa_flags = SA_SIGINFO | SA_RESTART;
+      __sigemptyset (&sa.sa_mask);
+      __libc_sigaction (SIGCANCEL, &sa, NULL);
+      atomic_store_relaxed (&init_sigcancel, 1);
+    }
+}
+
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index fcb06c68c71..2891c9b1af2 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).
+
+     - 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 f39e9674e58..fd377107fe6 100644
--- a/rt/Makefile
+++ b/rt/Makefile
@@ -76,7 +76,10 @@  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 \
+	 tst-timer8
 tests-internal := tst-timer-sigmask
 
 tests-time64 := \
@@ -98,6 +101,9 @@  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
+
+CFLAGS-tst-timer8.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..4ce4927c9e1 100644
--- a/rt/tst-timer-sigmask.c
+++ b/rt/tst-timer-sigmask.c
@@ -38,19 +38,22 @@  thread_handler (union sigval sv)
     printf ("%s: blocked signal mask = { ", __func__);
   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.  */
+      /* While the notification function runs, the SIGEV_THREAD helper blocks
+	 all signals except SIGKILL, SIGSTOP, SIGSETXID, and SIGCANCEL (the
+	 last, which aliases SIGTIMER, is unblocked around the notification
+	 function so that it can be cancelled).  */
       if (sigismember (&ss, sig))
-	{
-	  TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
-	  TEST_VERIFY (!is_internal_signal (sig));
-	}
+	TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP && sig != SIGSETXID
+		     && sig != SIGCANCEL);
       if (test_verbose && sigismember (&ss, sig))
 	printf ("%d, ", sig);
     }
   if (test_verbose > 0)
     printf ("}\n");
 
+  /* SIGCANCEL must be unblocked here so pthread_cancel is honored.  */
+  TEST_VERIFY (!sigismember (&ss, SIGCANCEL));
+
   xpthread_barrier_wait (&barrier);
 }
 
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/rt/tst-timer8.c b/rt/tst-timer8.c
new file mode 100644
index 00000000000..742cb69de4e
--- /dev/null
+++ b/rt/tst-timer8.c
@@ -0,0 +1,139 @@ 
+/* Check that SIGEV_THREAD notification functions honor cancellation
+   (BZ 30558).
+   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/>.  */
+
+
+/* POSIX requires SIGEV_THREAD notifications to behave as if a new thread was
+   created for each delivery and thus it must support cancellation.  */
+
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <support/check.h>
+
+static sem_t handler_started;
+static sem_t handler_cancelled;
+static sem_t handler_reused;
+
+static pthread_t handler_thread;
+
+static void
+sem_wait_retry (sem_t *sem)
+{
+  int r;
+  do
+    r = sem_wait (sem);
+  while (r != 0 && errno == EINTR);
+  if (r != 0)
+    FAIL_EXIT1 ("sem_wait: %m");
+}
+
+static void
+on_cancel (void *arg)
+{
+  sem_post (&handler_cancelled);
+}
+
+static void
+on_timer (union sigval sv)
+{
+  /* Plain static (not thread-local) storage, so it survives the per-firing
+     thread state reset and lets us distinguish the first firing.  */
+  static atomic_int firings;
+
+  if (atomic_fetch_add (&firings, 1) == 0)
+    {
+      handler_thread = pthread_self ();
+
+      TEST_COMPARE (pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL), 0);
+      TEST_COMPARE (pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL),
+		    0);
+
+      pthread_cleanup_push (on_cancel, NULL);
+
+      TEST_VERIFY_EXIT (sem_post (&handler_started) == 0);
+
+      /* Block at a cancellation point until cancelled.  */
+      while (1)
+	pause ();
+
+      pthread_cleanup_pop (0);
+    }
+  else
+    /* A firing after the cancelled one: the helper thread was reused.  */
+    sem_post (&handler_reused);
+}
+
+static int
+do_test (void)
+{
+  if (sem_init (&handler_started, 0, 0) != 0)
+    FAIL_EXIT1 ("sem_init: %m");
+  if (sem_init (&handler_cancelled, 0, 0) != 0)
+    FAIL_EXIT1 ("sem_init: %m");
+  if (sem_init (&handler_reused, 0, 0) != 0)
+    FAIL_EXIT1 ("sem_init: %m");
+
+  timer_t timerid;
+  struct sigevent ev =
+    {
+      .sigev_notify = SIGEV_THREAD,
+      .sigev_notify_function = on_timer,
+    };
+  TEST_COMPARE (timer_create (CLOCK_REALTIME, &ev, &timerid), 0);
+
+  /* Periodic so that a firing keeps arriving after the first one is
+     cancelled.  */
+  struct itimerspec its =
+    { .it_value    = { .tv_nsec = 10000000 /* 0.01s */ },
+      .it_interval = { .tv_nsec = 10000000 /* 0.01s */ } };
+  TEST_COMPARE (timer_settime (timerid, 0, &its, NULL), 0);
+
+  /* The notification function is now blocked with cancellation enabled.  */
+  sem_wait_retry (&handler_started);
+
+  TEST_COMPARE (pthread_cancel (handler_thread), 0);
+
+  /* The cleanup handler runs only if the cancellation was honored.  */
+  sem_wait_retry (&handler_cancelled);
+
+  /* The helper thread must have survived the cancellation and resumed
+     servicing the periodic timer.  */
+  sem_wait_retry (&handler_reused);
+
+  /* And it is indeed still alive.  */
+  TEST_COMPARE (pthread_kill (handler_thread, 0), 0);
+
+  struct itimerspec its_stop = { 0 };
+  TEST_COMPARE (timer_settime (timerid, 0, &its_stop, NULL), 0);
+  TEST_COMPARE (timer_delete (timerid), 0);
+
+  sem_destroy (&handler_started);
+  sem_destroy (&handler_cancelled);
+  sem_destroy (&handler_reused);
+
+  return 0;
+}
+
+#define TIMEOUT 3
+#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..b6362717395 100644
--- a/sysdeps/nptl/pthreadP.h
+++ b/sysdeps/nptl/pthreadP.h
@@ -667,6 +667,28 @@  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
+{
+  /* The thread's original (start_thread) cancellation landing pad.  It is
+     restored into self->cleanup_jmp_buf so the reused helper thread is left
+     in a ristine state after the notification function returns, calls
+     pthread_exit, or is cancelled.  */
+  struct pthread_unwind_buf *cleanup_jmp_buf;
+};
+
+/* 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;
+
+/* Install the process-wide SIGCANCEL handler if it is not already installed.
+   Used lazily by pthread_cancel and eagerly by the POSIX timer SIGEV_THREAD
+   support.  */
+void __pthread_install_sigcancel_handler (void) 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..141de9f837f 100644
--- a/sysdeps/unix/sysv/linux/timer_create.c
+++ b/sysdeps/unix/sysv/linux/timer_create.c
@@ -15,46 +15,205 @@ 
    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-diag.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)
+	{
+	  /* 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, install a cancellation landing
+	     pad rooted at this loop: if the notification function returns
+	     normally, calls pthread_exit, or is cancelled, control unwinds
+	     back here, the cleanup handler resets all observable per-thread
+	     state - TLS destructors, TSD, libc thread-local state, DTV,
+	     signal mask, and cancellation state - and the helper serves the
+	     next firing instead of terminating.  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).  */
+	  struct pthread_unwind_buf cancel_buf;
+	  DIAG_PUSH_NEEDS_COMMENT;
+	  /* Same false-positive -Wstringop-overflow as in start_thread.  */
+	  DIAG_IGNORE_NEEDS_COMMENT (11, "-Wstringop-overflow=");
+	  int not_first_call
+	    = setjmp ((struct __jmp_buf_tag *) cancel_buf.cancel_jmp_buf);
+	  DIAG_POP_NEEDS_COMMENT;
+	  cancel_buf.priv.data.prev = NULL;
+	  cancel_buf.priv.data.cleanup = NULL;
+
+	  if (__glibc_likely (! not_first_call))
+	    {
+	      self->cleanup_jmp_buf = &cancel_buf;
+	      pthread_cleanup_push (__pthread_reset_state, &clargs);
+
+	      /* Enable asynchronous cancellation.  A timer re-fire (SI_TIMER)
+		 or the timer_delete wake (SI_QUEUE) reaching the handler is
+		 ignored, so it neither cancels nor is queued.  */
+	      internal_signal_unblock_signal (SIGTIMER);
+
+	      thrfunc (sival);
+
+	      pthread_cleanup_pop (1);
+	    }
+	}
+
+      /* timer_delete sets the MSB and wakes 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)
+{
+  /* The helper thread unblocks SIGTIMER/SIGCANCEL only while running the
+     notification function; there an interval re-fire or a pthread_cancel can
+     be delivered to the SIGCANCEL handler, which must therefore already be
+     installed.  */
+  __pthread_install_sigcancel_handler ();
+
+  /* Block all signals in the helper thread but SIGSETXID.  SIGTIMER stays
+     blocked so it can be consumed synchronously with sigwaitinfo between
+     firings; it is unblocked only around the notification function.  */
+  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 +223,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..a215e213b9e 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,33 @@ 
 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);
+      /* The helper threads leaves SIGTIMER/SIGCANCEL unblocked while running
+	 the notification function, and a tgkill would  arrive as SI_TKILL
+	 and could be mistaken for a cancellation by the SIGCANCEL handler.
+	  With SI_QUEUE the handler ignores it (only SI_TKILL cancels), while
+	 a helper blocked in sigwaitinfo still wakes up, observes the MSB set
+	 above (si_code != SI_TIMER), and exits.  */
+      siginfo_t info = { 0 };
+      info.si_signo = SIGTIMER;
+      info.si_code = SI_QUEUE;
+      INTERNAL_SYSCALL_CALL (rt_tgsigqueueinfo, __getpid (), th->tid,
+			     SIGTIMER, &info);
       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);
-}