Make ^C kill shm event loops
This is done unconditinally. There are very few times when we don't
want ^C to kill the loop nicely, so just do it until someone complains.
Also, graceful exit is much better than getting terminated and
potentially core dumping.
Change-Id: Ib156ca779c1c5a8d1597c1531a4b12ec14ae0314
diff --git a/aos/events/shm_event_loop.cc b/aos/events/shm_event_loop.cc
index bd6d37c..f5feea9 100644
--- a/aos/events/shm_event_loop.cc
+++ b/aos/events/shm_event_loop.cc
@@ -17,6 +17,7 @@
#include "aos/ipc_lib/lockless_queue.h"
#include "aos/ipc_lib/signalfd.h"
#include "aos/realtime.h"
+#include "aos/stl_mutex/stl_mutex.h"
#include "aos/util/phased_loop.h"
#include "glog/logging.h"
@@ -526,9 +527,100 @@
}
}
+// RAII class to mask signals.
+class ScopedSignalMask {
+ public:
+ ScopedSignalMask(std::initializer_list<int> signals) {
+ sigset_t sigset;
+ PCHECK(sigemptyset(&sigset) == 0);
+ for (int signal : signals) {
+ PCHECK(sigaddset(&sigset, signal) == 0);
+ }
+
+ PCHECK(sigprocmask(SIG_BLOCK, &sigset, &old_) == 0);
+ }
+
+ ~ScopedSignalMask() { PCHECK(sigprocmask(SIG_SETMASK, &old_, nullptr) == 0); }
+
+ private:
+ sigset_t old_;
+};
+
+// Class to manage the static state associated with killing multiple event
+// loops.
+class SignalHandler {
+ public:
+ // Gets the singleton.
+ static SignalHandler *global() {
+ static SignalHandler loop;
+ return &loop;
+ }
+
+ // Handles the signal with the singleton.
+ static void HandleSignal(int) { global()->DoHandleSignal(); }
+
+ // Registers an event loop to receive Exit() calls.
+ void Register(ShmEventLoop *event_loop) {
+ // Block signals while we have the mutex so we never race with the signal
+ // handler.
+ ScopedSignalMask mask({SIGINT, SIGHUP, SIGTERM});
+ std::unique_lock<stl_mutex> locker(mutex_);
+ if (event_loops_.size() == 0) {
+ // The first caller registers the signal handler.
+ struct sigaction new_action;
+ sigemptyset(&new_action.sa_mask);
+ // This makes it so that 2 control c's to a stuck process will kill it by
+ // restoring the original signal handler.
+ new_action.sa_flags = SA_RESETHAND;
+ new_action.sa_handler = &HandleSignal;
+
+ PCHECK(sigaction(SIGINT, &new_action, &old_action_int_) == 0);
+ PCHECK(sigaction(SIGHUP, &new_action, &old_action_hup_) == 0);
+ PCHECK(sigaction(SIGTERM, &new_action, &old_action_term_) == 0);
+ }
+
+ event_loops_.push_back(event_loop);
+ }
+
+ // Unregisters an event loop to receive Exit() calls.
+ void Unregister(ShmEventLoop *event_loop) {
+ // Block signals while we have the mutex so we never race with the signal
+ // handler.
+ ScopedSignalMask mask({SIGINT, SIGHUP, SIGTERM});
+ std::unique_lock<stl_mutex> locker(mutex_);
+
+ event_loops_.erase(std::find(event_loops_.begin(), event_loops_.end(), event_loop));
+
+ if (event_loops_.size() == 0u) {
+ // The last caller restores the original signal handlers.
+ PCHECK(sigaction(SIGINT, &old_action_int_, nullptr) == 0);
+ PCHECK(sigaction(SIGHUP, &old_action_hup_, nullptr) == 0);
+ PCHECK(sigaction(SIGTERM, &old_action_term_, nullptr) == 0);
+ }
+ }
+
+ private:
+ void DoHandleSignal() {
+ // We block signals while grabbing the lock, so there should never be a
+ // race. Confirm that this is true using trylock.
+ CHECK(mutex_.try_lock()) << ": sigprocmask failed to block signals while "
+ "modifing the event loop list.";
+ for (ShmEventLoop *event_loop : event_loops_) {
+ event_loop->Exit();
+ }
+ mutex_.unlock();
+ }
+
+ // Mutex to protect all state.
+ stl_mutex mutex_;
+ std::vector<ShmEventLoop *> event_loops_;
+ struct sigaction old_action_int_;
+ struct sigaction old_action_hup_;
+ struct sigaction old_action_term_;
+};
+
void ShmEventLoop::Run() {
- // TODO(austin): Automatically register ^C with a sigaction so 2 in a row send
- // an actual control C.
+ SignalHandler::global()->Register(this);
std::unique_ptr<ipc_lib::SignalFd> signalfd;
@@ -590,6 +682,8 @@
epoll_.DeleteFd(signalfd->fd());
signalfd.reset();
}
+
+ SignalHandler::global()->Unregister(this);
}
void ShmEventLoop::Exit() { epoll_.Quit(); }