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

Carlos O'Donell carlos@redhat.com
Wed Jul 8 18:05:40 GMT 2026


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);
> -}


-- 
Cheers,
Carlos.



More information about the Libc-alpha mailing list