Merge "util.hh: split out signals stuff" into main

This commit is contained in:
jade 2024-03-11 11:14:24 -06:00 committed by Gerrit Code Review
commit 50c401b4c1
32 changed files with 310 additions and 251 deletions

View file

@ -40,6 +40,7 @@ extern "C" {
#include "finally.hh" #include "finally.hh"
#include "markdown.hh" #include "markdown.hh"
#include "local-fs-store.hh" #include "local-fs-store.hh"
#include "signals.hh"
#include "print.hh" #include "print.hh"
#if HAVE_BOEHMGC #if HAVE_BOEHMGC

View file

@ -1,6 +1,7 @@
#include "print-ambiguous.hh" #include "print-ambiguous.hh"
#include "print.hh" #include "print.hh"
#include "eval.hh" #include "eval.hh"
#include "signals.hh"
namespace nix { namespace nix {

View file

@ -5,6 +5,7 @@
#include "ansicolor.hh" #include "ansicolor.hh"
#include "store-api.hh" #include "store-api.hh"
#include "english.hh" #include "english.hh"
#include "signals.hh"
#include "eval.hh" #include "eval.hh"
namespace nix { namespace nix {

View file

@ -1,6 +1,7 @@
#include "value-to-json.hh" #include "value-to-json.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include "store-api.hh" #include "store-api.hh"
#include <cstdlib> #include <cstdlib>

View file

@ -2,6 +2,7 @@
#include "xml-writer.hh" #include "xml-writer.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include <cstdlib> #include <cstdlib>

View file

@ -3,6 +3,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include "loggers.hh" #include "loggers.hh"
#include "progress-bar.hh" #include "progress-bar.hh"

View file

@ -10,6 +10,7 @@
#include "nar-info-disk-cache.hh" #include "nar-info-disk-cache.hh"
#include "nar-accessor.hh" #include "nar-accessor.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
#include "signals.hh"
#include "callback.hh" #include "callback.hh"
#include <chrono> #include <chrono>

View file

@ -1,6 +1,7 @@
#include "worker.hh" #include "worker.hh"
#include "substitution-goal.hh" #include "substitution-goal.hh"
#include "nar-info.hh" #include "nar-info.hh"
#include "signals.hh"
#include "finally.hh" #include "finally.hh"
namespace nix { namespace nix {

View file

@ -3,6 +3,7 @@
#include "substitution-goal.hh" #include "substitution-goal.hh"
#include "drv-output-substitution-goal.hh" #include "drv-output-substitution-goal.hh"
#include "local-derivation-goal.hh" #include "local-derivation-goal.hh"
#include "signals.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include <poll.h> #include <poll.h>

View file

@ -3,6 +3,7 @@
#include "globals.hh" #include "globals.hh"
#include "store-api.hh" #include "store-api.hh"
#include "s3.hh" #include "s3.hh"
#include "signals.hh"
#include "compression.hh" #include "compression.hh"
#include "finally.hh" #include "finally.hh"
#include "callback.hh" #include "callback.hh"

View file

@ -1,6 +1,7 @@
#include "derivations.hh" #include "derivations.hh"
#include "globals.hh" #include "globals.hh"
#include "local-store.hh" #include "local-store.hh"
#include "signals.hh"
#include "finally.hh" #include "finally.hh"
#include <functional> #include <functional>

View file

@ -8,6 +8,7 @@
#include "references.hh" #include "references.hh"
#include "callback.hh" #include "callback.hh"
#include "topo-sort.hh" #include "topo-sort.hh"
#include "signals.hh"
#include "finally.hh" #include "finally.hh"
#include "compression.hh" #include "compression.hh"

View file

@ -1,6 +1,7 @@
#include "util.hh" #include "util.hh"
#include "local-store.hh" #include "local-store.hh"
#include "globals.hh" #include "globals.hh"
#include "signals.hh"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>

View file

@ -1,5 +1,6 @@
#include "pathlocks.hh" #include "pathlocks.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include "sync.hh" #include "sync.hh"
#include <cerrno> #include <cerrno>

View file

@ -1,5 +1,6 @@
#include "serialise.hh" #include "serialise.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "remote-fs-accessor.hh" #include "remote-fs-accessor.hh"

View file

@ -1,6 +1,7 @@
#include "sqlite.hh" #include "sqlite.hh"
#include "globals.hh" #include "globals.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include "url.hh" #include "url.hh"
#include <sqlite3.h> #include <sqlite3.h>

View file

@ -11,6 +11,7 @@
#include "archive.hh" #include "archive.hh"
#include "callback.hh" #include "callback.hh"
#include "remote-store.hh" #include "remote-store.hh"
#include "signals.hh"
// FIXME this should not be here, see TODO below on // FIXME this should not be here, see TODO below on
// `addMultipleToStore`. // `addMultipleToStore`.
#include "worker-protocol.hh" #include "worker-protocol.hh"

View file

@ -14,6 +14,7 @@
#include "archive.hh" #include "archive.hh"
#include "util.hh" #include "util.hh"
#include "config.hh" #include "config.hh"
#include "signals.hh"
namespace nix { namespace nix {

View file

@ -2,6 +2,7 @@
#include "tarfile.hh" #include "tarfile.hh"
#include "util.hh" #include "util.hh"
#include "finally.hh" #include "finally.hh"
#include "signals.hh"
#include "logging.hh" #include "logging.hh"
#include <archive.h> #include <archive.h>

View file

@ -4,6 +4,7 @@
#include "finally.hh" #include "finally.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include "types.hh" #include "types.hh"
namespace fs = std::filesystem; namespace fs = std::filesystem;

View file

@ -10,6 +10,8 @@
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include "signals.hh"
namespace nix { namespace nix {

View file

@ -1,5 +1,6 @@
#include "serialise.hh" #include "serialise.hh"
#include "util.hh" #include "util.hh"
#include "signals.hh"
#include <cstring> #include <cstring>
#include <cerrno> #include <cerrno>

184
src/libutil/signals.cc Normal file
View file

@ -0,0 +1,184 @@
#include "signals.hh"
#include "util.hh"
#include "error.hh"
#include "sync.hh"
#include <thread>
namespace nix {
std::atomic<bool> _isInterrupted = false;
static thread_local bool interruptThrown = false;
thread_local std::function<bool()> interruptCheck;
void setInterruptThrown()
{
interruptThrown = true;
}
void _interrupted()
{
/* Block user interrupts while an exception is being handled.
Throwing an exception while another exception is being handled
kills the program! */
if (!interruptThrown && !std::uncaught_exceptions()) {
interruptThrown = true;
throw Interrupted("interrupted by the user");
}
}
//////////////////////////////////////////////////////////////////////
/* We keep track of interrupt callbacks using integer tokens, so we can iterate
safely without having to lock the data structure while executing arbitrary
functions.
*/
struct InterruptCallbacks {
typedef int64_t Token;
/* We use unique tokens so that we can't accidentally delete the wrong
handler because of an erroneous double delete. */
Token nextToken = 0;
/* Used as a list, see InterruptCallbacks comment. */
std::map<Token, std::function<void()>> callbacks;
};
static Sync<InterruptCallbacks> _interruptCallbacks;
static void signalHandlerThread(sigset_t set)
{
while (true) {
int signal = 0;
sigwait(&set, &signal);
if (signal == SIGINT || signal == SIGTERM || signal == SIGHUP)
triggerInterrupt();
else if (signal == SIGWINCH) {
updateWindowSize();
}
}
}
void triggerInterrupt()
{
_isInterrupted = true;
{
InterruptCallbacks::Token i = 0;
while (true) {
std::function<void()> callback;
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto lb = interruptCallbacks->callbacks.lower_bound(i);
if (lb == interruptCallbacks->callbacks.end())
break;
callback = lb->second;
i = lb->first + 1;
}
try {
callback();
} catch (...) {
ignoreException();
}
}
}
}
static sigset_t savedSignalMask;
static bool savedSignalMaskIsSet = false;
void setChildSignalMask(sigset_t * sigs)
{
assert(sigs); // C style function, but think of sigs as a reference
#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
sigemptyset(&savedSignalMask);
// There's no "assign" or "copy" function, so we rely on (math) idempotence
// of the or operator: a or a = a.
sigorset(&savedSignalMask, sigs, sigs);
#else
// Without sigorset, our best bet is to assume that sigset_t is a type that
// can be assigned directly, such as is the case for a sigset_t defined as
// an integer type.
savedSignalMask = *sigs;
#endif
savedSignalMaskIsSet = true;
}
void saveSignalMask() {
if (sigprocmask(SIG_BLOCK, nullptr, &savedSignalMask))
throw SysError("querying signal mask");
savedSignalMaskIsSet = true;
}
void startSignalHandlerThread()
{
updateWindowSize();
saveSignalMask();
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGPIPE);
sigaddset(&set, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &set, nullptr))
throw SysError("blocking signals");
std::thread(signalHandlerThread, set).detach();
}
void restoreSignals()
{
// If startSignalHandlerThread wasn't called, that means we're not running
// in a proper libmain process, but a process that presumably manages its
// own signal handlers. Such a process should call either
// - initNix(), to be a proper libmain process
// - startSignalHandlerThread(), to resemble libmain regarding signal
// handling only
// - saveSignalMask(), for processes that define their own signal handling
// thread
// TODO: Warn about this? Have a default signal mask? The latter depends on
// whether we should generally inherit signal masks from the caller.
// I don't know what the larger unix ecosystem expects from us here.
if (!savedSignalMaskIsSet)
return;
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
}
/* RAII helper to automatically deregister a callback. */
struct InterruptCallbackImpl : InterruptCallback
{
InterruptCallbacks::Token token;
~InterruptCallbackImpl() override
{
auto interruptCallbacks(_interruptCallbacks.lock());
interruptCallbacks->callbacks.erase(token);
}
};
std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto token = interruptCallbacks->nextToken++;
interruptCallbacks->callbacks.emplace(token, callback);
auto res = std::make_unique<InterruptCallbackImpl>();
res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
}
};

93
src/libutil/signals.hh Normal file
View file

@ -0,0 +1,93 @@
#pragma once
/// @file
#include "types.hh"
#include "error.hh"
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <signal.h>
#include <atomic>
#include <functional>
#include <sstream>
namespace nix {
/* User interruption. */
extern std::atomic<bool> _isInterrupted;
extern thread_local std::function<bool()> interruptCheck;
void setInterruptThrown();
void _interrupted();
void inline checkInterrupt()
{
if (_isInterrupted || (interruptCheck && interruptCheck()))
_interrupted();
}
MakeError(Interrupted, BaseError);
void restoreSignals();
/**
* Start a thread that handles various signals. Also block those signals
* on the current thread (and thus any threads created by it).
* Saves the signal mask before changing the mask to block those signals.
* See saveSignalMask().
*/
void startSignalHandlerThread();
/**
* Saves the signal mask, which is the signal mask that nix will restore
* before creating child processes.
* See setChildSignalMask() to set an arbitrary signal mask instead of the
* current mask.
*/
void saveSignalMask();
/**
* Sets the signal mask. Like saveSignalMask() but for a signal set that doesn't
* necessarily match the current thread's mask.
* See saveSignalMask() to set the saved mask to the current mask.
*/
void setChildSignalMask(sigset_t *sigs);
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);
void triggerInterrupt();
/**
* A RAII class that causes the current thread to receive SIGUSR1 when
* the signal handler thread receives SIGINT. That is, this allows
* SIGINT to be multiplexed to multiple threads.
*/
struct ReceiveInterrupts
{
pthread_t target;
std::unique_ptr<InterruptCallback> callback;
ReceiveInterrupts()
: target(pthread_self())
, callback(createInterruptCallback([&]() { pthread_kill(target, SIGUSR1); }))
{ }
};
};

View file

@ -1,4 +1,5 @@
#include "thread-pool.hh" #include "thread-pool.hh"
#include "signals.hh"
namespace nix { namespace nix {

View file

@ -3,6 +3,7 @@
#include "finally.hh" #include "finally.hh"
#include "serialise.hh" #include "serialise.hh"
#include "cgroup.hh" #include "cgroup.hh"
#include "signals.hh"
#include <array> #include <array>
#include <cctype> #include <cctype>
@ -1361,31 +1362,6 @@ void closeOnExec(int fd)
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
std::atomic<bool> _isInterrupted = false;
static thread_local bool interruptThrown = false;
thread_local std::function<bool()> interruptCheck;
void setInterruptThrown()
{
interruptThrown = true;
}
void _interrupted()
{
/* Block user interrupts while an exception is being handled.
Throwing an exception while another exception is being handled
kills the program! */
if (!interruptThrown && !std::uncaught_exceptions()) {
interruptThrown = true;
throw Interrupted("interrupted by the user");
}
}
//////////////////////////////////////////////////////////////////////
template<class C> C tokenizeString(std::string_view s, std::string_view separators) template<class C> C tokenizeString(std::string_view s, std::string_view separators)
{ {
C result; C result;
@ -1717,7 +1693,7 @@ std::pair<std::string_view, std::string_view> getLine(std::string_view s)
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}}; static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
static void updateWindowSize() void updateWindowSize()
{ {
struct winsize ws; struct winsize ws;
if (ioctl(2, TIOCGWINSZ, &ws) == 0) { if (ioctl(2, TIOCGWINSZ, &ws) == 0) {
@ -1734,133 +1710,6 @@ std::pair<unsigned short, unsigned short> getWindowSize()
} }
/* We keep track of interrupt callbacks using integer tokens, so we can iterate
safely without having to lock the data structure while executing arbitrary
functions.
*/
struct InterruptCallbacks {
typedef int64_t Token;
/* We use unique tokens so that we can't accidentally delete the wrong
handler because of an erroneous double delete. */
Token nextToken = 0;
/* Used as a list, see InterruptCallbacks comment. */
std::map<Token, std::function<void()>> callbacks;
};
static Sync<InterruptCallbacks> _interruptCallbacks;
static void signalHandlerThread(sigset_t set)
{
while (true) {
int signal = 0;
sigwait(&set, &signal);
if (signal == SIGINT || signal == SIGTERM || signal == SIGHUP)
triggerInterrupt();
else if (signal == SIGWINCH) {
updateWindowSize();
}
}
}
void triggerInterrupt()
{
_isInterrupted = true;
{
InterruptCallbacks::Token i = 0;
while (true) {
std::function<void()> callback;
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto lb = interruptCallbacks->callbacks.lower_bound(i);
if (lb == interruptCallbacks->callbacks.end())
break;
callback = lb->second;
i = lb->first + 1;
}
try {
callback();
} catch (...) {
ignoreException();
}
}
}
}
static sigset_t savedSignalMask;
static bool savedSignalMaskIsSet = false;
void setChildSignalMask(sigset_t * sigs)
{
assert(sigs); // C style function, but think of sigs as a reference
#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
sigemptyset(&savedSignalMask);
// There's no "assign" or "copy" function, so we rely on (math) idempotence
// of the or operator: a or a = a.
sigorset(&savedSignalMask, sigs, sigs);
#else
// Without sigorset, our best bet is to assume that sigset_t is a type that
// can be assigned directly, such as is the case for a sigset_t defined as
// an integer type.
savedSignalMask = *sigs;
#endif
savedSignalMaskIsSet = true;
}
void saveSignalMask() {
if (sigprocmask(SIG_BLOCK, nullptr, &savedSignalMask))
throw SysError("querying signal mask");
savedSignalMaskIsSet = true;
}
void startSignalHandlerThread()
{
updateWindowSize();
saveSignalMask();
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGPIPE);
sigaddset(&set, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &set, nullptr))
throw SysError("blocking signals");
std::thread(signalHandlerThread, set).detach();
}
static void restoreSignals()
{
// If startSignalHandlerThread wasn't called, that means we're not running
// in a proper libmain process, but a process that presumably manages its
// own signal handlers. Such a process should call either
// - initNix(), to be a proper libmain process
// - startSignalHandlerThread(), to resemble libmain regarding signal
// handling only
// - saveSignalMask(), for processes that define their own signal handling
// thread
// TODO: Warn about this? Have a default signal mask? The latter depends on
// whether we should generally inherit signal masks from the caller.
// I don't know what the larger unix ecosystem expects from us here.
if (!savedSignalMaskIsSet)
return;
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
}
rlim_t savedStackSize = 0; rlim_t savedStackSize = 0;
void setStackSize(rlim_t stackSize) void setStackSize(rlim_t stackSize)
@ -1951,30 +1800,6 @@ void restoreProcessContext(bool restoreMounts)
} }
} }
/* RAII helper to automatically deregister a callback. */
struct InterruptCallbackImpl : InterruptCallback
{
InterruptCallbacks::Token token;
~InterruptCallbackImpl() override
{
auto interruptCallbacks(_interruptCallbacks.lock());
interruptCallbacks->callbacks.erase(token);
}
};
std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto token = interruptCallbacks->nextToken++;
interruptCallbacks->callbacks.emplace(token, callback);
auto res = std::make_unique<InterruptCallbackImpl>();
res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
}
AutoCloseFD createUnixDomainSocket() AutoCloseFD createUnixDomainSocket()
{ {
AutoCloseFD fdSocket = socket(PF_UNIX, SOCK_STREAM AutoCloseFD fdSocket = socket(PF_UNIX, SOCK_STREAM

View file

@ -516,25 +516,6 @@ void closeMostFDs(const std::set<int> & exceptions);
void closeOnExec(int fd); void closeOnExec(int fd);
/* User interruption. */
extern std::atomic<bool> _isInterrupted;
extern thread_local std::function<bool()> interruptCheck;
void setInterruptThrown();
void _interrupted();
void inline checkInterrupt()
{
if (_isInterrupted || (interruptCheck && interruptCheck()))
_interrupted();
}
MakeError(Interrupted, BaseError);
MakeError(FormatError, Error); MakeError(FormatError, Error);
@ -829,61 +810,6 @@ template<typename T>
class Callback; class Callback;
/**
* Start a thread that handles various signals. Also block those signals
* on the current thread (and thus any threads created by it).
* Saves the signal mask before changing the mask to block those signals.
* See saveSignalMask().
*/
void startSignalHandlerThread();
/**
* Saves the signal mask, which is the signal mask that nix will restore
* before creating child processes.
* See setChildSignalMask() to set an arbitrary signal mask instead of the
* current mask.
*/
void saveSignalMask();
/**
* Sets the signal mask. Like saveSignalMask() but for a signal set that doesn't
* necessarily match the current thread's mask.
* See saveSignalMask() to set the saved mask to the current mask.
*/
void setChildSignalMask(sigset_t *sigs);
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);
void triggerInterrupt();
/**
* A RAII class that causes the current thread to receive SIGUSR1 when
* the signal handler thread receives SIGINT. That is, this allows
* SIGINT to be multiplexed to multiple threads.
*/
struct ReceiveInterrupts
{
pthread_t target;
std::unique_ptr<InterruptCallback> callback;
ReceiveInterrupts()
: target(pthread_self())
, callback(createInterruptCallback([&]() { pthread_kill(target, SIGUSR1); }))
{ }
};
/** /**
* A RAII helper that increments a counter on construction and * A RAII helper that increments a counter on construction and
* decrements it on destruction. * decrements it on destruction.
@ -903,6 +829,8 @@ struct MaintainCount
*/ */
std::pair<unsigned short, unsigned short> getWindowSize(); std::pair<unsigned short, unsigned short> getWindowSize();
void updateWindowSize();
/** /**
* Used in various places. * Used in various places.

View file

@ -5,6 +5,7 @@
#include "shared.hh" #include "shared.hh"
#include "globals.hh" #include "globals.hh"
#include "legacy.hh" #include "legacy.hh"
#include "signals.hh"
#include <iostream> #include <iostream>
#include <cerrno> #include <cerrno>

View file

@ -12,6 +12,7 @@
#include "derivations.hh" #include "derivations.hh"
#include "finally.hh" #include "finally.hh"
#include "legacy.hh" #include "legacy.hh"
#include "signals.hh"
#include "daemon.hh" #include "daemon.hh"
#include <algorithm> #include <algorithm>

View file

@ -1,5 +1,6 @@
#include "command.hh" #include "command.hh"
#include "shared.hh" #include "shared.hh"
#include "signals.hh"
#include "store-api.hh" #include "store-api.hh"
#include <atomic> #include <atomic>

View file

@ -2,6 +2,7 @@
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
#include "signals.hh"
#include <atomic> #include <atomic>

View file

@ -3,6 +3,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "sync.hh" #include "sync.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
#include "signals.hh"
#include "references.hh" #include "references.hh"
#include <atomic> #include <atomic>