[0/4] Introduce scoped_ignore_signal & make it thread safe

Message ID 20210615111429.1879286-1-pedro@palves.net
Headers show
Series
  • Introduce scoped_ignore_signal & make it thread safe
Related show

Message

Pedro Alves June 15, 2021, 11:14 a.m.
For the Ctrl-C rework series I posted recently, I stared at code using
scoped_ignore_sigttou a lot, and it annoyed me that it isn't thread
safe, because it changes the signal's disposition.

Very recently, we got a new scoped_ignore_sigpipe class modelled on
scoped_ignore_sigttou, which made me want to fix this before it ever
becomes a (hard to debug) problem.  I mentioned this here:

 https://sourceware.org/pipermail/gdb-patches/2021-June/179958.html

This series then:

  - Moves scoped_ignore_sigttou to gdbsupport/.  This patch is
    actually included in my Ctrl-C series too, I just borrowed it from
    there.

  - Introduces a scoped_ignore_signal template, to be used by both
    scoped_ignore_sigpipe and scoped_ignore_sigttou.

  - Makes scoped_ignore_signal thread-safe, by using sigprocmask +
    sigtimedwait.

  - Adds a scoped_ignore_sigpipe unit test.

You can also find this in the users/palves/scoped_ignore_signal
branch.

Pedro Alves (4):
  Move scoped_ignore_sigttou to gdbsupport/
  Introduce scoped_restore_signal
  scoped_ignore_signal: Use sigprocmask+sigtimedwait instead of signal
  Add a unit test for scoped_ignore_sigpipe

 gdb/Makefile.in                               |   2 +-
 gdb/compile/compile.c                         |  29 +---
 gdb/inf-ptrace.c                              |   1 -
 gdb/inflow.c                                  |   2 +-
 gdb/procfs.c                                  |   1 -
 gdb/ser-unix.c                                |   2 +-
 .../scoped_ignore_signal-selftests.c          | 125 ++++++++++++++++++
 gdbsupport/scoped_ignore_signal.h             |  97 ++++++++++++++
 .../scoped_ignore_sigttou.h                   |  56 +++++---
 9 files changed, 266 insertions(+), 49 deletions(-)
 create mode 100644 gdb/unittests/scoped_ignore_signal-selftests.c
 create mode 100644 gdbsupport/scoped_ignore_signal.h
 rename gdb/inflow.h => gdbsupport/scoped_ignore_sigttou.h (51%)


base-commit: c8795e1f2f4d8617f22c3bd40dad75c75482e164
-- 
2.26.2

Comments

John Baldwin June 15, 2021, 5:04 p.m. | #1
On 6/15/21 4:14 AM, Pedro Alves wrote:
> For the Ctrl-C rework series I posted recently, I stared at code using

> scoped_ignore_sigttou a lot, and it annoyed me that it isn't thread

> safe, because it changes the signal's disposition.

> 

> Very recently, we got a new scoped_ignore_sigpipe class modelled on

> scoped_ignore_sigttou, which made me want to fix this before it ever

> becomes a (hard to debug) problem.  I mentioned this here:

> 

>   https://sourceware.org/pipermail/gdb-patches/2021-June/179958.html


I think this looks ok to me.  The one difference I can think of when
using sigpromask() instead of SIG_IGN is if the signal might be
sent to another thread instead of masked.  However, both SIGPIPE
and SIGTTOU sent in the context of write() should be sent to the
specific thread calling write().  SIGTTOU is a bit funky in that
it gets raised to the entire process group, but in FreeBSD at least
we only raise it if it is not masked for the current thread that
has invoked write().  (If the signal is masked, it isn't raised at
all.)

-- 
John Baldwin
Tom Tromey June 15, 2021, 8:16 p.m. | #2
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:


Pedro> This series then:

Pedro>   - Moves scoped_ignore_sigttou to gdbsupport/.  This patch is
Pedro>     actually included in my Ctrl-C series too, I just borrowed it from
Pedro>     there.

Pedro>   - Introduces a scoped_ignore_signal template, to be used by both
Pedro>     scoped_ignore_sigpipe and scoped_ignore_sigttou.

Pedro>   - Makes scoped_ignore_signal thread-safe, by using sigprocmask +
Pedro>     sigtimedwait.

