Add interrupt handling classes.
This patch introduces a number of helper classes to
manage user interruption for both Win32 and Posix systems.
Fuchsia-Topic: advanced-ipc
Original-Change-Id: I97c658cc234e5f979aaa506246b4032893ba4fee
Original-Change-Id: Ie7ce9503a895b8e355927b93b80c68b139474790
Change-Id: Ie2e81f9db6ee8b37734ca85e66756d901488be8e
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/github.com/ninja-build/ninja/+/975452
Reviewed-by: David Fang <fangism@google.com>
Reviewed-by: David Turner <digit@google.com>
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Tyler Mandry <tmandry@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5213814..012197f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -150,6 +150,7 @@
target_sources(libninja PRIVATE
src/subprocess-win32.cc
src/includes_normalize-win32.cc
+ src/interrupt_handling-win32.cc
src/ipc_handle-win32.cc
src/msvc_helper-win32.cc
src/msvc_helper_main-win32.cc
@@ -162,6 +163,7 @@
set_source_files_properties(src/getopt.c PROPERTIES LANGUAGE CXX)
else()
target_sources(libninja PRIVATE
+ src/interrupt_handling-posix.cc
src/ipc_handle-posix.cc
src/subprocess-posix.cc
)
@@ -282,6 +284,7 @@
src/dyndep_parser_test.cc
src/edit_distance_test.cc
src/graph_test.cc
+ src/interrupt_handling_test.cc
src/ipc_handle_test.cc
src/ipc_utils_test.cc
src/json_test.cc
diff --git a/configure.py b/configure.py
index 238fcd2..e1b17e2 100755
--- a/configure.py
+++ b/configure.py
@@ -544,6 +544,7 @@
if platform.is_windows():
for name in ['subprocess-win32',
'includes_normalize-win32',
+ 'interrupt_handling-win32',
'ipc_handle-win32',
'msvc_helper-win32',
'msvc_helper_main-win32']:
@@ -552,7 +553,8 @@
objs += cxx('minidump-win32', variables=cxxvariables)
objs += cc('getopt')
else:
- for name in ['ipc_handle-posix',
+ for name in ['interrupt_handling-posix',
+ 'ipc_handle-posix',
'subprocess-posix']:
objs += cxx(name, variables=cxxvariables)
if platform.is_aix():
diff --git a/src/interrupt_handling-posix.cc b/src/interrupt_handling-posix.cc
new file mode 100644
index 0000000..fa47131
--- /dev/null
+++ b/src/interrupt_handling-posix.cc
@@ -0,0 +1,177 @@
+// Copyright 2023 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "interrupt_handling.h"
+#include "util.h"
+
+// Set to 1 to print debug messages to stderr during development
+#define DEBUG 0
+
+namespace {
+
+// Retrieve a signal mask for SIGINT/SIGHUP/SIGTERM
+sigset_t GetInterruptSignalMask() {
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGTERM);
+ return mask;
+}
+
+// Set the signal action for a given |signum|, returning the previous one
+// in |*old_action| is |old_action != nullptr|.
+void SetSignalAction(int signum, const struct sigaction* action,
+ struct sigaction* old_action) {
+ if (sigaction(signum, action, old_action) < 0)
+ ErrnoFatal("sigaction");
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////////
+///
+/// InterruptBlocker
+///
+
+InterruptBlocker::InterruptBlocker() {
+ sigset_t block_interrupts = GetInterruptSignalMask();
+ if (sigprocmask(SIG_BLOCK, &block_interrupts, &prev_signal_mask_) < 0)
+ ErrnoFatal("sigprocmask");
+}
+
+InterruptBlocker::~InterruptBlocker() {
+ if (sigprocmask(SIG_SETMASK, &prev_signal_mask_, nullptr) < 0)
+ ErrnoFatal("sigprocmask");
+}
+
+//////////////////////////////////////////////////////////////////////////
+///
+/// InterruptHandlerBase
+///
+
+InterruptHandlerBase::InterruptHandlerBase(const struct sigaction& action) {
+ // Block the signals before changing the handlers.
+ sigset_t mask = GetInterruptSignalMask();
+ sigprocmask(SIG_BLOCK, &mask, &old_mask_);
+
+ SetSignalAction(SIGINT, &action, &old_int_action_);
+ SetSignalAction(SIGHUP, &action, &old_hup_action_);
+ SetSignalAction(SIGTERM, &action, &old_term_action_);
+
+ // Unblock the signals now.
+ sigprocmask(SIG_UNBLOCK, &mask, nullptr);
+}
+
+InterruptHandlerBase::~InterruptHandlerBase() {
+ // Block the signal before changing the action handlers.
+ sigset_t mask = GetInterruptSignalMask();
+ sigprocmask(SIG_BLOCK, &mask, nullptr);
+
+ SetSignalAction(SIGINT, &old_int_action_, nullptr);
+ SetSignalAction(SIGHUP, &old_hup_action_, nullptr);
+ SetSignalAction(SIGTERM, &old_term_action_, nullptr);
+
+ // Restore the original signal mask.
+ sigprocmask(SIG_SETMASK, &old_mask_, nullptr);
+}
+
+//////////////////////////////////////////////////////////////////////////
+///
+/// InterruptCatcher
+///
+
+InterruptCatcher::InterruptCatcher() : InterruptHandlerBase(MakeAction()) {
+ s_interrupted_ = 0;
+ HandlePendingInterrupt();
+}
+
+InterruptCatcher::~InterruptCatcher() = default;
+
+#if DEBUG
+#define WRITE(msg) ::write(2, msg, sizeof(msg) - 1)
+#else
+#define WRITE(msg) (void)(msg)
+#endif
+
+// static
+struct sigaction InterruptCatcher::MakeAction() {
+ struct sigaction result = {};
+ result.sa_handler = [](int signum) {
+ s_interrupted_ = signum;
+ if (signum == SIGINT)
+ WRITE("\nSIGINT SIGNALED\n");
+ else if (signum == SIGHUP)
+ WRITE("\nSIGHUP SIGNALED\n");
+ else if (signum == SIGTERM)
+ WRITE("\nSIGTERM SIGNALED\n");
+ };
+ return result;
+}
+
+// static
+void InterruptCatcher::HandlePendingInterrupt() {
+ sigset_t pending;
+ sigemptyset(&pending);
+ if (sigpending(&pending) == -1) {
+ perror("ninja: sigpending");
+ return;
+ }
+ if (sigismember(&pending, SIGINT)) {
+ WRITE("\nSIGINT PENDING\n");
+ s_interrupted_ = SIGINT;
+ } else if (sigismember(&pending, SIGTERM)) {
+ WRITE("\nSIGTERM PENDING\n");
+ s_interrupted_ = SIGTERM;
+ } else if (sigismember(&pending, SIGHUP)) {
+ WRITE("\nSIGHUP PENDING\n");
+ s_interrupted_ = SIGHUP;
+ }
+}
+
+// static
+volatile sig_atomic_t InterruptCatcher::s_interrupted_ = 0;
+
+//////////////////////////////////////////////////////////////////////////
+///
+/// InterruptForwarder
+///
+
+#include <unistd.h>
+
+InterruptForwarder::InterruptForwarder(pid_t process_group)
+ : InterruptHandlerBase(MakeAction()), old_process_group_(s_process_group_) {
+ s_process_group_ = process_group;
+}
+
+InterruptForwarder::~InterruptForwarder() {
+ s_process_group_ = old_process_group_;
+}
+
+// static
+struct sigaction InterruptForwarder::MakeAction() {
+ struct sigaction result = {};
+ result.sa_handler = [](int signum) {
+ // Send the interrupt to the server's process group
+ kill(-s_process_group_, signum);
+ WRITE("\nINTERRUPT FORWARDED\n");
+ };
+ return result;
+}
+
+// static
+pid_t InterruptForwarder::s_process_group_ = 0;
diff --git a/src/interrupt_handling-win32.cc b/src/interrupt_handling-win32.cc
new file mode 100644
index 0000000..a0b866c
--- /dev/null
+++ b/src/interrupt_handling-win32.cc
@@ -0,0 +1,88 @@
+// Copyright 2023 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "interrupt_handling.h"
+#include "util.h"
+
+//////////////////////////////////////////////////////////////////////////
+///
+/// InterruptCompletionPortHandler
+///
+
+InterruptCompletionPortHandler::InterruptCompletionPortHandler(
+ HANDLE ioport, ULONG_PTR completion_key)
+ : old_ioport_(s_ioport_), old_completion_key_(s_completion_key_) {
+ s_ioport_ = ioport;
+ s_completion_key_ = completion_key;
+
+ if (!SetConsoleCtrlHandler(HandlerRoutine, TRUE))
+ Win32Fatal("SetConsoleCtrlHandler");
+}
+
+InterruptCompletionPortHandler::~InterruptCompletionPortHandler() {
+ s_ioport_ = old_ioport_;
+ s_completion_key_ = old_completion_key_;
+
+ if (!SetConsoleCtrlHandler(HandlerRoutine, FALSE))
+ Win32Fatal("SetConsoleCtrlHandler");
+}
+
+// static
+BOOL InterruptCompletionPortHandler::HandlerRoutine(DWORD dwCtrlType) {
+ if (dwCtrlType != CTRL_C_EVENT && dwCtrlType != CTRL_BREAK_EVENT)
+ return FALSE;
+
+ if (!PostQueuedCompletionStatus(s_ioport_, 0, s_completion_key_, nullptr))
+ Win32Fatal("PostQueuedCompletionStatus");
+
+ return TRUE;
+}
+
+// static
+HANDLE InterruptCompletionPortHandler::s_ioport_ = INVALID_HANDLE_VALUE;
+
+// static
+ULONG_PTR InterruptCompletionPortHandler::s_completion_key_ = 0;
+
+//////////////////////////////////////////////////////////////////////////
+///
+/// InterruptForwarder
+///
+
+InterruptForwarder::InterruptForwarder(DWORD pgid)
+ : old_process_group_id_(s_process_group_id_) {
+ s_process_group_id_ = pgid;
+ if (!SetConsoleCtrlHandler(InterruptForwarder::HandlerRoutine, TRUE))
+ Win32Fatal("SetConsoleCtrlHandler");
+}
+
+InterruptForwarder::~InterruptForwarder() {
+ s_process_group_id_ = old_process_group_id_;
+ if (!SetConsoleCtrlHandler(InterruptForwarder::HandlerRoutine, FALSE))
+ Win32Fatal("SetConsoleCtrlHandler");
+}
+
+// static
+BOOL InterruptForwarder::HandlerRoutine(DWORD dwCtrlType) {
+ if (dwCtrlType != CTRL_C_EVENT && dwCtrlType != CTRL_BREAK_EVENT)
+ return FALSE;
+
+ if (!GenerateConsoleCtrlEvent(dwCtrlType, s_process_group_id_))
+ Win32Fatal("GenerateConsoleCtrlEvent");
+
+ return TRUE;
+}
+
+// static
+DWORD InterruptForwarder::s_process_group_id_ = 0;
diff --git a/src/interrupt_handling.h b/src/interrupt_handling.h
new file mode 100644
index 0000000..6dc562c
--- /dev/null
+++ b/src/interrupt_handling.h
@@ -0,0 +1,125 @@
+// Copyright 2023 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef NINJA_INTERRUPT_HANDLING_H_
+#define NINJA_INTERRUPT_HANDLING_H_
+
+/// Convenience classes used to control how user interruption
+/// is handled by Ninja at various times. This means Ctrl+C and Ctrl+Break
+/// events on Win32, and SIGINT/SIGHUP/SIGTERM ones on Posix.
+
+#ifdef _WIN32
+
+#include <windows.h>
+
+/// On Ctrl-C or Ctrl-Break, post en empty i/o completion packet
+/// to a given i/o completion queue. Useful to catch signals when
+/// waiting for overlapped i/o on Win32.
+struct InterruptCompletionPortHandler {
+ InterruptCompletionPortHandler(HANDLE ioport, ULONG_PTR completion_key = 0);
+ ~InterruptCompletionPortHandler();
+
+ private:
+ static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType);
+ HANDLE old_ioport_;
+ ULONG_PTR old_completion_key_;
+ static HANDLE s_ioport_;
+ static ULONG_PTR s_completion_key_;
+};
+
+/// Forward all Ctrl-C and Ctrl-Break signals to a different
+/// process group.
+struct InterruptForwarder {
+ InterruptForwarder(DWORD process_group_id);
+ ~InterruptForwarder();
+
+ private:
+ static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType);
+ DWORD old_process_group_id_;
+ static DWORD s_process_group_id_;
+};
+
+#else // !_WIN32
+
+#include <signal.h>
+
+/// A class used to block all SIGINT/SIGHUP/SIGTERM signals
+/// in the current process. Restores the previous signal
+/// mask in the destructor.
+struct InterruptBlocker {
+ InterruptBlocker();
+ ~InterruptBlocker();
+
+ const sigset_t& old_mask() const { return prev_signal_mask_; }
+
+ private:
+ sigset_t prev_signal_mask_;
+};
+
+/// Base class for all interrupt handlers.
+struct InterruptHandlerBase {
+ /// Constructor sets a new signal handler for SIGINT/SIGHUP/SIGTERM
+ /// and unblocks these signals!
+ InterruptHandlerBase(const struct sigaction& action);
+
+ /// Destructor restores previous interrupt handlers and signal mask.
+ ~InterruptHandlerBase();
+
+ /// Return the signal mask before construction. This will be restored
+ /// on destruction as well.
+ sigset_t old_mask() const { return old_mask_; }
+
+ private:
+ struct sigaction old_int_action_;
+ struct sigaction old_hup_action_;
+ struct sigaction old_term_action_;
+ sigset_t old_mask_;
+};
+
+/// Catch all SIGINT/SIGHUP/SIGTERM signals and stores the
+/// corresponding signal number into a global interrupted()
+/// variable. This can also detect pending signals.
+struct InterruptCatcher : public InterruptHandlerBase {
+ InterruptCatcher();
+ ~InterruptCatcher();
+
+ /// Return interrupt signal number, or 0 if there were none.
+ int interrupted() const { return s_interrupted_; }
+
+ /// Clear the interrupted signal number.
+ void Clear() { s_interrupted_ = 0; }
+
+ /// Handle any pending interruption signal. This updates
+ /// the interrupted() value but does not cancel the signals.
+ static void HandlePendingInterrupt();
+
+ private:
+ static struct sigaction MakeAction();
+ static volatile sig_atomic_t s_interrupted_;
+};
+
+/// Forward all SIGINT/SIGHUP/SIGTERM signal to a different
+/// process group
+struct InterruptForwarder : public InterruptHandlerBase {
+ explicit InterruptForwarder(pid_t process_group);
+ ~InterruptForwarder();
+
+ private:
+ static struct sigaction MakeAction();
+ pid_t old_process_group_ = -1;
+ static pid_t s_process_group_;
+};
+
+#endif // !_WIN32
+
+#endif // NINJA_INTERRUPT_HANDLING_H_
diff --git a/src/interrupt_handling_test.cc b/src/interrupt_handling_test.cc
new file mode 100644
index 0000000..2df18db
--- /dev/null
+++ b/src/interrupt_handling_test.cc
@@ -0,0 +1,204 @@
+// Copyright 2023 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "interrupt_handling.h"
+
+#include "test.h"
+#include "util.h"
+
+#ifdef _WIN32
+
+// TODO(digit): Write real test for _WIN32
+char dummy_;
+
+#else // !_WIN32
+
+#include <sys/signal.h>
+#include <unistd.h>
+
+namespace {
+
+// Retrieve a signal mask for SIGINT/SIGHUP/SIGTERM
+sigset_t GetInterruptSignalMask() {
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGTERM);
+ return mask;
+}
+
+// Set the signal action for a given |signum|, returning the previous one
+// in |*old_action| is |old_action != nullptr|.
+void SetSignalAction(int signum, const struct sigaction* action,
+ struct sigaction* old_action) {
+ if (sigaction(signum, action, old_action) < 0)
+ ErrnoFatal("sigaction");
+}
+
+// Base class for all tests, used to set the signal mask and actions for
+// interrupts to the same base state.
+class InterruptHandlingTest : public ::testing::Test {
+ public:
+ InterruptHandlingTest() {
+ // Block signals before changing the action handler + save previous mask.
+ sigset_t mask = GetInterruptSignalMask();
+ sigprocmask(SIG_BLOCK, &mask, &prev_mask_);
+
+ // Change signal handlers.
+ struct sigaction sigint_action = {};
+ sigint_action.sa_handler = [](int) { s_got_sigint = 1; };
+ SetSignalAction(SIGINT, &sigint_action, &prev_sigint_action_);
+
+ struct sigaction sighup_action = {};
+ sighup_action.sa_handler = [](int) { s_got_sighup = 1; };
+ SetSignalAction(SIGHUP, &sighup_action, &prev_sighup_action_);
+
+ struct sigaction sigterm_action = {};
+ sigterm_action.sa_handler = [](int) { s_got_sigterm = 1; };
+ SetSignalAction(SIGTERM, &sigterm_action, &prev_sigterm_action_);
+
+ // Unblock signals.
+ sigprocmask(SIG_UNBLOCK, &mask, nullptr);
+ }
+
+ ~InterruptHandlingTest() {
+ // Block signals before changing handlers.
+ sigset_t mask = GetInterruptSignalMask();
+ sigprocmask(SIG_BLOCK, &mask, nullptr);
+
+ // Restore previous handlers.
+ SetSignalAction(SIGINT, &prev_sigint_action_, nullptr);
+ SetSignalAction(SIGHUP, &prev_sighup_action_, nullptr);
+ SetSignalAction(SIGTERM, &prev_sigterm_action_, nullptr);
+
+ // Clear flags for next test.
+ Clear();
+
+ // Restore previous signal mask.
+ sigprocmask(SIG_SETMASK, &prev_mask_, nullptr);
+ }
+
+ void Clear() {
+ s_got_sigint = 0;
+ s_got_sighup = 0;
+ s_got_sigterm = 0;
+ }
+
+ void SendSelfSignal(int signum) { ::kill(getpid(), signum); }
+
+ sigset_t prev_mask_;
+ struct sigaction prev_sigint_action_;
+ struct sigaction prev_sighup_action_;
+ struct sigaction prev_sigterm_action_;
+
+ static volatile sig_atomic_t s_got_sigint;
+ static volatile sig_atomic_t s_got_sighup;
+ static volatile sig_atomic_t s_got_sigterm;
+};
+
+volatile sig_atomic_t InterruptHandlingTest::s_got_sigint = 0;
+volatile sig_atomic_t InterruptHandlingTest::s_got_sighup = 0;
+volatile sig_atomic_t InterruptHandlingTest::s_got_sigterm = 0;
+
+} // namespace
+
+TEST_F(InterruptHandlingTest, SendSelfSignals) {
+ // Verify that the interrupt signals are not blocked
+ sigset_t empty_mask;
+ sigemptyset(&empty_mask);
+ sigset_t cur_mask;
+ sigprocmask(SIG_BLOCK, &empty_mask, &cur_mask);
+ ASSERT_FALSE(sigismember(&cur_mask, SIGINT));
+ ASSERT_FALSE(sigismember(&cur_mask, SIGHUP));
+ ASSERT_FALSE(sigismember(&cur_mask, SIGTERM));
+
+ SendSelfSignal(SIGINT);
+ ASSERT_TRUE(s_got_sigint);
+
+ SendSelfSignal(SIGHUP);
+ ASSERT_TRUE(s_got_sighup);
+
+ SendSelfSignal(SIGTERM);
+ ASSERT_TRUE(s_got_sigterm);
+
+ Clear();
+ ASSERT_FALSE(s_got_sigint);
+ ASSERT_FALSE(s_got_sighup);
+ ASSERT_FALSE(s_got_sigterm);
+}
+
+TEST_F(InterruptHandlingTest, InterruptBlocker) {
+ {
+ InterruptBlocker blocker;
+
+ SendSelfSignal(SIGINT);
+ ASSERT_FALSE(s_got_sigint);
+
+ SendSelfSignal(SIGHUP);
+ ASSERT_FALSE(s_got_sighup);
+
+ SendSelfSignal(SIGTERM);
+ ASSERT_FALSE(s_got_sigterm);
+ }
+ ASSERT_TRUE(s_got_sigint);
+ ASSERT_TRUE(s_got_sighup);
+ ASSERT_TRUE(s_got_sigterm);
+}
+
+TEST_F(InterruptHandlingTest, InterruptCatcher) {
+ {
+ InterruptCatcher catcher;
+
+ SendSelfSignal(SIGINT);
+ ASSERT_FALSE(s_got_sigint);
+ ASSERT_EQ(SIGINT, catcher.interrupted());
+
+ SendSelfSignal(SIGHUP);
+ ASSERT_FALSE(s_got_sighup);
+ ASSERT_EQ(SIGHUP, catcher.interrupted());
+
+ SendSelfSignal(SIGTERM);
+ ASSERT_FALSE(s_got_sigterm);
+ ASSERT_EQ(SIGTERM, catcher.interrupted());
+ }
+
+ // Verify that a second instance does not keep the old interrupted()
+ // value from a stale global variable.
+ {
+ InterruptCatcher catcher;
+ ASSERT_EQ(0, catcher.interrupted());
+ }
+}
+
+TEST_F(InterruptHandlingTest, InterruptForwarder) {
+ ASSERT_FALSE(s_got_sigint);
+
+ // Create child process with fork.
+ pid_t child_pid = fork();
+ ASSERT_GE(child_pid, 0);
+
+ if (child_pid == 0) {
+ // In the child process, do nothing, just wait for an interrupt.
+ sleep(10);
+ exit(0);
+ }
+
+ InterruptForwarder forwarder(child_pid);
+
+ SendSelfSignal(SIGINT);
+ ASSERT_FALSE(s_got_sigint);
+}
+
+#endif // !_WIN32
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index fccf53c..7369f93 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -78,7 +78,7 @@
short flags = 0;
flags |= POSIX_SPAWN_SETSIGMASK;
- err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
+ err = posix_spawnattr_setsigmask(&attr, &set->old_signal_mask());
if (err != 0)
ErrnoFatal("posix_spawnattr_setsigmask", err);
// Signals which are set to be caught in the calling process image are set to
@@ -185,58 +185,17 @@
return buf_;
}
-int SubprocessSet::interrupted_;
-
-void SubprocessSet::SetInterruptedFlag(int signum) {
- interrupted_ = signum;
-}
-
-void SubprocessSet::HandlePendingInterruption() {
- sigset_t pending;
- sigemptyset(&pending);
- if (sigpending(&pending) == -1) {
- perror("ninja: sigpending");
- return;
- }
- if (sigismember(&pending, SIGINT))
- interrupted_ = SIGINT;
- else if (sigismember(&pending, SIGTERM))
- interrupted_ = SIGTERM;
- else if (sigismember(&pending, SIGHUP))
- interrupted_ = SIGHUP;
-}
-
SubprocessSet::SubprocessSet() {
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGINT);
- sigaddset(&set, SIGTERM);
- sigaddset(&set, SIGHUP);
- if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0)
- Fatal("sigprocmask: %s", strerror(errno));
-
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- act.sa_handler = SetInterruptedFlag;
- if (sigaction(SIGINT, &act, &old_int_act_) < 0)
- Fatal("sigaction: %s", strerror(errno));
- if (sigaction(SIGTERM, &act, &old_term_act_) < 0)
- Fatal("sigaction: %s", strerror(errno));
- if (sigaction(SIGHUP, &act, &old_hup_act_) < 0)
- Fatal("sigaction: %s", strerror(errno));
+ // Allow ppoll()/pselect() to be interrupted by SIGINT/SIGHUP/SIGTERM
+ // even if these are blocked in the previous process mask.
+ wait_mask_ = interrupt_blocker_.old_mask();
+ sigdelset(&wait_mask_, SIGINT);
+ sigdelset(&wait_mask_, SIGHUP);
+ sigdelset(&wait_mask_, SIGTERM);
}
SubprocessSet::~SubprocessSet() {
Clear();
-
- if (sigaction(SIGINT, &old_int_act_, 0) < 0)
- Fatal("sigaction: %s", strerror(errno));
- if (sigaction(SIGTERM, &old_term_act_, 0) < 0)
- Fatal("sigaction: %s", strerror(errno));
- if (sigaction(SIGHUP, &old_hup_act_, 0) < 0)
- Fatal("sigaction: %s", strerror(errno));
- if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0)
- Fatal("sigprocmask: %s", strerror(errno));
}
Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
@@ -264,20 +223,24 @@
++nfds;
}
- interrupted_ = 0;
- int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_);
+ interrupt_catcher_.Clear();
+
+ int ret = ppoll(&fds.front(), nfds, NULL, &wait_mask_);
if (ret == -1) {
if (errno != EINTR) {
perror("ninja: ppoll");
return false;
}
- return IsInterrupted();
+ return interrupt_catcher_.interrupted();
}
- HandlePendingInterruption();
- if (IsInterrupted())
+ interrupt_catcher_.HandlePendingInterrupt();
+ if (interrupt_catcher_.interrupted())
return true;
+ if (ret == 0)
+ return false;
+
nfds_t cur_nfd = 0;
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ) {
@@ -296,7 +259,7 @@
++i;
}
- return IsInterrupted();
+ return interrupt_catcher_.interrupted();
}
#else // !defined(USE_PPOLL)
@@ -315,20 +278,24 @@
}
}
- interrupted_ = 0;
- int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_);
+ interrupt_catcher_.Clear();
+
+ int ret = pselect(nfds, &set, 0, 0, 0, &wait_mask_);
if (ret == -1) {
if (errno != EINTR) {
perror("ninja: pselect");
return false;
}
- return IsInterrupted();
+ return interrupt_catcher_.interrupted();
}
- HandlePendingInterruption();
- if (IsInterrupted())
+ interrupt_catcher_.HandlePendingInterrupt();
+ if (interrupt_catcher_.interrupted())
return true;
+ if (ret == 0)
+ return false;
+
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ) {
int fd = (*i)->fd_;
@@ -343,7 +310,7 @@
++i;
}
- return IsInterrupted();
+ return interrupt_catcher_.interrupted();
}
#endif // !defined(USE_PPOLL)
@@ -356,14 +323,19 @@
}
void SubprocessSet::Clear() {
- for (vector<Subprocess*>::iterator i = running_.begin();
- i != running_.end(); ++i)
- // Since the foreground process is in our process group, it will receive
- // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us.
- if (!(*i)->use_console_)
- kill(-(*i)->pid_, interrupted_);
- for (vector<Subprocess*>::iterator i = running_.begin();
- i != running_.end(); ++i)
- delete *i;
+ int interrupted = interrupt_catcher_.interrupted();
+ if (interrupted) {
+ for (auto* subproc : running_) {
+ // Since the foreground process is in our process group, it will receive
+ // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as
+ // us.
+ if (!subproc->use_console_)
+ kill(-subproc->pid_, interrupted);
+ }
+ }
+
+ for (auto* subproc : running_)
+ delete subproc;
+
running_.clear();
}
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index ff3baac..2325773 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -75,7 +75,7 @@
}
bool Subprocess::Start(SubprocessSet* set, const string& command) {
- HANDLE child_pipe = SetupPipe(set->ioport_);
+ HANDLE child_pipe = SetupPipe(set->ioport_.get());
SECURITY_ATTRIBUTES security_attributes;
memset(&security_attributes, 0, sizeof(SECURITY_ATTRIBUTES));
@@ -211,31 +211,23 @@
return buf_;
}
-HANDLE SubprocessSet::ioport_;
-
-SubprocessSet::SubprocessSet() {
- ioport_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
- if (!ioport_)
+SubprocessSet::ScopedIoPort::ScopedIoPort() {
+ s_handle_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+ if (!s_handle_)
Win32Fatal("CreateIoCompletionPort");
- if (!SetConsoleCtrlHandler(NotifyInterrupted, TRUE))
- Win32Fatal("SetConsoleCtrlHandler");
}
+SubprocessSet::ScopedIoPort::~ScopedIoPort() {
+ ::CloseHandle(s_handle_);
+}
+
+// static
+HANDLE SubprocessSet::ScopedIoPort::s_handle_ = INVALID_HANDLE_VALUE;
+
+SubprocessSet::SubprocessSet() : ioport_(), interrupt_handler_(ioport_.get()) {}
+
SubprocessSet::~SubprocessSet() {
Clear();
-
- SetConsoleCtrlHandler(NotifyInterrupted, FALSE);
- CloseHandle(ioport_);
-}
-
-BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) {
- if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
- if (!PostQueuedCompletionStatus(ioport_, 0, 0, NULL))
- Win32Fatal("PostQueuedCompletionStatus");
- return TRUE;
- }
-
- return FALSE;
}
Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
@@ -256,14 +248,14 @@
Subprocess* subproc;
OVERLAPPED* overlapped;
- if (!GetQueuedCompletionStatus(ioport_, &bytes_read, (PULONG_PTR)&subproc,
- &overlapped, INFINITE)) {
+ if (!GetQueuedCompletionStatus(ioport_.get(), &bytes_read,
+ (PULONG_PTR)&subproc, &overlapped, INFINITE)) {
if (GetLastError() != ERROR_BROKEN_PIPE)
Win32Fatal("GetQueuedCompletionStatus");
}
- if (!subproc) // A NULL subproc indicates that we were interrupted and is
- // delivered by NotifyInterrupted above.
+ if (!subproc) // A NULL subproc indicates that we were interrupted and is
+ // delivered by an InterruptCompletionPortHandler.
return true;
subproc->OnPipeReady();
diff --git a/src/subprocess.h b/src/subprocess.h
index 9e3d2ee..dbf9a72 100644
--- a/src/subprocess.h
+++ b/src/subprocess.h
@@ -15,9 +15,10 @@
#ifndef NINJA_SUBPROCESS_H_
#define NINJA_SUBPROCESS_H_
+#include <memory>
+#include <queue>
#include <string>
#include <vector>
-#include <queue>
#ifdef _WIN32
#include <windows.h>
@@ -34,6 +35,7 @@
#endif
#include "exit_status.h"
+#include "interrupt_handling.h"
/// Subprocess wraps a single async subprocess. It is entirely
/// passive: it expects the caller to notify it when its fds are ready
@@ -92,21 +94,26 @@
std::queue<Subprocess*> finished_;
#ifdef _WIN32
- static BOOL WINAPI NotifyInterrupted(DWORD dwCtrlType);
- static HANDLE ioport_;
+ // Helper class to create and close a static I/O completion port handle
+ // in the right order.
+ struct ScopedIoPort {
+ ScopedIoPort();
+ ~ScopedIoPort();
+ HANDLE get() const { return s_handle_; }
+
+ private:
+ static HANDLE s_handle_;
+ };
+ ScopedIoPort ioport_;
+ InterruptCompletionPortHandler interrupt_handler_;
#else
- static void SetInterruptedFlag(int signum);
- static void HandlePendingInterruption();
- /// Store the signal number that causes the interruption.
- /// 0 if not interruption.
- static int interrupted_;
+ const sigset_t& old_signal_mask() const {
+ return interrupt_blocker_.old_mask();
+ }
- static bool IsInterrupted() { return interrupted_ != 0; }
-
- struct sigaction old_int_act_;
- struct sigaction old_term_act_;
- struct sigaction old_hup_act_;
- sigset_t old_mask_;
+ InterruptBlocker interrupt_blocker_;
+ InterruptCatcher interrupt_catcher_;
+ sigset_t wait_mask_;
#endif
};