aboutsummaryrefslogtreecommitdiff
path: root/rt
diff options
context:
space:
mode:
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>2023-06-19 14:11:58 -0300
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>2025-04-04 14:57:36 -0300
commit5bc7f090051c094656b71c9cbeb737040c1aedf6 (patch)
tree5b1b74c3b9dc4987699b416b51d12a2f43be7dfa /rt
parentc8e73a1492b01b9b0c189d6a5c53a5a697827bae (diff)
downloadglibc-azanella/bz30558-posix_timer.tar.xz
glibc-azanella/bz30558-posix_timer.zip
linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558, 27895, 29705, 32833)azanella/bz30558-posix_timer
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 x86_64-linux-gnu and i686-linux-gnu.
Diffstat (limited to 'rt')
-rw-r--r--rt/Makefile4
-rw-r--r--rt/tst-timer-sigmask.c7
-rw-r--r--rt/tst-timer6.c79
3 files changed, 84 insertions, 6 deletions
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>