Pedro>   - Adds a scoped_ignore_sigpipe unit test.

I read through these and it all looks reasonable to me.
Thanks for doing this.

Tom
Pedro Alves June 17, 2021, 2:38 p.m. | #3
On 2021-06-15 6:04 p.m., John Baldwin wrote:
> On 6/15/21 4:14 AM, Pedro Alves wrote:

>> For the Ctrl-C rework series I posted recently, I stared at code using

>> scoped_ignore_sigttou a lot, and it annoyed me that it isn't thread

>> safe, because it changes the signal's disposition.

>>

>> Very recently, we got a new scoped_ignore_sigpipe class modelled on

>> scoped_ignore_sigttou, which made me want to fix this before it ever

>> becomes a (hard to debug) problem.  I mentioned this here:

>>

>>   https://sourceware.org/pipermail/gdb-patches/2021-June/179958.html

> 

> I think this looks ok to me.  The one difference I can think of when

> using sigpromask() instead of SIG_IGN is if the signal might be

> sent to another thread instead of masked.  However, both SIGPIPE

> and SIGTTOU sent in the context of write() should be sent to the

> specific thread calling write().  SIGTTOU is a bit funky in that

> it gets raised to the entire process group, but in FreeBSD at least

> we only raise it if it is not masked for the current thread that

> has invoked write().  (If the signal is masked, it isn't raised at

> all.)


Yeah, I was a bit worried about that too, but what I observe on Linux
is the same as what you're describing for FreeBSD.

 - SIGTTOU isn't sent at all if the thread touching the terminal has it masked.

 - SIGPIPE is sent to the thread that writes and remains pending (hence
   the need for sigtimedwait).

     (confirmed that it was sent to the thread and now the process by inspecting
      the Sig* entries in /proc/TID/status.)

To double check that the kernel wouldn't send the SIGTTOU signal if some other
thread had it unblocked, I hacked GDB to make sure all GDB threads start
with signals unblocked:

--- c/gdbsupport/thread-pool.cc
+++ w/gdbsupport/thread-pool.cc
@@ -100,7 +100,7 @@ thread_pool::set_thread_count (size_t num_threads)
     {
       /* Ensure that signals used by gdb are blocked in the new
         threads.  */
-      block_signals blocker;
+      // block_signals blocker;

(and then confirmed by inspecting the Sig* entries in /proc/TID/status.)

If SIGTTOU ends up raised by some other thread while the main thread has it blocked
in scoped_ignore_sigttou, and if it is sent to the whole process, there's a small time window
where the sigtimedwait in the main thread can eat the signal.  That still seems better than
the current status where the end result can be that we can end up with the signal enabled
instead of ignored and then gdb backgrounds itself.  This is of course highly theoretical since I
expect that gdb threads run with signals blocked from the get go.  But you never know what
Python code does.
Pedro Alves June 17, 2021, 3:36 p.m. | #4
On 2021-06-17 3:38 p.m., Pedro Alves wrote:
> On 2021-06-15 6:04 p.m., John Baldwin wrote:


> If SIGTTOU ends up raised by some other thread while the main thread has it blocked

> in scoped_ignore_sigttou, and if it is sent to the whole process, there's a small time window

> where the sigtimedwait in the main thread can eat the signal.  That still seems better than

> the current status where the end result can be that we can end up with the signal enabled

> instead of ignored and then gdb backgrounds itself.  This is of course highly theoretical since I

> expect that gdb threads run with signals blocked from the get go.  But you never know what

> Python code does.

> 


Hmm, I just thought of an easy fix for that.  Just don't consume the pending
signal in SIGTTOU's case.  I wonder how portable is this?  WDYT?

I'll see if I can try this on Solaris.

From 7f9288f1c3c4f184ebc08dd56406de0c13f5246a Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>

Date: Thu, 17 Jun 2021 16:23:03 +0100
Subject: [PATCH] No sigtimedwait for SIGTTOU

Change-Id: I92f754dbc45c45819dce2ce68b8c067d8d5c61b1
---
 gdbsupport/scoped_ignore_signal.h  | 18 +++++++++++++-----
 gdbsupport/scoped_ignore_sigttou.h |  2 +-
 2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/gdbsupport/scoped_ignore_signal.h b/gdbsupport/scoped_ignore_signal.h
index 55a921cb332..a14c96779bf 100644
--- a/gdbsupport/scoped_ignore_signal.h
+++ b/gdbsupport/scoped_ignore_signal.h
@@ -25,9 +25,16 @@
 /* RAII class used to ignore a signal in a scope.  If sigprocmask is
    supported, then the signal is only ignored by the calling thread.
    Otherwise, the signal disposition is set to SIG_IGN, which affects
-   the whole process.  */
-
-template <int Sig>
+   the whole process.  If ConsumePending is true, the destructor
+   consumes a pending Sig.  SIGPIPE for example is queued on the
+   thread even if blocked at the time the pipe is written to.  SIGTTOU
+   OTOH is not raised at all if the thread writing to the terminal has
+   it blocked.  Because SIGTTOU is sent to the whole process instead
+   of to a specific thread, consuming a pending SIGTTOU in the
+   destructor could consume a signal raised due to actions done by
+   some other thread.  */
+
+template <int Sig, bool ConsumePending>
 class scoped_ignore_signal
 {
 public:
@@ -58,7 +65,8 @@ class scoped_ignore_signal
 
 	/* If we got a pending Sig signal, consume it before
 	   unblocking.  */
-	sigtimedwait (&set, nullptr, &zero_timeout);
+	if (ConsumePending)
+	  sigtimedwait (&set, nullptr, &zero_timeout);
 
 	sigprocmask (SIG_UNBLOCK, &set, nullptr);
       }
@@ -89,7 +97,7 @@ struct scoped_ignore_signal_nop
 };
 
 #ifdef SIGPIPE
