[PATCH v2] linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558, 27895, 29705, 32833)
enh
enh@google.com
Fri Apr 4 18:15:24 GMT 2025
fwiw, this is how bionic always worked, and i've never seen any
reports of compatibility issues with code expecting the old glibc
semantics. (to the extent that i was not aware glibc didn't already
work like this!)
On Fri, Apr 4, 2025 at 2:08 PM Adhemerval Zanella
<adhemerval.zanella@linaro.org> wrote:
>
> The current timer_create SIGEV_THREAD implementation has some
> downsides:
>
> 1. There is no way to report failure at thread creation when a
> timer triggers. It means that it might occur unreported and with
> missed events depending of the system load.
>
> 2. The backgroup thread also kept in backgroun even when there is no
> more timers, consuming resources and also misleading memory
> profile tools (BZ 29705).
>
> 3. There is a lot of metadata that required 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 wrong 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
> issues the multiple timers, thus there is no unreported missed events.
> Also, avoiding parallel timer activation also avoid possible parallel
> timer invocation to see the same overrun value.
>
> To implement using SIGTIMER internally as SIGCANCEL, it requires to
> mask out SIGCANCEL on thread creation. It essentially disable async
> thread cancellation, but POSIX requires that SIGEV_THREAD is always
> created in detached mode and cancelling detached thread s UB (glibc
> check the internal tid, but the memory referenced by pthread_t might
> not always be valid as the momento of pthread_cancel call).
>
> And to avoid the need to recreate the thread for pthread_exit call
> (and having possible unreported missed due failed thread creation),
> the SIGEV_THREAD install a cleanup handler that reset all internal
> thread state.
>
> It also prevents the re-use issue when a newly-allocated timer has
> in-flight event being delivered by the kernel (BZ 32833).
>
> Performance-wise it see it uses less CPU timer for multiple thread
> activation, although each thread now requires a sigwaitinfo which
> generate more context-switches/page-faults (check comment 7 from
> BZ 30558). I would expect that latency should improve, since it
> avoid a thread creation for each timer expiration.
>
> Checked on aarch64-linux-gnu, x86_64-linux-gnu and i686-linux-gnu.
>
> --
> 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/descr.h | 3 +
> rt/Makefile | 4 +-
> rt/tst-timer-sigmask.c | 7 +-
> rt/tst-timer6.c | 79 +++++
> sysdeps/nptl/Makefile | 2 -
> sysdeps/nptl/fork.h | 2 -
> 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 | 75 ++---
> sysdeps/unix/sysv/linux/timer_create.c | 289 ++++++++++++------
> sysdeps/unix/sysv/linux/timer_delete.c | 46 +--
> sysdeps/unix/sysv/linux/timer_routines.c | 154 ----------
> 12 files changed, 323 insertions(+), 348 deletions(-)
> create mode 100644 rt/tst-timer6.c
> delete mode 100644 sysdeps/unix/sysv/linux/timer_routines.c
>
> diff --git a/nptl/descr.h b/nptl/descr.h
> index ada6867a19..07eedda067 100644
> --- a/nptl/descr.h
> +++ b/nptl/descr.h
> @@ -413,6 +413,9 @@ struct pthread
> /* getrandom vDSO per-thread opaque state. */
> void *getrandom_buf;
>
> + /* POSIX per-process timer. */
> + int timerid;
> +
> /* Amount of end padding, if any, in this structure.
> This definition relies on getrandom_buf being last. */
> #define PTHREAD_STRUCT_END_PADDING \
> diff --git a/rt/Makefile b/rt/Makefile
> index 8880e25b64..bdda9dd660 100644
> --- a/rt/Makefile
> +++ b/rt/Makefile
> @@ -79,7 +79,8 @@ 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
> tests-internal := tst-timer-sigmask
>
> tests-time64 := \
> @@ -101,6 +102,7 @@ include ../Rules
> CFLAGS-aio_suspend.c += -fexceptions
> CFLAGS-mq_timedreceive.c += -fexceptions -fasynchronous-unwind-tables
> CFLAGS-mq_timedsend.c += -fexceptions -fasynchronous-unwind-tables
> +CFLAGS-timer_create.c += -fexceptions -fasynchronous-unwind-tables
>
> # Exclude fortified routines from being built with _FORTIFY_SOURCE
> routines_no_fortify += \
> diff --git a/rt/tst-timer-sigmask.c b/rt/tst-timer-sigmask.c
> index d8a576bba7..61b7927863 100644
> --- a/rt/tst-timer-sigmask.c
> +++ b/rt/tst-timer-sigmask.c
> @@ -39,12 +39,9 @@ thread_handler (union sigval sv)
> for (int sig = 1; sig < NSIG; sig++)
> {
> /* POSIX timers threads created to handle SIGEV_THREAD block all
> - signals except SIGKILL, SIGSTOP and glibc internals ones. */
> + signals except SIGKILL, SIGSTOP, and SIGSETXID. */
> if (sigismember (&ss, sig))
> - {
> - TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
> - TEST_VERIFY (!is_internal_signal (sig));
> - }
> + TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP && sig != SIGSETXID);
> if (test_verbose && sigismember (&ss, sig))
> printf ("%d, ", sig);
> }
> diff --git a/rt/tst-timer6.c b/rt/tst-timer6.c
> new file mode 100644
> index 0000000000..d0f3b030b6
> --- /dev/null
> +++ b/rt/tst-timer6.c
> @@ -0,0 +1,79 @@
> +/* Check re-use timer id for SIGEV_THREAD (BZ 32833)
> + Copyright (C) 2025 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 ("time_delete: %m");
> + }
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/nptl/Makefile b/sysdeps/nptl/Makefile
> index c6e15d2351..12b7cb5bc2 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 c7b4a195c1..b241ffaffa 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/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h
> index ecb00f5f3c..13b840ca08 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 bea1e0e62d..eda53be167 100644
> --- a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
> +++ b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
> @@ -8,6 +8,8 @@
> - A clockid is invalid if bits 2, 1, and 0 are all set.
> */
>
> +#include <time.h>
> +
> #define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))
> #define CPUCLOCK_PERTHREAD(clock) \
> (((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0)
> diff --git a/sysdeps/unix/sysv/linux/kernel-posix-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-timers.h
> index 3000953754..046cd3c993 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 MSB bit (INT_MAX) is used on timer_delete to
> + signal the helper thread to stop and issue the timer_delete syscall. */
>
> 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,33 @@ 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);
> + {
> + struct pthread *pthr = timerid_to_pthread (timerid);
> + return pthr->timerid & INT_MAX;
> + }
> + return (uintptr_t) timerid;
> +}
> +
> +static inline void
> +timerid_signal_delete (kernel_timer_t *timerid)
> +{
> + 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 ca377a69f4..8ec71254a7 100644
> --- a/sysdeps/unix/sysv/linux/timer_create.c
> +++ b/sysdeps/unix/sysv/linux/timer_create.c
> @@ -15,46 +15,196 @@
> License along with the GNU C Library; see the file COPYING.LIB. If
> not, see <https://www.gnu.org/licenses/>. */
>
> -#include <errno.h>
> -#include <pthread.h>
> -#include <signal.h>
> -#include <stdlib.h>
> -#include <string.h>
> -#include <time.h>
> -#include <sysdep.h>
> -#include <internaltypes.h>
> +#include <jmpbuf-unwind.h>
> +#include <kernel-posix-cpu-timers.h>
> +#include <kernel-posix-timers.h>
> +#include <ldsodefs.h>
> +#include <libc-internal.h>
> +#include <libc-lock.h>
> #include <pthreadP.h>
> -#include "kernel-posix-timers.h"
> -#include "kernel-posix-cpu-timers.h"
> #include <shlib-compat.h>
>
> +struct timer_helper_thread_args_t
> +{
> + /* The barrier is used to synchronize the arguments copy from timer_create
> + and the SIGEV_THREAD thread and to instruct the thread to exit if the
> + timer_create syscall fails. */
> + pthread_barrier_t b;
> + struct sigevent *evp;
> +};
> +
> +struct cleanup_args_t
> +{
> + struct pthread_unwind_buf *cleanup_jmp_buf;
> + jmp_buf jb;
> +};
> +
> +/* Reset internal thread state if the callback issues pthread_exit. It avoids
> + recreating the thread and having possible unreported missed events due
> + thread creation failure. */
> +static void
> +timer_helper_thread_cleanup (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 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;
> +
> + /* Re-initialize the TLS. */
> + _dl_allocate_tls_init (TLS_TPADJ (self), true);
> +
> + /* Reset to the expected initial signal mask. */
> + internal_sigset_t ss;
> + internal_sigfillset (&ss);
> + internal_sigdelset (&ss, SIGSETXID);
> + internal_sigprocmask (SIG_SETMASK, &ss, NULL);
> +
> + /* There is no need to perform any additional cleanup by the frames. */
> + struct __jmp_buf_tag *env = args->jb;
> + __longjmp (env[0].__jmpbuf, 1);
> +}
> +
> +static void *
> +timer_helper_thread (void *arg)
> +{
> + struct pthread *self = THREAD_SELF;
> + struct timer_helper_thread_args_t *args = arg;
> + struct 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->b);
> + /* timer_create syscall failed. */
> + if (self->exiting)
> + return 0;
> +
> + while (1)
> + {
> + siginfo_t si;
> + while (__sigwaitinfo (&sigtimer_set, &si) < 0);
> +
> + if (si.si_code == SI_TIMER && !setjmp (clargs.jb))
> + {
> + pthread_cleanup_push (timer_helper_thread_cleanup, &clargs);
> + thrfunc (sival);
> + pthread_cleanup_pop (0);
> + }
> +
> + /* timer_delete will set the MSB and signal the thread. */
> + if (self->timerid < 0)
> + break;
> + }
> +
> + /* Clear the MSB bit set by timer_delete. */
> + INTERNAL_SYSCALL_CALL (timer_delete, timerid_clear (self->timerid));
> +
> + return NULL;
> +}
> +
> +static int
> +timer_create_sigev_thread (clockid_t clockid, struct sigevent *evp,
> + timer_t *timerid, pthread_attr_t *attr)
> +{
> + /* Block all signals in the helper thread but SIGSETXID. */
> + sigset_t ss;
> + __sigfillset (&ss);
> + __sigdelset (&ss, SIGSETXID);
> + if (__pthread_attr_setsigmask_internal (attr, &ss) < 0)
> + return -1;
> +
> + struct timer_helper_thread_args_t args;
> + __pthread_barrier_init (&args.b, NULL, 2);
> + args.evp = evp;
> +
> + 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;
> + struct sigevent kevp =
> + {
> + .sigev_value.sival_ptr = NULL,
> + .sigev_signo = SIGTIMER,
> + .sigev_notify = SIGEV_THREAD_ID,
> + ._sigev_un = { ._tid = pthr->tid },
> + };
> +
> + kernel_timer_t ktimerid;
> + if (INLINE_SYSCALL_CALL (timer_create, clockid, &kevp, &ktimerid) < 0)
> + {
> + ktimerid = -1;
> + /* On timer creation failure we need to signal the helper thread to
> + exit and we can not use the an negative timerid value after the
> + ptherad_barrier_wait because we can not distinguish between
> + a timer creation failure and request to delete a timer if it happens
> + to arrive quickly (for where two timers are create in sequence,
> + where first succeeds).
> +
> + We re-use the 'exiting' member to signal the failure, it is set only
> + at pthread_create to avoid pthread_kill to send further signals.
> + Since the thread should not be user-visible, signal 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.b);
> +
> + if (ktimerid < 0)
> + 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 +214,28 @@ ___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;
> + __pthread_attr_copy (&attr, evp->sigev_notify_attributes);
> + else
> + __pthread_attr_init (&attr);
> + __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
>
> - nattr = (struct pthread_attr *) &newp->attr;
> - oattr = (struct pthread_attr *) evp->sigev_notify_attributes;
> + int r = timer_create_sigev_thread (syscall_clockid, evp, timerid,
> + &attr);
>
> - nattr->schedparam = oattr->schedparam;
> - nattr->schedpolicy = oattr->schedpolicy;
> - nattr->flags = oattr->flags;
> - nattr->guardsize = oattr->guardsize;
> - nattr->stackaddr = oattr->stackaddr;
> - nattr->stacksize = oattr->stacksize;
> - }
> + if (&attr != evp->sigev_notify_attributes)
> + __pthread_attr_destroy (&attr);
>
> - /* In any case set the detach flag. */
> - __pthread_attr_setdetachstate (&newp->attr, PTHREAD_CREATE_DETACHED);
> -
> - /* 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 } } };
> -
> - /* 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 69f26b266b..0fd3cb15f1 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,20 @@
> 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);
> -
> - /* 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);
> - }
> + struct pthread *th = timerid_to_pthread (timerid);
>
> + /* The helper thread itself will be responsible to call the
> + timer_delete syscall. */
> + timerid_signal_delete (&th->timerid);
> + /* We can send the signal directly instead of through
> + __pthread_kill_internal because the thread is not user-visible
> + and it blocks SIGTIMER. */
> + INTERNAL_SYSCALL_CALL (tgkill, __getpid (), th->tid, SIGTIMER);
> return 0;
> }
> -
> - /* The kernel timer is not known or something else bad happened.
> - Return the error. */
> - return -1;
> + return INLINE_SYSCALL_CALL (timer_delete, timerid);
> }
> versioned_symbol (libc, ___timer_delete, timer_delete, GLIBC_2_34);
> libc_hidden_ver (___timer_delete, __timer_delete)
> diff --git a/sysdeps/unix/sysv/linux/timer_routines.c b/sysdeps/unix/sysv/linux/timer_routines.c
> deleted file mode 100644
> index b2affaac91..0000000000
> --- a/sysdeps/unix/sysv/linux/timer_routines.c
> +++ /dev/null
> @@ -1,154 +0,0 @@
> -/* Copyright (C) 2003-2025 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);
> -}
> --
> 2.43.0
>
More information about the Libc-alpha
mailing list