[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