diff mbox series

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

Message ID 20230627180212.841916-1-adhemerval.zanella@linaro.org
State New
Headers show
Series linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558, 27895, 29705) | expand

Commit Message

Adhemerval Zanella Netto June 27, 2023, 6:02 p.m. UTC
The current timer_create SIGEV_THREAD implementation has some
downsides:

  1. There is no way to report failure at thread creation when a
     timer triggers.  It means that it might occur unreported 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).

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.

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 x86_64-linux-gnu and i686-linux-gnu.
---
 nptl/descr.h                                  |   3 +
 rt/Makefile                                   |   1 +
 rt/tst-timer-sigmask.c                        |  59 ++--
 sysdeps/nptl/fork.h                           |   2 -
 sysdeps/pthread/tst-cancel28.c                |  44 ++-
 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 |  54 +---
 sysdeps/unix/sysv/linux/timer_create.c        | 294 ++++++++++++------
 sysdeps/unix/sysv/linux/timer_delete.c        |  39 +--
 sysdeps/unix/sysv/linux/timer_routines.c      | 155 +--------
 11 files changed, 283 insertions(+), 378 deletions(-)
diff mbox series

Patch

diff --git a/nptl/descr.h b/nptl/descr.h
index 746a4b9e4a..0874c2d64c 100644
--- a/nptl/descr.h
+++ b/nptl/descr.h
@@ -408,6 +408,9 @@  struct pthread
   /* rseq area registered with the kernel.  */
   struct rseq rseq_area;
 
+  /* POSIX per-process timer.  */
+  int timerid;
+
   /* This member must be last.  */
   char end_padding[];
 
diff --git a/rt/Makefile b/rt/Makefile
index a97333dc02..0c93a7f79d 100644
--- a/rt/Makefile
+++ b/rt/Makefile
@@ -99,6 +99,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
 
 LDFLAGS-rt.so = -Wl,--enable-new-dtags,-z,nodelete
 
diff --git a/rt/tst-timer-sigmask.c b/rt/tst-timer-sigmask.c
index a6c6d53778..3a2504ade8 100644
--- a/rt/tst-timer-sigmask.c
+++ b/rt/tst-timer-sigmask.c
@@ -29,29 +29,43 @@ 
 
 static pthread_barrier_t barrier;
 
+static void
+__attribute_used__
+cl (void *arg)
+{
+  xpthread_barrier_wait (&barrier);
+}
+
 static void
 thread_handler (union sigval sv)
 {
-  sigset_t ss;
-  sigprocmask (SIG_BLOCK, NULL, &ss);
-  if (test_verbose > 0)
-    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.  */
-      if (sigismember (&ss, sig))
-	{
-	  TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
-	  TEST_VERIFY (!is_internal_signal (sig));
-	}
-      if (test_verbose && sigismember (&ss, sig))
-	printf ("%d, ", sig);
-    }
-  if (test_verbose > 0)
-    printf ("}\n");
+  /* POSIX timers threads created to handle SIGEV_THREAD block all
+     signals except SIGKILL, SIGSTOP, and SIGSETXID.  */
+  sigset_t expected_set = {
+    .__val = {[0 ...  __NSIG_WORDS-1 ] = -1 }
+  };
+  __sigdelset (&expected_set, SIGSETXID);
+  __sigdelset (&expected_set, SIGKILL);
+  __sigdelset (&expected_set, SIGSTOP);
 
-  xpthread_barrier_wait (&barrier);
+  sigset_t ss;
+  sigprocmask (SIG_SETMASK, NULL, &ss);
+
+  TEST_COMPARE_BLOB (&ss.__val, __NSIG_BYTES, &expected_set, __NSIG_BYTES);
+
+  /* Unblock some signals, the signal mask should be reset by the timer_create
+     helper thread.  */
+  {
+    sigset_t toset;
+    sigemptyset (&toset);
+    sigaddset (&toset, SIGHUP);
+    sigaddset (&toset, SIGILL);
+    sigprocmask (SIG_UNBLOCK, &toset, NULL);
+  }
+
+  pthread_cleanup_push (cl, NULL);
+  pthread_exit (NULL);
+  pthread_cleanup_pop (0);
 }
 
 static int
@@ -66,11 +80,12 @@  do_test (void)
 
   xpthread_barrier_init (&barrier, NULL, 2);
 
-  struct itimerspec trigger = { 0 };
-  trigger.it_value.tv_nsec = 1000000;
+  struct itimerspec trigger = { { 0, 1000000 }, { 0, 1000000 } };
   TEST_COMPARE (timer_settime (timerid, 0, &trigger, NULL), 0);
 
