aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2017-01-17 18:21:02 +0100
committerEelco Dolstra <edolstra@gmail.com>2017-01-17 18:21:02 +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 0c6e3fb76..44579a236 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 954044c23..42873d9e8 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 961c14e3a..d79cb5c13 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 679c3a1b6..052173ff9 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);
+
+
}