Created attachment 182523 [details] Test program: compile with `cc -lthr main.c`; Send '^t^t^t^c' to observe problem. I don't know if this is a bug or within the bounds of undefined behaviour, but I certainly found it surprising. Essentially I have a thread with all possible signals masked and I expect that it should never return the error of EINTR because it will never catch any signals. It seems however that if there are signals queued (not just pending) then when the signals are dequeued then the last started thread is interrupted even if it doesn't handle the signal. In the attached sample program if SIGINFO is sent three times, the first is handled (but then the handler blocks at `pause()`), the second is pending, and the third is queued. When then sending a SIGINT the handler unblocks, returns, then receives the pending SIGINFO and blocks again, but then the worker thread is woken and interrupted with EINTR. The test program prints '*' for every iteration of the worker thread (1 per second), 'HND' when the signal handler is entered, and 'WRK: Interrupted system call' when the worker thread is interrupted. If SIGINFO is only sent two times then the EINTR in the worker thread never occurs. If it is sent more than three times then an EINTR is received every time a queued signal is moved from the queue to pending. I don't understand this area of the kernel code enough to know where the problem is, but I don't believe a masked signal should cause an EINTR. If it is necessary to interrupt a thread for some reason when the signal is masked everywhere is there any chance that this could be forced to the main thread rather than to the last started. At least the main thread is identifiable. With the current behaviour I cannot even have a single sacrificial thread to handle these because whenever a new thread is started it becomes the target of the EINTR.
I rummaged a bit more in the code, and I might be on the wrong track, but it looks like `reschedule_signals()` is called whenever the mask is changed (such as when a handler is launched) and if the signal is still pending and in the `block` set then `sigtd()` is picks an arbitrary thread if they are all blocked, and then since the process wants to catch the signal (i.e. `SIGISMEMBER(ps->ps_sigcatch, sig)` this calls `tdsigwakeup()` to force a wakeup of the thread even if it is blocked from receiving the signal. I think either `sigtd()` should default to the main thread rather than the most recent, or else the `SIGISMEMBER` test should also check `!SIGISMEMBER(td->td_sigmask, sig)` before calling `tdsigwakeup()`. Or maybe both of these changes.
(In reply to Duane from comment #1) I agree with your analysis. OTOH, I do not quite follow why the main thread (what is this ? initial thread ? what if it terminated already ?) should receive the wakeup if it also blocks the signal. Suggestion to check the thread sigmask before calling tdsigwakeup() is IMO the right thing. Spurious EINTR were allowed by POSIX, e.g. fork() in other thread might cause this (but not on FreeBSD).
Created attachment 182542 [details] Do not wake up sleeping thread in reschedule_signals() if the signal is blocked.
(In reply to Konstantin Belousov from comment #2) It didn't occur to me that the main thread could be terminated before the others, I had assumed it was privileged in some way. I only suggested in case the spurious EINTR was required for some reason that it be directed somewhere predictable. Not having spurious EINTR at all is best. Thank you for the prompt response.
A commit references this bug: Author: kib Date: Fri May 12 15:35:00 UTC 2017 New revision: 318243 URL: https://svnweb.freebsd.org/changeset/base/318243 Log: Do not wake up sleeping thread in reschedule_signals() if the signal is blocked. The spurious wakeup might result in spurious EINTR. The reschedule_signals() function is called when the calling thread has the signal mask changed. For each newly blocked signal, we try to find a thread which might have the signal not blocked. If no such thread exists, sigtd() returns random thread, which must not be waken up. I decided that re-checking, as suggested by PR submitter, is more reasonable change than to change sigtd() interface, due to other uses of sigtd(). signotify() already performs this check. Submitted by: Duane <parakleta@darkreality.org> PR: 219228 Sponsored by: The FreeBSD Foundation MFC after: 1 week Changes: head/sys/kern/kern_sig.c
A commit references this bug: Author: kib Date: Fri May 19 09:04:19 UTC 2017 New revision: 318528 URL: https://svnweb.freebsd.org/changeset/base/318528 Log: MFC r318243: Do not wake up sleeping thread in reschedule_signals() if the signal is blocked. The spurious wakeup might result in spurious EINTR. PR: 219228 Changes: _U stable/11/ stable/11/sys/kern/kern_sig.c
A commit references this bug: Author: kib Date: Fri May 19 10:16:51 UTC 2017 New revision: 318529 URL: https://svnweb.freebsd.org/changeset/base/318529 Log: MFC r318243: Do not wake up sleeping thread in reschedule_signals() if the signal is blocked. The spurious wakeup might result in spurious EINTR. PR: 219228 Changes: _U stable/10/ stable/10/sys/kern/kern_sig.c