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]

Reply via email to