-  xpthread_barrier_wait (&barrier);
+  enum { TIMERS_TO_CHECK = 2 };
+  for (int i = 0; i < TIMERS_TO_CHECK; i++)
+    xpthread_barrier_wait (&barrier);
 
   return 0;
 }
diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h
index ee7967416c..40ed5cf7e7 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>
@@ -45,7 +44,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);
 }
 
 /* In case of a fork() call the memory allocation in the child will be
diff --git a/sysdeps/pthread/tst-cancel28.c b/sysdeps/pthread/tst-cancel28.c
index ae55f38210..2eb78acede 100644
--- a/sysdeps/pthread/tst-cancel28.c
+++ b/sysdeps/pthread/tst-cancel28.c
@@ -28,48 +28,64 @@ 
 #include <support/xthread.h>
 
 static pthread_barrier_t barrier;
-static pthread_t timer_thread;
+
+#define VALUE1_INITIAL 0
+static __thread unsigned int value1 = VALUE1_INITIAL;
+#define VALUE2_INITIAL 0xdeadbeef
+static __thread unsigned int value2 = VALUE2_INITIAL;
+
+static int cl_called = 0;
 
 static void
 cl (void *arg)
 {
-  xpthread_barrier_wait (&barrier);
+  cl_called++;
+  xpthread_barrier_wait (&barrier); /* 1 */
 }
 
 static void
 thread_handler (union sigval sv)
 {
-  timer_thread = pthread_self ();
+  /* Check if thread state is correctly reset after a pthread_exit.  */
+  TEST_COMPARE (value1, VALUE1_INITIAL);
+  TEST_COMPARE (value2, VALUE2_INITIAL);
+
+  value1 = VALUE2_INITIAL;
+  value2 = VALUE1_INITIAL;
 
-  xpthread_barrier_wait (&barrier);
+  xpthread_barrier_wait (&barrier);  /* 0 */
 
   pthread_cleanup_push (cl, NULL);
-  while (1)
-    clock_nanosleep (CLOCK_REALTIME, 0, &(struct timespec) { 1, 0 }, NULL);
+  pthread_exit (NULL);
   pthread_cleanup_pop (0);
 }
 
 static int
 do_test (void)
 {
-  struct sigevent sev = { 0 };
-  sev.sigev_notify = SIGEV_THREAD;
-  sev.sigev_notify_function = &thread_handler;
+  struct sigevent sev = {
+    .sigev_notify = SIGEV_THREAD,
+    .sigev_notify_function = &thread_handler,
+  };
 
   timer_t timerid;
   TEST_COMPARE (timer_create (CLOCK_REALTIME, &sev, &timerid), 0);
 
   xpthread_barrier_init (&barrier, NULL, 2);
 
-  struct itimerspec trigger = { 0 };
-  trigger.it_value.tv_nsec = 1000000;
+  struct itimerspec trigger = { { 0, 1000000 }, { 0, 1000000 } };
   TEST_COMPARE (timer_settime (timerid, 0, &trigger, NULL), 0);
 
-  xpthread_barrier_wait (&barrier);
+  enum { TIMERS_TO_CHECK = 4 };
+  for (int i = 0; i < TIMERS_TO_CHECK; i++)
+    {
+      /* Check the thread local values.  */
+      xpthread_barrier_wait (&barrier);  /* 0 */
 
-  xpthread_cancel (timer_thread);
+      xpthread_barrier_wait (&barrier);  /* 1 */
+    }
 
-  xpthread_barrier_wait (&barrier);
+  TEST_COMPARE (cl_called, TIMERS_TO_CHECK);
 
   return 0;
 }
diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h
index 43c3c0b4a0..e83b48a58e 100644
--- a/sysdeps/unix/sysv/linux/internal-signals.h
+++ b/sysdeps/unix/sysv/linux/internal-signals.h
@@ -99,12 +99,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 9d86bb1dab..cd6bf89acd 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,9 @@  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 is always 8-byte aligned.  */
 
 static inline timer_t
 kernel_timer_to_timerid (kernel_timer_t ktimerid)
@@ -80,7 +42,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,17 +53,17 @@  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;
+    return timerid_to_pthread (timerid)->timerid;
   else
     return (kernel_timer_t) ((uintptr_t) timerid);
 }
