Repository navigation
sched/timer: Queue one signal of a POSIX timer at a time. - #21152
Merged
Merged
Conversation
casaroli
requested review from
GUIDINGLI,
Ouss4,
anchao,
jerpelea,
masayuki2009,
pkarashchenko,
pussuw,
xiaoxiang781216 and
yamt
as code owners
October 30, 2026 06:59
|
POSIX: "Only a single signal shall be queued to the process for a given timer at any point in time. When a timer for which a signal is still pending expires, no signal shall be queued, and a timer overrun shall occur." NuttX queued one more signal at each expiration. A blocked real-time signal and a signal for a handler of a thread that does not run are not merged, so the pending signals grew with each expiration. The timer runs in the watchdog interrupt, which cannot allocate more: when the preallocated ones ran out, nxsig_dispatch() failed with EAGAIN and the DEBUGVERIFY() of timer_signotify() stopped the system. The pending and the queued signal entries now keep their source (the timer, or NULL), which nxsig_tcbdispatch(), nxsig_dispatch(), nxsig_notification_source() and group_signal() pass on, also when a pending signal is unmasked. At an expiration the timer checks with nxsig_source_queued() whether its last signal is still pending or queued (the same source and signal number): if so, or if no signal can be queued, the expiration is an overrun. The overrun count starts again with each signal that is queued, and the periods that the watchdog missed add to it; timer_getoverrun() gives it, at most DELAYTIMER_MAX. The other callers of nxsig_tcbdispatch() and nxsig_dispatch() pass NULL; nxsig_notification(), which drivers use, keeps its arguments and passes NULL. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
NuttX keeps pending signal structures for interrupt handlers (CONFIG_SIG_PREALLOC_IRQ_ACTIONS), and nxsig_alloc_pendingsignal() and nxsig_alloc_pendingsigaction() take them in an interrupt when the free lists are empty. But nxsig_alloc_dyn_pending() returned EAGAIN in an interrupt as soon as a free list was empty, so the reserved structures were never used: when other tasks held the few preallocated pending signals, every signal sent from an interrupt handler (a POSIX timer, a driver notification) failed. In an interrupt, fail only when a needed free list and its reserve are both empty. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
…nal(). timer_getoverrun() counts the overruns of POSIX now: remove the notes that say it is not implemented. group_signal() takes the source of the signal. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli
force-pushed
the
fb-timer-overrun-up
branch
from
November 1, 2026 10:50
cfb672a to
769b6a1
Compare
casaroli
marked this pull request as draft
November 1, 2026 11:28
casaroli
marked this pull request as ready for review
November 1, 2026 11:28
xiaoxiang781216
approved these changes
Nov 1, 2026
acassis
approved these changes
Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A periodic POSIX timer whose signal is not taken at once queued one more signal at each expiration. A blocked real-time signal and a signal for the handler of a thread that does not run are not merged, so the pending signals grew until the preallocated ones ran out. The timer runs in the watchdog interrupt, which cannot allocate more:
nxsig_dispatch()then failed withEAGAIN, and theDEBUGVERIFY()oftimer_signotify()stopped the system (withCONFIG_DEBUG_ASSERTIONS). An unprivileged program can do this in a few milliseconds.nxsig_source_queued()whether its last signal is still pending or queued (the same source and signal number). If so, or if no signal can be queued, the expiration is an overrun. The overrun count starts again with each signal that is queued, and the periods that the watchdog missed add to it.timer_getoverrun()gives it, at mostDELAYTIMER_MAX. The internalnxsig_tcbdispatch(),nxsig_dispatch()andgroup_signal()get asourceargument; their other callers pass NULL.nxsig_notification(), which drivers use, keeps its arguments; only the timer calls the internalnxsig_notification_source().nxsig_alloc_dyn_pending()returnedEAGAINin an interrupt as soon as a free list was empty, so the structures reserved for interrupt handlers (CONFIG_SIG_PREALLOC_IRQ_ACTIONS) were never used. Now it fails only when the free list and its reserve are both empty.timer_getoverrun()is not "not implemented" any more; the signature ofgroup_signal().Commit 2 does not need commit 1. It is in this PR because the same test finds it; it can go in a PR of its own if reviewers prefer.
Impact
timer_getoverrun()counts the expirations in between, as POSIX says. A timer whose signals are not taken does not stop the system.tg_lock).Testing
Test program
timerovr.c(7 cases; not in nuttx-apps). The POSIX timer test of ostest passes before and after.waitmask: after asigtimedwait()with a zero timeout, a blocked signal that comes duringusleep()stays pending, and the sleep lasts its time (master has this fix since #21065)drain: threesigqueue()of a real-time signal are taken three times (a check of the test)blocked: a 2 ms timer, its real-time signal blocked for 300 ms: one signal pending,timer_getoverrun()=DELAYTIMER_MAXagain: after the signal was taken, the timer queues a new onetwo: two timers with the same signal and value: one signal of eachhandler: a handler for a thread that cannot run for 200 ms (SIGEV_THREAD_ID, a busy thread of higher priority) runs onceAssertion failed at file: timer/timer_settime.c:79qemu-armv8a:knsh(DEBUG_ASSERTIONSis on in this config)handlercaseknshandnsh)qemu-armv8a:knsh+DEBUG_FEATURESqemu-armv8a:nsh(the test as a builtin app)handlercaseqemu-armv8a:nsh_smp, 2 CPUs (the test as a builtin app; thehandlercase keeps both threads on CPU 0)The "this PR" column is from this head, rebased on master 290d9ec (nuttx-apps d40132fba).
Builds (make), each without new warnings:
nucleo-f072rb:nsh(flat, armv6-m, partial signals, no pthreads, no POSIX timers),nucleo-f302r8:ihm07m1_f32(flat, no signals),esp32s3-devkit:knsh(kernel, xtensa; it builds the twonxsig_tcbdispatch()callers inarch/xtensa/src/esp32s3),rv-virt:pnsh64(protected, risc-v),rv-virt:knsh64(kernel, risc-v),qemu-armv8a:nsh_smp(SMP),qemu-armv8a:nshwithDISABLE_PTHREAD(no group members; ostest and getprime off, they need pthreads): OK.lm3s6965-ek:qemu-protected(protected, armv7-m) overflows its kernel flash by 720 bytes with the local GCC 13.2 (master: 288 bytes); withCONFIG_DISABLE_MQUEUE_SYSV(#21321) it links.tools/checkpatch.sh -c -u -m -gpasses.