From cc3b93c991e04aff49a44dbced53f070a06f426e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Jan 2017 18:21:02 +0100 Subject: [PATCH] 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. --- src/libmain/shared.cc | 20 ++---------- src/libstore/download.cc | 22 +++++++++---- src/libutil/util.cc | 70 +++++++++++++++++++++++++++++++++++++--- src/libutil/util.hh | 17 +++++++++- 4 files changed, 101 insertions(+), 28 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() { - /* Signal the worker thread to exit. */ - { - auto state(state_.lock()); - state->quit = true; - } - writeFull(wakeupPipe.writeSide.get(), " "); + 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(), " ", false); + } + void workerThreadMain() { + /* Cause this thread to be notified on SIGINT. */ + auto callback = createInterruptCallback([&]() { + stopWorkerThread(); + }); + std::map> 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 +#include #include #include #include -#include #include -#include +#include +#include +#include #include #include @@ -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 & failure, st } +static Sync>> _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>::iterator it; + ~InterruptCallbackImpl() override + { + _interruptCallbacks.lock()->erase(it); + } +}; + +std::unique_ptr createInterruptCallback(std::function callback) +{ + auto interruptCallbacks(_interruptCallbacks.lock()); + interruptCallbacks->push_back(callback); + + auto res = std::make_unique(); + 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 createInterruptCallback( + std::function callback); + + }