the worker already wants to make a promise explicitly control-C-able, and some other things in the future will want this as well. we do not yet have the option to use the signal interfaces kj offers, but if we can manage to remove all uses of the old-style notifiers we might get there. until then we can at least wrap the old interfaces to be a bit nicer to use, and eventually easier to replace with kj-provided code. Change-Id: I079fdfe7720485820a9862c3615335d4e1df13e7
143 lines
4.1 KiB
C++
143 lines
4.1 KiB
C++
#pragma once
|
|
/** @file Signal handling in Lix
|
|
*
|
|
* Processes are expected to be either:
|
|
* - Advanced processes which call into Lix's logic, like the daemon processes.
|
|
* - Basic processes that are just going to execve.
|
|
*
|
|
* Processes should be set up accordingly following a fork:
|
|
* In the first case, such processes should have a signal handler thread that
|
|
* catches SIGINT and dispatches it to the rest of the system so they should
|
|
* call startSignalHandlerThread(). In the second case, processes should call
|
|
* restoreProcessContext(), possibly with `false` (depends on whether mounts
|
|
* should be restored), which will unmask SIGINT and other signals that were
|
|
* previously masked in an advanced process such as the one that started
|
|
* them, so the process can be interrupted.
|
|
*
|
|
* It is generally a mistake to fork a process without at least calling
|
|
* restoreSignals() or restoreProcessContext().
|
|
*/
|
|
|
|
|
|
#include "lix/libutil/error.hh"
|
|
#include "lix/libutil/result.hh"
|
|
|
|
#include <kj/async.h>
|
|
#include <memory>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
|
|
namespace nix {
|
|
|
|
/* User interruption. */
|
|
|
|
class Interrupted;
|
|
|
|
extern std::atomic<bool> _isInterrupted;
|
|
|
|
extern thread_local std::function<bool()> interruptCheck;
|
|
|
|
Interrupted makeInterrupted();
|
|
void _interrupted();
|
|
|
|
void inline checkInterrupt()
|
|
{
|
|
if (_isInterrupted || (interruptCheck && interruptCheck()))
|
|
_interrupted();
|
|
}
|
|
|
|
MakeError(Interrupted, BaseError);
|
|
|
|
void restoreSignals();
|
|
|
|
|
|
/**
|
|
* Whether to save the signal mask when starting the signal handler thread.
|
|
*
|
|
* The signal mask shouldn't be saved if the current signal mask is the one for
|
|
* processes with a signal handler thread.
|
|
*/
|
|
enum class DoSignalSave
|
|
{
|
|
Save,
|
|
DontSaveBecauseAdvancedProcess,
|
|
};
|
|
|
|
/**
|
|
* Start a thread that handles various signals. Also block those signals
|
|
* on the current thread (and thus any threads created by it).
|
|
*
|
|
* Optionally saves the signal mask before changing the mask to block those
|
|
* signals. See saveSignalMask().
|
|
*
|
|
* This should also be executed after certain forks from Lix processes that
|
|
* expect to be "advanced" (see file doc comment), since the signal thread will
|
|
* die on fork. Of course this whole situation is kind of unsound since we
|
|
* definitely violate async-signal-safety requirements, but, well.
|
|
*/
|
|
void startSignalHandlerThread(DoSignalSave doSave = DoSignalSave::Save);
|
|
|
|
/**
|
|
* 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);
|
|
|
|
template<typename T>
|
|
kj::Promise<Result<T>> makeInterruptible(kj::Promise<Result<T>> p)
|
|
{
|
|
auto onInterrupt = kj::newPromiseAndCrossThreadFulfiller<Result<T>>();
|
|
auto interruptCallback = createInterruptCallback([fulfiller{onInterrupt.fulfiller.get()}] {
|
|
fulfiller->fulfill(result::failure(std::make_exception_ptr(makeInterrupted())));
|
|
});
|
|
return p.attach(std::move(onInterrupt.fulfiller), std::move(interruptCallback))
|
|
.exclusiveJoin(std::move(onInterrupt.promise));
|
|
}
|
|
|
|
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); }))
|
|
{ }
|
|
};
|
|
|
|
};
|