diff --git a/sysdeps/unix/sysv/linux/timer_create.c b/sysdeps/unix/sysv/linux/timer_create.c
index b7e84e2bb1..1aa13abb21 100644
--- a/sysdeps/unix/sysv/linux/timer_create.c
+++ b/sysdeps/unix/sysv/linux/timer_create.c
@@ -15,46 +15,200 @@ 
    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.  */
+#ifndef SHARED
+  if (&__call_tls_dtors != NULL)
+#endif
+    __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;
+
+  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->timerid < 0)
+    return 0;
+
+  struct cleanup_args_t clargs = {
+    .cleanup_jmp_buf = self->cleanup_jmp_buf
+  };
+
+  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 (atomic_load_relaxed (&self->timerid) < 0)
+	break;
+    }
+
+  /* Clear the MSB bit set by timer_delete.  */
+  INTERNAL_SYSCALL_CALL (timer_delete, self->timerid & INT_MAX);
+
+  return NULL;
+}
+
+static int
+timer_create_sigev_thread (clockid_t syscall_clockid, struct sigevent *evp,
+			   timer_t *timerid)
+{
+  int ret = -1;
+
+  pthread_attr_t attr;
+  if (evp->sigev_notify_attributes != NULL)
+    __pthread_attr_copy (&attr, evp->sigev_notify_attributes);
+  else
+    __pthread_attr_init (&attr);
+  __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+
+  /* Block all signals in the helper thread but SIGSETXID.  */
+  sigset_t ss;
+  __sigfillset (&ss);
+  __sigdelset (&ss, SIGSETXID);
+  ret = __pthread_attr_setsigmask_internal (&attr, &ss);
+  if (ret != 0)
+    goto out;
+
+  struct timer_helper_thread_args_t args;
+  __pthread_barrier_init (&args.b, NULL, 2);
+  args.evp = evp;
+
+  pthread_t th;
+  ret = __pthread_create (&th, &attr, timer_helper_thread, &args);
+  if (ret != 0)
+    {
+      __set_errno (ret);
+      goto out;
+    }
+
+  struct sigevent kevp = {
+    .sigev_value.sival_ptr = NULL,
+    .sigev_signo = SIGTIMER,
+    .sigev_notify = SIGEV_THREAD_ID,
+    ._sigev_un = { ._pad = { [0] = ((struct pthread *)th)->tid } }
+  };
+
+  struct pthread *pthr = (struct pthread *)th;
+
+  kernel_timer_t ktimerid;
+  if (INLINE_SYSCALL_CALL (timer_create, syscall_clockid, &kevp, &ktimerid) < 0)
+    pthr->timerid = -1;
+  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 (timerid >= 0)
+    *timerid = pthread_to_timerid (th);
+
+  ret = 0;
+out:
+  if (&attr != evp->sigev_notify_attributes)
+    __pthread_attr_destroy (&attr);
+
+  return ret;
+}
+
 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);
-
-    /* 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))
-      {
-	struct sigevent local_evp;
+  clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
+			       ? PROCESS_CLOCK
+			       : clock_id == CLOCK_THREAD_CPUTIME_ID
+			       ? THREAD_CLOCK
+			       : clock_id);
 
+  switch (evp != NULL ? evp->sigev_notify : SIGEV_SIGNAL)
+    {
+    case SIGEV_NONE:
+    case SIGEV_SIGNAL:
+    case SIGEV_THREAD_ID:
+      {
+	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 +218,15 @@  ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
 
 	*timerid = kernel_timer_to_timerid (ktimerid);
       }
-    else
-      {
-	/* 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);
-	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;
-	  }
-
-	/* 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);
-      }
-  }
+      break;
+    case SIGEV_THREAD:
+      if (timer_create_sigev_thread (syscall_clockid, evp, timerid) < 0)
+	return -1;
+      break;
+    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 a611d5013a..56f8c3ed1e 100644
--- a/sysdeps/unix/sysv/linux/timer_delete.c
+++ b/sysdeps/unix/sysv/linux/timer_delete.c
@@ -26,42 +26,17 @@ 
 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.  */
+      atomic_fetch_or_relaxed (&th->timerid, INT_MIN);
+      __pthread_kill_internal ((pthread_t) th, 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
index 2ad1e7baf2..6e25b021ab 100644
--- a/sysdeps/unix/sysv/linux/timer_routines.c
+++ b/sysdeps/unix/sysv/linux/timer_routines.c
@@ -1,154 +1 @@ 
-/* Copyright (C) 2003-2023 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);
-}
+/* Empty.  */