Bug 219228 - EINTR on thread with full signal mask.
Summary: EINTR on thread with full signal mask.
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 10.3-STABLE
Hardware: amd64 Any
: --- Affects Only Me
Assignee: freebsd-bugs (Nobody)
URL:
Keywords: patch
Depends on:
Blocks:
 
Reported: 2017-05-12 00:44 UTC by Duane
Modified: 2017-07-27 07:13 UTC (History)
3 users (show)

See Also:


Attachments
Test program: compile with `cc -lthr main.c`; Send '^t^t^t^c' to observe problem. (1.00 KB, text/x-csrc)
2017-05-12 00:44 UTC, Duane
no flags Details
Do not wake up sleeping thread in reschedule_signals() if the signal is blocked. (609 bytes, patch)
2017-05-12 10:35 UTC, Konstantin Belousov
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Duane 2017-05-12 00:44:16 UTC
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.
Comment 1 Duane 2017-05-12 01:04:35 UTC
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.
Comment 2 Konstantin Belousov freebsd_committer freebsd_triage 2017-05-12 10:34:59 UTC
(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).
Comment 3 Konstantin Belousov freebsd_committer freebsd_triage 2017-05-12 10:35:51 UTC
Created attachment 182542 [details]
Do not wake up sleeping thread in reschedule_signals() if the signal is blocked.
Comment 4 Duane 2017-05-12 14:20:52 UTC
(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.
Comment 5 commit-hook freebsd_committer freebsd_triage 2017-05-12 15:35:53 UTC
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
Comment 6 commit-hook freebsd_committer freebsd_triage 2017-05-19 09:04:26 UTC
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
Comment 7 commit-hook freebsd_committer freebsd_triage 2017-05-19 10:17:28 UTC
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