ClickHouse/src/Common/InterruptListener.h
2020-04-03 18:14:31 +03:00

116 lines
2.6 KiB
C++

#pragma once
#include <signal.h>
#include <errno.h>
#include <Common/Exception.h>
namespace DB
{
namespace ErrorCodes
{
extern const int CANNOT_MANIPULATE_SIGSET;
extern const int CANNOT_WAIT_FOR_SIGNAL;
extern const int CANNOT_BLOCK_SIGNAL;
extern const int CANNOT_UNBLOCK_SIGNAL;
}
#ifdef __APPLE__
// We only need to support timeout = {0, 0} at this moment
static int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec * /*timeout*/)
{
sigset_t pending;
int signo;
sigpending(&pending);
for (signo = 1; signo < NSIG; ++signo)
{
if (sigismember(set, signo) && sigismember(&pending, signo))
{
sigwait(set, &signo);
if (info)
{
memset(info, 0, sizeof *info);
info->si_signo = signo;
}
return signo;
}
}
errno = EAGAIN;
return -1;
}
#endif
/** As long as there exists an object of this class - it blocks the INT signal, at the same time it lets you know if it came.
* This is necessary so that you can interrupt the execution of the request with Ctrl+C.
* Use only one instance of this class at a time.
* If `check` method returns true (the signal has arrived), the next call will wait for the next signal.
*/
class InterruptListener
{
private:
bool active;
sigset_t sig_set;
public:
InterruptListener() : active(false)
{
if (sigemptyset(&sig_set)
|| sigaddset(&sig_set, SIGINT))
throwFromErrno("Cannot manipulate with signal set.", ErrorCodes::CANNOT_MANIPULATE_SIGSET);
block();
}
~InterruptListener()
{
unblock();
}
bool check()
{
if (!active)
return false;
timespec timeout = { 0, 0 };
if (-1 == sigtimedwait(&sig_set, nullptr, &timeout))
{
if (errno == EAGAIN)
return false;
else
throwFromErrno("Cannot poll signal (sigtimedwait).", ErrorCodes::CANNOT_WAIT_FOR_SIGNAL);
}
return true;
}
void block()
{
if (!active)
{
if (pthread_sigmask(SIG_BLOCK, &sig_set, nullptr))
throwFromErrno("Cannot block signal.", ErrorCodes::CANNOT_BLOCK_SIGNAL);
active = true;
}
}
/// You can stop blocking the signal earlier than in the destructor.
void unblock()
{
if (active)
{
if (pthread_sigmask(SIG_UNBLOCK, &sig_set, nullptr))
throwFromErrno("Cannot unblock signal.", ErrorCodes::CANNOT_UNBLOCK_SIGNAL);
active = false;
}
}
};
}