about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2017-01-17T17·21+0100
committerEelco Dolstra <edolstra@gmail.com>2017-01-17T17·21+0100
commitcc3b93c991e04aff49a44dbced53f070a06f426e (patch)
treebc88ad9093faabd477ce935aedb5bd6a09459107
parentc0d55f918379f46b87e43457745895439a85555c (diff)
Handle SIGINT etc. via a sigwait() signal handler thread
This allows other threads to install callbacks that run in a regular,
non-signal context. In particular, we can use this to signal the
downloader thread to quit.

Closes #1183.
-rw-r--r--src/libmain/shared.cc20
-rw-r--r--src/libstore/download.cc20
-rw-r--r--src/libutil/util.cc70
-rw-r--r--src/libutil/util.hh17
4 files changed, 100 insertions, 27 deletions
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index 0c6e3fb76d64..44579a236774 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -24,12 +24,6 @@
 namespace nix {
 
 
-static void sigintHandler(int signo)
-{
-    _isInterrupted = 1;
-}
-
-
 static bool gcWarning = true;
 
 void printGCWarning()
@@ -120,19 +114,11 @@ void initNix()
     settings.processEnvironment();
     settings.loadConfFile();
 
-    /* Catch SIGINT. */
-    struct sigaction act;
-    act.sa_handler = sigintHandler;
-    sigemptyset(&act.sa_mask);
-    act.sa_flags = 0;
-    if (sigaction(SIGINT, &act, 0))
-        throw SysError("installing handler for SIGINT");
-    if (sigaction(SIGTERM, &act, 0))
-        throw SysError("installing handler for SIGTERM");
-    if (sigaction(SIGHUP, &act, 0))
-        throw SysError("installing handler for SIGHUP");
+    startSignalHandlerThread();
 
     /* Ignore SIGPIPE. */
+    struct sigaction act;
+    sigemptyset(&act.sa_mask);
     act.sa_handler = SIG_IGN;
     act.sa_flags = 0;
     if (sigaction(SIGPIPE, &act, 0))
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 954044c2344f..42873d9e8a10 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -324,20 +324,30 @@ struct CurlDownloader : public Downloader
 
     ~CurlDownloader()
     {
+        stopWorkerThread();
+
+        workerThread.join();
+
+        if (curlm) curl_multi_cleanup(curlm);
+    }
+
+    void stopWorkerThread()
+    {
         /* Signal the worker thread to exit. */
         {
             auto state(state_.lock());
             state->quit = true;
         }
-        writeFull(wakeupPipe.writeSide.get(), " ");
-
-        workerThread.join();
-
-        if (curlm) curl_multi_cleanup(curlm);
+        writeFull(wakeupPipe.writeSide.get(), " ", false);
     }
 
     void workerThreadMain()
     {
+        /* Cause this thread to be notified on SIGINT. */
+        auto callback = createInterruptCallback([&]() {
+            stopWorkerThread();
+        });
+
         std::map<CURL *, std::shared_ptr<DownloadItem>> items;
 
         bool quit = false;
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 961c14e3a47f..d79cb5c1333e 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -2,14 +2,16 @@
 
 #include "util.hh"
 #include "affinity.hh"
+#include "sync.hh"
 
-#include <iostream>
+#include <cctype>
 #include <cerrno>
 #include <cstdio>
 #include <cstdlib>
-#include <sstream>
 #include <cstring>
-#include <cctype>
+#include <iostream>
+#include <sstream>
+#include <thread>
 
 #include <sys/wait.h>
 #include <unistd.h>
@@ -933,7 +935,7 @@ void restoreSIGPIPE()
 //////////////////////////////////////////////////////////////////////
 
 
-volatile sig_atomic_t _isInterrupted = 0;
+bool _isInterrupted = false;
 
 thread_local bool interruptThrown = false;
 
@@ -1200,4 +1202,64 @@ void callFailure(const std::function<void(std::exception_ptr exc)> & failure, st
 }
 
 
+static Sync<std::list<std::function<void()>>> _interruptCallbacks;
+
+static void signalHandlerThread(sigset_t set)
+{
+    while (true) {
+        int signal = 0;
+        sigwait(&set, &signal);
+
+        if (signal == SIGINT || signal == SIGTERM || signal == SIGHUP) {
+            _isInterrupted = 1;
+
+            {
+                auto interruptCallbacks(_interruptCallbacks.lock());
+                for (auto & callback : *interruptCallbacks) {
+                    try {
+                        callback();
+                    } catch (...) {
+                        ignoreException();
+                    }
+                }
+            }
+        }
+    }
+}
+
+void startSignalHandlerThread()
+{
+    sigset_t set;
+    sigemptyset(&set);
+    sigaddset(&set, SIGINT);
+    sigaddset(&set, SIGTERM);
+    sigaddset(&set, SIGHUP);
+    if (pthread_sigmask(SIG_BLOCK, &set, nullptr))
+        throw SysError("blocking signals");
+
+    std::thread(signalHandlerThread, set).detach();
+}
+
+/* RAII helper to automatically deregister a callback. */
+struct InterruptCallbackImpl : InterruptCallback
+{
+    std::list<std::function<void()>>::iterator it;
+    ~InterruptCallbackImpl() override
+    {
+        _interruptCallbacks.lock()->erase(it);
+    }
+};
+
+std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
+{
+    auto interruptCallbacks(_interruptCallbacks.lock());
+    interruptCallbacks->push_back(callback);
+
+    auto res = std::make_unique<InterruptCallbackImpl>();
+    res->it = interruptCallbacks->end();
+    res->it--;
+
+    return res;
+}
+
 }
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 679c3a1b6df4..052173ff9976 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -263,7 +263,7 @@ void restoreSIGPIPE();
 
 /* User interruption. */
 
-extern volatile sig_atomic_t _isInterrupted;
+extern bool _isInterrupted;
 
 extern thread_local bool interruptThrown;
 
@@ -416,4 +416,19 @@ void callSuccess(
 }
 
 
+/* Start a thread that handles various signals. Also block those signals
+   on the current thread (and thus any threads created by it). */
+void startSignalHandlerThread();
+
+struct InterruptCallback
+{
+    virtual ~InterruptCallback() { };
+};
+
+/* Register a function that gets called on SIGINT (in a non-signal
+   context). */
+std::unique_ptr<InterruptCallback> createInterruptCallback(
+    std::function<void()> callback);
+
+
 }