diff --git a/lib/base/application.cpp b/lib/base/application.cpp
index e450b330006..3b946b3973c 100644
--- a/lib/base/application.cpp
+++ b/lib/base/application.cpp
@@ -300,6 +300,137 @@ void Application::SetArgV(char **argv)
m_ArgV = argv;
}
+#ifndef _WIN32
+/**
+ * Checks if one of the given signals is pending.
+ *
+ * This encapsulates most of the nastiness of POSIX and un-POSIX (i.e. MacOS and OpenBSD)
+ * signal handling so the rest of the code can remain sane.
+ *
+ * However on OpenBSD and MacOS the returned siginfo_t structure does only contain one
+ * valid field, which is .si_signo. Especially when checking against the pid using .si_pid
+ * it has to be taken into account that on these operating systems it will always be zero.
+ *
+ * If wait is given as false or a zero duration, the function will return immediately.
+ * If wait is given as true, the function will wait for the signal until it is received.
+ *
+ * @param signal The signals to check
+ * @param wait Whether the function should wait for a signal and if yes, how long
+ * @return A struct containing the signal id and pid of the sender
+ */
+std::optional Application::GetPendingSignal(std::initializer_list signals,
+ std::variant wait)
+{
+ sigset_t sigSet;
+ sigemptyset(&sigSet);
+ for(auto signal : signals){
+ sigaddset(&sigSet, signal);
+ }
+
+ int ret = 0;
+ siginfo_t info{};
+
+ if (wait == decltype(wait)(0us) || wait == decltype(wait)(false)) {
+#if defined(__APPLE__) || defined(__OpenBSD__)
+ sigset_t pendingSigs;
+ sigpending(&pendingSigs);
+ ret = -1;
+ for(auto signal : signals){
+ if (sigismember(&pendingSigs, signal)) {
+ ret = sigwait(&sigSet, &info.si_signo);
+ VERIFY(ret != EINVAL);
+ break;
+ };
+ }
+#else
+ timespec ts{};
+ ret = sigtimedwait(&sigSet, &info, &ts);
+ VERIFY(ret != -1 || errno != EINVAL);
+#endif
+ } else {
+ if (wait != decltype(wait)(true)) {
+ itimerval tv{};
+ tv.it_value.tv_sec = std::get(wait).count() / 1000000;
+ tv.it_value.tv_usec = std::get(wait).count() % 1000000;
+ ret = setitimer(ITIMER_REAL, &tv, nullptr);
+ VERIFY(ret != -1);
+ sigaddset(&sigSet, SIGALRM);
+ }
+
+#if defined(__APPLE__) || defined(__OpenBSD__)
+ ret = sigwait(&sigSet, &info.si_signo);
+ VERIFY(ret != EINVAL);
+#else
+ do{
+ ret = sigwaitinfo(&sigSet, &info);
+ VERIFY(ret != -1 || errno != EINVAL);
+ // sigwaitinfo can be interrupted by other signal handlers (see signal(7))
+ } while (ret == -1 && errno == EINTR);
+#endif
+ }
+
+ if (ret == -1 || info.si_signo == SIGALRM) {
+ return std::nullopt;
+ }
+
+ return info;
+}
+
+/**
+ * Checks if the given signal is pending.
+ *
+ * @param signal The signal id of the signal to check
+ * @param wait Whether the function should wait for a signal and if yes, how long
+ * @return A std::optional containing a siginfo_t if one of the signals was received
+ */
+std::optional Application::GetPendingSignal(int signal,
+ std::variant wait)
+{
+ return Application::GetPendingSignal({signal}, wait);
+}
+
+/**
+ * Seamless worker's signal handlers
+ */
+void Application::WorkerSignalHandler(std::chrono::microseconds timeout)
+{
+ auto sig = GetPendingSignal({SIGUSR1, SIGUSR2, SIGTERM, SIGINT}, timeout);
+ if (!sig){
+ return;
+ }
+
+ switch(sig->si_signo){
+ case SIGUSR1:
+ if (sig->si_pid == 0 || sig->si_pid == getppid()) {
+ Log(LogInformation, "Application")
+ << "Received USR1 signal, reopening application logs.";
+
+ RequestReopenLogs();
+ } else {
+ Log(LogWarning, "Application")
+ << "Received USR1 from unknown pid: " << sig->si_pid;
+ }
+ break;
+ case SIGUSR2:
+ Log(LogWarning, "Application")
+ << "Received superfluous USR2";
+ break;
+ case SIGINT:
+ case SIGTERM:
+ if (sig->si_pid == 0 || sig->si_pid == getppid()) {
+ // The umbrella process requested our termination
+ RequestShutdown();
+ } else {
+ Log(LogCritical, "Application")
+ << "Received " << strsignal(sig->si_signo) << " from unknown pid: " << sig->si_pid;
+ }
+ break;
+ default:
+ VERIFY(!"Error: Unhandled Signal");
+ }
+}
+#endif /* _WIN32 */
+
/**
* Processes events for registered sockets and timers and calls whatever
* handlers have been set up for these events.
@@ -325,9 +456,6 @@ void Application::RunEventLoop()
(void)kill(m_UmbrellaProcess, SIGHUP);
#endif /* _WIN32 */
} else {
- /* Watches for changes to the system time. Adjusts timers if necessary. */
- Utility::Sleep(2.5);
-
if (m_RequestReopenLogs) {
Log(LogNotice, "Application", "Reopening log files");
m_RequestReopenLogs = false;
@@ -337,6 +465,7 @@ void Application::RunEventLoop()
double now = Utility::GetTime();
double timeDiff = lastLoop - now;
+ // Watches for changes to the system time. Adjusts timers if necessary.
if (std::fabs(timeDiff) > 15) {
/* We made a significant jump in time. */
Log(LogInformation, "Application")
@@ -349,6 +478,11 @@ void Application::RunEventLoop()
lastLoop = now;
}
+#ifndef _WIN32
+ WorkerSignalHandler(2500ms);
+#else
+ Utility::Sleep(2.5);
+#endif /* _WIN32 */
}
Log(LogInformation, "Application", "Shutting down...");
@@ -706,20 +840,6 @@ void Application::AttachDebugger(const String& filename, bool interactive)
#endif /* _WIN32 */
}
-/**
- * Signal handler for SIGUSR1. This signal causes Icinga to re-open
- * its log files and is mainly for use by logrotate.
- *
- * @param - The signal number.
- */
-void Application::SigUsr1Handler(int)
-{
- Log(LogInformation, "Application")
- << "Received USR1 signal, reopening application logs.";
-
- RequestReopenLogs();
-}
-
/**
* Signal handler for SIGABRT. Helps with debugging ASSERT()s.
*
@@ -995,13 +1115,7 @@ void Application::InstallExceptionHandlers()
*/
int Application::Run()
{
-#ifndef _WIN32
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = &Application::SigUsr1Handler;
- sa.sa_flags = SA_RESTART;
- sigaction(SIGUSR1, &sa, nullptr);
-#else /* _WIN32 */
+#ifdef _WIN32
SetConsoleCtrlHandler(&Application::CtrlHandler, TRUE);
#endif /* _WIN32 */
diff --git a/lib/base/application.hpp b/lib/base/application.hpp
index f45c8bdd74a..12e739ad151 100644
--- a/lib/base/application.hpp
+++ b/lib/base/application.hpp
@@ -12,10 +12,15 @@
#include
#include
#include
+#include
+#include
+#include
namespace icinga
{
+using namespace std::literals::chrono_literals;
+
class ThreadPool;
/**
@@ -63,6 +68,11 @@ class Application : public ObjectImpl {
#ifndef _WIN32
static void SetUmbrellaProcess(pid_t pid);
+
+ static std::optional GetPendingSignal(std::initializer_list signals,
+ std::variant wait = 0us);
+ static std::optional GetPendingSignal(int signal,
+ std::variant wait = 0us);
#endif /* _WIN32 */
static bool IsShuttingDown();
@@ -157,8 +167,8 @@ class Application : public ObjectImpl {
static void DisplayBugMessage(std::ostream& os);
static void SigAbrtHandler(int signum);
- static void SigUsr1Handler(int signum);
static void ExceptionHandler();
+ static void WorkerSignalHandler(std::chrono::microseconds timeout);
static String GetCrashReportFilename();
diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp
index 3a9ce8c0a74..babc1121de4 100644
--- a/lib/cli/daemoncommand.cpp
+++ b/lib/cli/daemoncommand.cpp
@@ -274,12 +274,11 @@ int RunWorker(const std::vector& configs, bool closeConsoleLog = fa
Logger::DisableConsoleLog();
}
- while (!l_AllowedToWork.load()) {
- Utility::Sleep(0.2);
- }
+ // The umbrella process will send this signal when the previous worker has shut down.
+ Application::GetPendingSignal(SIGUSR2, true);
Log(LogNotice, "cli")
- << "The umbrella process let us continuing";
+ << "The umbrella process let us continue";
#endif /* _WIN32 */
NotifyStatus("Restoring the previous program state...");
@@ -328,104 +327,40 @@ int RunWorker(const std::vector& configs, bool closeConsoleLog = fa
}
#ifndef _WIN32
-// The signals to block temporarily in StartUnixWorker().
-static const sigset_t l_UnixWorkerSignals = ([]() -> sigset_t {
- sigset_t s;
-
- (void)sigemptyset(&s);
- (void)sigaddset(&s, SIGUSR1);
- (void)sigaddset(&s, SIGUSR2);
- (void)sigaddset(&s, SIGINT);
- (void)sigaddset(&s, SIGTERM);
- (void)sigaddset(&s, SIGHUP);
-
- return s;
-})();
-
-// The PID of the seamless worker currently being started by StartUnixWorker()
-static Atomic l_CurrentlyStartingUnixWorkerPid (-1);
-
-// The state of the seamless worker currently being started by StartUnixWorker()
-static Atomic l_CurrentlyStartingUnixWorkerReady (false);
-
-// The last temination signal we received
-static Atomic l_TermSignal (-1);
-
-// Whether someone requested to re-load config (and we didn't handle that request, yet)
-static Atomic l_RequestedReload (false);
-
-// Whether someone requested to re-open logs (and we didn't handle that request, yet)
-static Atomic l_RequestedReopenLogs (false);
/**
- * Umbrella process' signal handlers
+ * Block the signals from the given list.
+ *
+ * @param signals A std::initializer_list containing the signals to block
*/
-static void UmbrellaSignalHandler(int num, siginfo_t *info, void*)
-{
- switch (num) {
- case SIGUSR1:
- // Someone requested to re-open logs
- l_RequestedReopenLogs.store(true);
- break;
- case SIGUSR2:
- if (!l_CurrentlyStartingUnixWorkerReady.load()
- && (info->si_pid == 0 || info->si_pid == l_CurrentlyStartingUnixWorkerPid.load()) ) {
- // The seamless worker currently being started by StartUnixWorker() successfully loaded its config
- l_CurrentlyStartingUnixWorkerReady.store(true);
- }
- break;
- case SIGINT:
- case SIGTERM:
- // Someone requested our termination
-
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
-
- sa.sa_handler = SIG_DFL;
+static void BlockUnixSignals(std::initializer_list signals){
+ sigset_t sigSet;
- (void)sigaction(num, &sa, nullptr);
- }
-
- l_TermSignal.store(num);
- break;
- case SIGHUP:
- // Someone requested to re-load config
- l_RequestedReload.store(true);
- break;
- default:
- // Programming error (or someone has broken the userspace)
- VERIFY(!"Caught unexpected signal");
+ (void)sigemptyset(&sigSet);
+ for (auto signal : signals) {
+ (void)sigaddset(&sigSet, signal);
}
-}
+
+ (void)pthread_sigmask(SIG_BLOCK, &sigSet, nullptr);
+};
/**
- * Seamless worker's signal handlers
+ * Unblocks the signal and sets the handler to the given value.
+ *
+ * @param signal The signal to unblock and reset
+ * @param handler The new handler after unblocking
*/
-static void WorkerSignalHandler(int num, siginfo_t *info, void*)
+static void UnblockSignalAndSetHandler(int signal, void (*handler)(int))
{
- switch (num) {
- case SIGUSR1:
- // Catches SIGUSR1 as long as the actual handler (logrotate)
- // has not been installed not to let SIGUSR1 terminate the process
- break;
- case SIGUSR2:
- if (info->si_pid == 0 || info->si_pid == l_UmbrellaPid) {
- // The umbrella process allowed us to continue working beyond config validation
- l_AllowedToWork.store(true);
- }
- break;
- case SIGINT:
- case SIGTERM:
- if (info->si_pid == 0 || info->si_pid == l_UmbrellaPid) {
- // The umbrella process requested our termination
- Application::RequestShutdown();
- }
- break;
- default:
- // Programming error (or someone has broken the userspace)
- VERIFY(!"Caught unexpected signal");
- }
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = handler;
+ (void)sigaction(signal, &sa, nullptr);
+
+ sigset_t sigSet;
+ (void)sigemptyset(&sigSet);
+ (void)sigaddset(&sigSet, signal);
+ (void)pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr);
}
#ifdef HAVE_SYSTEMD
@@ -468,11 +403,6 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close
exit(EXIT_FAILURE);
}
- /* Block the signal handlers we'd like to change in the child process until we changed them.
- * Block SIGUSR2 handler until we've set l_CurrentlyStartingUnixWorkerPid.
- */
- (void)sigprocmask(SIG_BLOCK, &l_UnixWorkerSignals, nullptr);
-
pid_t pid = fork();
switch (pid) {
@@ -487,44 +417,15 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close
<< "Failed to re-initialize thread pool after forking (parent): " << DiagnosticInformation(ex);
exit(EXIT_FAILURE);
}
-
- (void)sigprocmask(SIG_UNBLOCK, &l_UnixWorkerSignals, nullptr);
return -1;
case 0:
try {
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
-
- sa.sa_handler = SIG_DFL;
-
- (void)sigaction(SIGUSR1, &sa, nullptr);
- }
-
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
-
- sa.sa_handler = SIG_IGN;
-
- (void)sigaction(SIGHUP, &sa, nullptr);
- }
-
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
-
- sa.sa_sigaction = &WorkerSignalHandler;
- sa.sa_flags = SA_RESTART | SA_SIGINFO;
-
- (void)sigaction(SIGUSR1, &sa, nullptr);
- (void)sigaction(SIGUSR2, &sa, nullptr);
- (void)sigaction(SIGINT, &sa, nullptr);
- (void)sigaction(SIGTERM, &sa, nullptr);
- }
-
- (void)sigprocmask(SIG_UNBLOCK, &l_UnixWorkerSignals, nullptr);
+ /* We'll leave the other signals blocked since we're going
+ * to handle them synchronously with sigtimedwait in
+ * Application::RunEventLoop().
+ */
+ UnblockSignalAndSetHandler(SIGHUP, SIG_IGN);
try {
Application::InitializeBase();
@@ -551,9 +452,6 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close
}
default:
- l_CurrentlyStartingUnixWorkerPid.store(pid);
- (void)sigprocmask(SIG_UNBLOCK, &l_UnixWorkerSignals, nullptr);
-
Log(LogNotice, "cli")
<< "Spawned worker process (PID " << pid << "), waiting for it to load its config";
@@ -571,7 +469,8 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close
break;
}
- if (l_CurrentlyStartingUnixWorkerReady.load()) {
+ auto sig = Application::GetPendingSignal(SIGUSR2);
+ if (sig && (sig->si_pid == 0 || sig->si_pid == pid)) {
Log(LogNotice, "cli")
<< "Worker process successfully loaded its config";
break;
@@ -580,10 +479,6 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close
Utility::Sleep(0.2);
}
- // Reset flags for the next time
- l_CurrentlyStartingUnixWorkerPid.store(-1);
- l_CurrentlyStartingUnixWorkerReady.store(false);
-
try {
Application::InitializeBase();
} catch (const std::exception& ex) {
@@ -620,6 +515,13 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vectorsi_signo, SIG_DFL);
+ Log(LogCritical, "cli")
+ << "Got signal " << sig->si_signo << ", forwarding to seamless worker (PID " << currentWorker << ")";
- (void)kill(currentWorker, termSig);
+ (void)kill(currentWorker, sig->si_signo);
requestedTermination = true;
#ifdef HAVE_SYSTEMD
@@ -794,7 +683,7 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vector