-using scoped_ignore_sigpipe = scoped_ignore_signal<SIGPIPE>;
+using scoped_ignore_sigpipe = scoped_ignore_signal<SIGPIPE, true>;
 #else
 using scoped_ignore_sigpipe = scoped_ignore_signal_nop;
 #endif
diff --git a/gdbsupport/scoped_ignore_sigttou.h b/gdbsupport/scoped_ignore_sigttou.h
index 1fc8f80d7fd..5695c5db905 100644
--- a/gdbsupport/scoped_ignore_sigttou.h
+++ b/gdbsupport/scoped_ignore_sigttou.h
@@ -75,7 +75,7 @@ class scoped_ignore_sigttou
   DISABLE_COPY_AND_ASSIGN (scoped_ignore_sigttou);
 
 private:
-  lazy_init<scoped_ignore_signal<SIGTTOU>> m_ignore_signal;
+  lazy_init<scoped_ignore_signal<SIGTTOU, false>> m_ignore_signal;
 };
 
 #else

base-commit: 2af6d46fd331b8e632bb9245614bad0c974392a4
-- 
2.26.2
John Baldwin June 17, 2021, 4:45 p.m. | #5
On 6/17/21 8:36 AM, Pedro Alves wrote:
> On 2021-06-17 3:38 p.m., Pedro Alves wrote:

>> On 2021-06-15 6:04 p.m., John Baldwin wrote:

> 

>> If SIGTTOU ends up raised by some other thread while the main thread has it blocked

>> in scoped_ignore_sigttou, and if it is sent to the whole process, there's a small time window

>> where the sigtimedwait in the main thread can eat the signal.  That still seems better than

>> the current status where the end result can be that we can end up with the signal enabled

>> instead of ignored and then gdb backgrounds itself.  This is of course highly theoretical since I

>> expect that gdb threads run with signals blocked from the get go.  But you never know what

>> Python code does.

>>

> 

> Hmm, I just thought of an easy fix for that.  Just don't consume the pending

> signal in SIGTTOU's case.  I wonder how portable is this?  WDYT?

> 

> I'll see if I can try this on Solaris.


I think this is ok.  I suspect that some of the rules around SIGTTOU are in POSIX as I
think job control is in POSIX, so (hopefully) the behavior there is fairly uniform.
I strongly suspect the other BSD's all follow FreeBSD at least, so if Solaris is the
same I'd feel pretty good about this behavior being consistent.

-- 
John Baldwin