casaroli opened a new pull request, #21152: URL: https://github.com/apache/nuttx/pull/21152
## 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 with `EAGAIN`, and the `DEBUGVERIFY()` of `timer_signotify()` stopped the system (with `CONFIG_DEBUG_ASSERTIONS`). An unprivileged program can do this in a few milliseconds. 1. **sched/timer: Queue one signal of a POSIX timer at a time.** 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." The pending and the queued signal entries now keep their source (the timer, or NULL). 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`. `nxsig_tcbdispatch()`, `nxsig_dispatch()` and `nxsig_notification()` keep their signatures (they call new internal `*_source()` forms with NULL). The internal `group_signal()` gets a `source` argument; its two callers are changed. 2. **sched/signal: Let an interrupt handler use the reserved pending signals.** `nxsig_alloc_dyn_pending()` returned `EAGAIN` in 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. 3. **sched/signal: Clear the wait mask when sigtimedwait() fails at once.** When the wait of `nxsig_timedwait()` failed at once (a timeout of zero), it returned without emptying `sigwaitmask`. The next sleep of the thread (`usleep()`, `nanosleep()`) then took any blocked signal of that set as its own: the signal woke the sleep early and was lost. 4. **Documentation**: `timer_getoverrun()` is not "not implemented" any more; the signature of `group_signal()`. Commits 2 and 3 do not need commit 1. They are in this PR because the same test finds them; they can go in a PR of their own if reviewers prefer. ## Impact - A periodic POSIX timer has at most one queued signal, and `timer_getoverrun()` counts the expirations in between, as POSIX says. A timer whose signals are not taken does not stop the system. - Signals from interrupt handlers (timers, driver notifications) can use the reserved structures. - A blocked signal is not lost after a `sigtimedwait()` that found nothing. - Kernel-internal change only: no public header, no user-visible structure. The same code in flat, protected and kernel builds, and in SMP builds (the search holds the critical section and the group's `tg_lock`). ## Testing Test program `timerovr.c` (7 cases; not in nuttx-apps). The POSIX timer test of ostest passes before and after. | Case | master | this PR | |---|---|---| | `waitmask`: after a `sigtimedwait()` with a zero timeout, a blocked signal that comes during `usleep()` stays pending, and the sleep lasts its time | FAIL (the signal was lost; the sleep ended after 10 ms of 60) | PASS | | `drain`: three `sigqueue()` of a real-time signal are taken three times (a check of the test) | PASS | PASS | | `blocked`: a 2 ms timer, its real-time signal blocked for 300 ms: one signal pending, `timer_getoverrun()` = `DELAYTIMER_MAX` | FAIL (0 pending) | PASS | | `again`: after the signal was taken, the timer queues a new one | FAIL | PASS | | `two`: two timers with the same signal and value: one signal of each | FAIL (1 pending) | PASS | | `handler`: a handler for a thread that cannot run for 200 ms (`SIGEV_THREAD_ID`, a busy thread of higher priority) runs once | system stop: `Assertion failed at file: timer/timer_settime.c:79` | PASS | | Build | Config (QEMU, HVF) | master (192ac6d3fbc) | this PR | |---|---|---|---| | kernel, arm64 | `qemu-armv8a:knsh` (`DEBUG_ASSERTIONS` is on in this config) | 4 FAIL, then the system stops in the `handler` case | all PASS; ostest 0 (`knsh` and `nsh`) | | kernel, arm64 | `qemu-armv8a:knsh` + `DEBUG_FEATURES` | | all PASS | | flat, arm64 | `qemu-armv8a:nsh` (the test as a builtin app) | 4 FAIL, then the system stops in the `handler` case | all PASS; ostest 0 | | flat, arm64, SMP | `qemu-armv8a:nsh_smp`, 2 CPUs (the test as a builtin app; the `handler` case keeps both threads on CPU 0) | ostest does not end (its spinlock test) | all PASS; ostest does not end in its spinlock test, the same as on master | Builds (make): `nucleo-f072rb:nsh` (flat, armv6-m, partial signals, no pthreads, no POSIX timers), `lm3s6965-ek:qemu-protected` (protected, armv7-m), `rv-virt:pnsh64` (protected, risc-v), `rv-virt:knsh64` (kernel, risc-v), `qemu-armv8a:nsh_smp` (SMP), `qemu-armv8a:nsh` with `DISABLE_PTHREAD` (no group members), `qemu-armv8a:nsh` with `ENABLE_PARTIAL_SIGNALS` (POSIX timers on): OK. `tools/checkpatch.sh -c -u -m -g` passes. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
