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.
This commit is contained in:
parent
c0d55f9183
commit
cc3b93c991
4 changed files with 101 additions and 28 deletions
|
@ -24,12 +24,6 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
static void sigintHandler(int signo)
|
|
||||||
{
|
|
||||||
_isInterrupted = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool gcWarning = true;
|
static bool gcWarning = true;
|
||||||
|
|
||||||
void printGCWarning()
|
void printGCWarning()
|
||||||
|
@ -120,19 +114,11 @@ void initNix()
|
||||||
settings.processEnvironment();
|
settings.processEnvironment();
|
||||||
settings.loadConfFile();
|
settings.loadConfFile();
|
||||||
|
|
||||||
/* Catch SIGINT. */
|
startSignalHandlerThread();
|
||||||
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");
|
|
||||||
|
|
||||||
/* Ignore SIGPIPE. */
|
/* Ignore SIGPIPE. */
|
||||||
|
struct sigaction act;
|
||||||
|
sigemptyset(&act.sa_mask);
|
||||||
act.sa_handler = SIG_IGN;
|
act.sa_handler = SIG_IGN;
|
||||||
act.sa_flags = 0;
|
act.sa_flags = 0;
|
||||||
if (sigaction(SIGPIPE, &act, 0))
|
if (sigaction(SIGPIPE, &act, 0))
|
||||||
|
|
|
@ -324,20 +324,30 @@ struct CurlDownloader : public Downloader
|
||||||
|
|
||||||
~CurlDownloader()
|
~CurlDownloader()
|
||||||
{
|
{
|
||||||
/* Signal the worker thread to exit. */
|
stopWorkerThread();
|
||||||
{
|
|
||||||
auto state(state_.lock());
|
|
||||||
state->quit = true;
|
|
||||||
}
|
|
||||||
writeFull(wakeupPipe.writeSide.get(), " ");
|
|
||||||
|
|
||||||
workerThread.join();
|
workerThread.join();
|
||||||
|
|
||||||
if (curlm) curl_multi_cleanup(curlm);
|
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()
|
void workerThreadMain()
|
||||||
{
|
{
|
||||||
|
/* Cause this thread to be notified on SIGINT. */
|
||||||
|
auto callback = createInterruptCallback([&]() {
|
||||||
|
stopWorkerThread();
|
||||||
|
});
|
||||||
|
|
||||||
std::map<CURL *, std::shared_ptr<DownloadItem>> items;
|
std::map<CURL *, std::shared_ptr<DownloadItem>> items;
|
||||||
|
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
|
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "affinity.hh"
|
#include "affinity.hh"
|
||||||
|
#include "sync.hh"
|
||||||
|
|
||||||
#include <iostream>
|
#include <cctype>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <sstream>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cctype>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -933,7 +935,7 @@ void restoreSIGPIPE()
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
volatile sig_atomic_t _isInterrupted = 0;
|
bool _isInterrupted = false;
|
||||||
|
|
||||||
thread_local bool interruptThrown = 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,7 +263,7 @@ void restoreSIGPIPE();
|
||||||
|
|
||||||
/* User interruption. */
|
/* User interruption. */
|
||||||
|
|
||||||
extern volatile sig_atomic_t _isInterrupted;
|
extern bool _isInterrupted;
|
||||||
|
|
||||||
extern thread_local bool interruptThrown;
|
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);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue