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/BUILD b/aos/events/BUILD
index 50a5263..5aef05a 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -198,6 +198,7 @@
         "//aos:realtime",
         "//aos/ipc_lib:lockless_queue",
         "//aos/ipc_lib:signalfd",
+        "//aos/stl_mutex",
         "//aos/util:phased_loop",
     ],
 )
diff --git a/aos/events/ping.cc b/aos/events/ping.cc
index 66c8f12..5a79635 100644
--- a/aos/events/ping.cc
+++ b/aos/events/ping.cc
@@ -7,19 +7,17 @@
 #include "glog/logging.h"
 
 int main(int argc, char **argv) {
-  FLAGS_logtostderr = true;
-  google::InitGoogleLogging(argv[0]);
-  ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+  aos::InitGoogle(&argc, &argv);
 
   aos::FlatbufferDetachedBuffer<aos::Configuration> config =
       aos::configuration::ReadConfig("aos/events/pingpong_config.json");
 
-  ::aos::ShmEventLoop event_loop(&config.message());
+  aos::ShmEventLoop event_loop(&config.message());
 
   aos::Ping ping(&event_loop);
 
   event_loop.Run();
 
-  ::aos::Cleanup();
+  aos::Cleanup();
   return 0;
 }
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(); }
diff --git a/aos/ipc_lib/shared_mem.cc b/aos/ipc_lib/shared_mem.cc
index 93ced7d..3bb7267 100644
--- a/aos/ipc_lib/shared_mem.cc
+++ b/aos/ipc_lib/shared_mem.cc
@@ -123,16 +123,17 @@
 }
 
 void aos_core_free_shared_mem() {
-  void *shm_address = global_core->shared_mem;
-  PCHECK(munmap((void *)SHM_START, SIZEOFSHMSEG) != -1)
-      << ": munmap(" << shm_address << ", 0x" << std::hex
-      << (size_t)SIZEOFSHMSEG << ") failed";
-  if (global_core->owner) {
-    PCHECK(shm_unlink(global_core->shm_name) == 0)
-        << ": shared_mem: shm_unlink(" << global_core->shm_name << ") failed";
+  if (global_core != nullptr) {
+    void *shm_address = global_core->shared_mem;
+    PCHECK(munmap((void *)SHM_START, SIZEOFSHMSEG) != -1)
+        << ": munmap(" << shm_address << ", 0x" << std::hex
+        << (size_t)SIZEOFSHMSEG << ") failed";
+    if (global_core->owner) {
+      PCHECK(shm_unlink(global_core->shm_name) == 0)
+          << ": shared_mem: shm_unlink(" << global_core->shm_name << ") failed";
+    }
+    global_core = nullptr;
   }
 }
 
-int aos_core_is_init(void) {
-  return global_core != NULL;
-}
+int aos_core_is_init(void) { return global_core != NULL; }