From cc3b93c991e04aff49a44dbced53f070a06f426e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Jan 2017 18:21:02 +0100 Subject: 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/libutil/util.cc | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/libutil/util.hh | 17 ++++++++++++- 2 files changed, 82 insertions(+), 5 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 961c14e3a4..d79cb5c133 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 679c3a1b6d..052173ff99 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); + + } -- cgit 1.4.1