scoped_ignore_signal: Use sigprocmask+sigtimedwait instead of signal
authorPedro Alves <pedro@palves.net>
Thu, 17 Jun 2021 15:16:55 +0000 (16:16 +0100)
committerPedro Alves <pedro@palves.net>
Thu, 17 Jun 2021 15:22:12 +0000 (16:22 +0100)
The problem with using signal(...) to temporarily ignore a signal, is
that that changes the the signal disposition for the whole process.
If multiple threads do it at the same time, you have a race.

Fix this by using sigprocmask + sigtimedwait to implement the ignoring
instead, if available, which I think probably means everywhere except
Windows nowadays.  This way, we only change the signal mask for the
current thread, so there's no race.

Change-Id: Idfe3fb08327ef8cae926f3de9ee81c56a83b1738

gdbsupport/ChangeLog:
yyyy-mm-dd  Pedro Alves  <pedro@palves.net>

* scoped_ignore_signal.h
(scoped_ignore_signal::scoped_ignore_signal)
[HAVE_SIGPROCMASK]: Use sigprocmask to block the signal instead of
changing the signal disposition for the whole process.
(scoped_ignore_signal::~scoped_ignore_signal) [HAVE_SIGPROCMASK]:
Use sigtimedwait and sigprocmask to flush and unblock the signal.

gdbsupport/ChangeLog
gdbsupport/scoped_ignore_signal.h

index ac424f3ae7fd4a4f5f827e223f004ada3302a312..d054899333344cb002b20824f2ca59f9d131d6d8 100644 (file)
@@ -1,3 +1,12 @@
+2021-06-17  Pedro Alves  <pedro@palves.net>
+
+       * scoped_ignore_signal.h
+       (scoped_ignore_signal::scoped_ignore_signal)
+       [HAVE_SIGPROCMASK]: Use sigprocmask to block the signal instead of
+       changing the signal disposition for the whole process.
+       (scoped_ignore_signal::~scoped_ignore_signal) [HAVE_SIGPROCMASK]:
+       Use sigtimedwait and sigprocmask to flush and unblock the signal.
+
 2021-06-17  Pedro Alves  <pedro@palves.net>
 
        * scoped_ignore_sigttou.h: New file, moved from gdb/ and renamed.
index cccd390529b1686fff3a28d76ad9990a8e488ebd..55a921cb332891a4a92d265533bddb7f34ef59d2 100644 (file)
 
 #include <signal.h>
 
-/* RAII class used to ignore a signal in a scope.  */
+/* 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>
 class scoped_ignore_signal
@@ -30,18 +33,48 @@ class scoped_ignore_signal
 public:
   scoped_ignore_signal ()
   {
+#ifdef HAVE_SIGPROCMASK
+    sigset_t set, old_state;
+
+    sigemptyset (&set);
+    sigaddset (&set, Sig);
+    sigprocmask (SIG_BLOCK, &set, &old_state);
+    m_was_blocked = sigismember (&old_state, Sig);
+#else
     m_osig = signal (Sig, SIG_IGN);
+#endif
   }
 
   ~scoped_ignore_signal ()
   {
+#ifdef HAVE_SIGPROCMASK
+    if (!m_was_blocked)
+      {
+       sigset_t set;
+       const timespec zero_timeout = {};
+
+       sigemptyset (&set);
+       sigaddset (&set, Sig);
+
+       /* If we got a pending Sig signal, consume it before
+          unblocking.  */
+       sigtimedwait (&set, nullptr, &zero_timeout);
+
+       sigprocmask (SIG_UNBLOCK, &set, nullptr);
+      }
+#else
     signal (Sig, m_osig);
+#endif
   }
 
   DISABLE_COPY_AND_ASSIGN (scoped_ignore_signal);
 
 private:
-  sighandler_t m_osig = nullptr;
+#ifdef HAVE_SIGPROCMASK
+  bool m_was_blocked;
+#else
+  sighandler_t m_osig;
+#endif
 };
 
 struct scoped_ignore_signal_nop