[PATCH v6] linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558, 27895, 29705, 32833)
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Tue Jul 7 14:11:55 GMT 2026
On 06/07/26 19:09, Carlos O'Donell wrote:
> On 5/14/26 7:14 AM, Adhemerval Zanella wrote:
>> The current timer_create SIGEV_THREAD implementation has some
>> downsides:
>
> Three issues:
>
> 1. timerid_clear is unused but still defined. Please delete.
> 2. Incomplete sentence "(or force a fresh allocation if)"?
Ack.
> 3. Conformance of pthread_cancel vs notification thread. See POC.
>
>> 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.
>
> OK. This is indeed the most serious issue. It prevents the use of SIGEV_THREAD
> in functional safety contexts.
>
>> 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).
>
> OK. When we delete the timer the helper thread is exited.
>
>> 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.
>
> OK. We drop all of that.
>
>> 4. timer_create does not propagate all thread attributes to the new
>> thread (BZ 27895).
>
> OK. We fix that.
>
>> 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).
>
> OK.
>
>> 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.
>
> OK.
>
>> To implement using SIGTIMER internally as SIGCANCEL, it requires to
>> mask out SIGCANCEL on thread creation. It essentially disables async
>> thread cancellation, but POSIX requires that SIGEV_THREAD is always
>> created in detached mode and cancelling a detached thread is UB (glibc
>> checks the internal tid, but the memory referenced by pthread_t might
>> not always be valid at the moment of a pthread_cancel call).
>
> Unfortunately it is not UB if another thread can prove the thread is
> alive, and you can, which creates a conformance issue (POC included).
>
> For example:
>
> thread A
> -> timer_create, SIGEV_THREAD
> -> timer fires
>
> thread S
> -> sigwaitinfo returns.
> -> run handler
> -> Call pthread_self
> -> Pass result to thread B thread B
> -> enable async cancel -> Read pthread_t
> -> while (1); -> pthread_cancel on S
>
> It's possible the handler is passing data to other worker threads
> and is sharing enough information to guarnatee that it is alive (and
> that pthread_t is valid despite detached state), and needs asynchronous
> cancellation, at which point it is valid to act on pthread_cancel().
>
> The implementation prevents asynchronous cancellation from being used
> because of the re-used same-signal, with the pthread_cancel() call not
> acting.
>
> Do we need another internal signal for this?
>
> Or can we just reject pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, ...)
> for the helper thread and document this issue?
>
> A signal needs to be delivered to interrupt the process, but to do that
> requires unblocking a signal. We have SIGSETXID unblocked. Could
> cancellation requests also be sent via the multiplexed mechansim?
Indeed this is not strickly UB, so the commit message is misleading. And I agree
that this is a regression that prevents the asynchronous cancellation.
I think there is no need to reserver *another* internal signal, but at same time
it does result in a clear implementation. This new scheme where the helper thread
does both the sigwaitinfo *and* runs thrfunc directly would require to eager-install
the sigcancel handler at the timer creation. It couples the timer_create with some
pthread internal code, but it is doable.
I will send a new version.
>
> Assisted-by: LLM
> ~~~
> /* POSIX requires SIGEV_THREAD notifications to behave as if a new thread
> was created for each delivery. Such a thread must honor asynchronous
> cancellation once the notify function enables it.
>
> glibc's SIGEV_THREAD helper blocks all signals except SIGSETXID at thread
> creation (see timer_create_sigev_thread), which prevents SIGCANCEL from
> being delivered. This test demonstrates the resulting non-conformance:
> pthread_cancel on the pthread_t obtained from pthread_self inside the
> notify function has no effect and the test times out. */
>
> #include <errno.h>
> #include <pthread.h>
> #include <semaphore.h>
> #include <signal.h>
> #include <time.h>
> #include <unistd.h>
>
> #include <support/check.h>
>
> static pthread_t handler_thread;
> static sem_t handler_started;
>
> static void
> on_timer (union sigval sv)
> {
> handler_thread = pthread_self ();
> if (sem_post (&handler_started) != 0)
> FAIL_EXIT1 ("sem_post from handler: %m");
>
> TEST_COMPARE (pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL), 0);
> TEST_COMPARE (pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL), 0);
>
> while (1)
> ;
> }
>
> static int
> do_test (void)
> {
> if (sem_init (&handler_started, 0, 0) != 0)
> FAIL_EXIT1 ("sem_init: %m");
>
> 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");
>
> struct itimerspec its =
> { .it_value = { .tv_nsec = 10000000 /* 0.01s */ } };
> if (timer_settime (timerid, 0, &its, NULL) == -1)
> FAIL_EXIT1 ("timer_settime: %m");
>
> if (sem_wait (&handler_started) != 0)
> FAIL_EXIT1 ("sem_wait: %m");
>
> /* Give the handler time to enter the busy loop. */
> usleep (10000);
>
> if (pthread_cancel (handler_thread) != 0)
> FAIL_EXIT1 ("pthread_cancel: %m");
>
> /* A conforming implementation cancels the notify thread; detect that by
> waiting until pthread_kill no longer finds it. With the current glibc
> SIGEV_THREAD helper this never happens and the test times out. */
> for (;;)
> {
> int err = pthread_kill (handler_thread, 0);
> if (err == ESRCH)
> break;
> if (err != 0)
> FAIL_EXIT1 ("pthread_kill: %m");
> usleep (10000);
> }
>
> struct itimerspec its_stop = { 0 };
> if (timer_settime (timerid, 0, &its_stop, NULL) == -1)
> FAIL_EXIT1 ("timer_settime (disarm): %m");
> if (timer_delete (timerid) == -1)
> FAIL_EXIT1 ("timer_delete: %m");
>
> if (sem_destroy (&handler_started) != 0)
> FAIL_EXIT1 ("sem_destroy: %m");
>
> return 0;
> }
>
> #define TIMEOUT 3
> #include <support/test-driver.c>
> ~~~
Thanks, I will use this a base for a regression testcase. >
More information about the Libc-alpha
mailing list