diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:34:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:34:54 +0000 |
commit | 0915b3ef56dfac3113cce55a59a5765dc94976be (patch) | |
tree | a8fea11d50b4f083e1bf0f90025ece7f0824784a /lib/cli/daemoncommand.cpp | |
parent | Initial commit. (diff) | |
download | icinga2-0915b3ef56dfac3113cce55a59a5765dc94976be.tar.xz icinga2-0915b3ef56dfac3113cce55a59a5765dc94976be.zip |
Adding upstream version 2.13.6.upstream/2.13.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/cli/daemoncommand.cpp')
-rw-r--r-- | lib/cli/daemoncommand.cpp | 882 |
1 files changed, 882 insertions, 0 deletions
diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp new file mode 100644 index 0000000..fb1dd02 --- /dev/null +++ b/lib/cli/daemoncommand.cpp @@ -0,0 +1,882 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "cli/daemoncommand.hpp" +#include "cli/daemonutility.hpp" +#include "remote/apilistener.hpp" +#include "remote/configobjectutility.hpp" +#include "config/configcompiler.hpp" +#include "config/configcompilercontext.hpp" +#include "config/configitembuilder.hpp" +#include "base/atomic.hpp" +#include "base/defer.hpp" +#include "base/logger.hpp" +#include "base/application.hpp" +#include "base/process.hpp" +#include "base/timer.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include "base/convert.hpp" +#include "base/scriptglobal.hpp" +#include "base/context.hpp" +#include "config.h" +#include <cstdint> +#include <cstring> +#include <boost/program_options.hpp> +#include <iostream> +#include <fstream> + +#ifdef _WIN32 +#include <windows.h> +#else /* _WIN32 */ +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#endif /* _WIN32 */ + +#ifdef HAVE_SYSTEMD +#include <systemd/sd-daemon.h> +#endif /* HAVE_SYSTEMD */ + +using namespace icinga; +namespace po = boost::program_options; + +static po::variables_map g_AppParams; + +REGISTER_CLICOMMAND("daemon", DaemonCommand); + +static inline +void NotifyStatus(const char* status) +{ +#ifdef HAVE_SYSTEMD + (void)sd_notifyf(0, "STATUS=%s", status); +#endif /* HAVE_SYSTEMD */ +} + +/* + * Daemonize(). On error, this function logs by itself and exits (i.e. does not return). + * + * Implementation note: We're only supposed to call exit() in one of the forked processes. + * The other process calls _exit(). This prevents issues with exit handlers like atexit(). + */ +static void Daemonize() noexcept +{ +#ifndef _WIN32 + try { + Application::UninitializeBase(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to stop thread pool before daemonizing, unexpected error: " << DiagnosticInformation(ex); + exit(EXIT_FAILURE); + } + + pid_t pid = fork(); + if (pid == -1) { + Log(LogCritical, "cli") + << "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + exit(EXIT_FAILURE); + } + + if (pid) { + // systemd requires that the pidfile of the daemon is written before the forking + // process terminates. So wait till either the forked daemon has written a pidfile or died. + + int status; + int ret; + pid_t readpid; + do { + Utility::Sleep(0.1); + + readpid = Application::ReadPidFile(Configuration::PidPath); + ret = waitpid(pid, &status, WNOHANG); + } while (readpid != pid && ret == 0); + + if (ret == pid) { + Log(LogCritical, "cli", "The daemon could not be started. See log output for details."); + _exit(EXIT_FAILURE); + } else if (ret == -1) { + Log(LogCritical, "cli") + << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + Log(LogDebug, "Daemonize()") + << "Child process with PID " << Utility::GetPid() << " continues; re-initializing base."; + + // Detach from controlling terminal + pid_t sid = setsid(); + if (sid == -1) { + Log(LogCritical, "cli") + << "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + exit(EXIT_FAILURE); + } + + try { + Application::InitializeBase(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to re-initialize thread pool after daemonizing: " << DiagnosticInformation(ex); + exit(EXIT_FAILURE); + } +#endif /* _WIN32 */ +} + +static void CloseStdIO(const String& stderrFile) +{ +#ifndef _WIN32 + int fdnull = open("/dev/null", O_RDWR); + if (fdnull >= 0) { + if (fdnull != 0) + dup2(fdnull, 0); + + if (fdnull != 1) + dup2(fdnull, 1); + + if (fdnull > 1) + close(fdnull); + } + + const char *errPath = "/dev/null"; + + if (!stderrFile.IsEmpty()) + errPath = stderrFile.CStr(); + + int fderr = open(errPath, O_WRONLY | O_APPEND); + + if (fderr < 0 && errno == ENOENT) + fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600); + + if (fderr >= 0) { + if (fderr != 2) + dup2(fderr, 2); + + if (fderr > 2) + close(fderr); + } +#endif +} + +String DaemonCommand::GetDescription() const +{ + return "Starts Icinga 2."; +} + +String DaemonCommand::GetShortDescription() const +{ + return "starts Icinga 2"; +} + +void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const +{ + visibleDesc.add_options() + ("config,c", po::value<std::vector<std::string> >(), "parse a configuration file") + ("no-config,z", "start without a configuration file") + ("validate,C", "exit after validating the configuration") + ("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize or --close-stdio)") +#ifndef _WIN32 + ("daemonize,d", "detach from the controlling terminal") + ("close-stdio", "do not log to stdout (or stderr) after startup") +#endif /* _WIN32 */ + ; +} + +std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const +{ + if (argument == "config" || argument == "errorlog") + return GetBashCompletionSuggestions("file", word); + else + return CLICommand::GetArgumentSuggestions(argument, word); +} + +#ifndef _WIN32 +// The PID of the Icinga umbrella process +pid_t l_UmbrellaPid = 0; + +// Whether the umbrella process allowed us to continue working beyond config validation +static Atomic<bool> l_AllowedToWork (false); +#endif /* _WIN32 */ + +#ifdef I2_DEBUG +/** + * Determine whether the developer wants to delay the worker process to attach a debugger to it. + * + * @return Internal.DebugWorkerDelay double + */ +static double GetDebugWorkerDelay() +{ + Namespace::Ptr internal = ScriptGlobal::Get("Internal", &Empty); + + Value vdebug; + if (internal && internal->Get("DebugWorkerDelay", &vdebug)) + return Convert::ToDouble(vdebug); + + return 0.0; +} +#endif /* I2_DEBUG */ + +/** + * Do the actual work (config loading, ...) + * + * @param configs Files to read config from + * @param closeConsoleLog Whether to close the console log after config loading + * @param stderrFile Where to log errors + * + * @return Exit code + */ +static inline +int RunWorker(const std::vector<std::string>& configs, bool closeConsoleLog = false, const String& stderrFile = String()) +{ + +#ifdef I2_DEBUG + double delay = GetDebugWorkerDelay(); + + if (delay > 0.0) { + Log(LogInformation, "RunWorker") + << "DEBUG: Current PID: " << Utility::GetPid() << ". Sleeping for " << delay << " seconds to allow lldb/gdb -p <PID> attachment."; + + Utility::Sleep(delay); + } +#endif /* I2_DEBUG */ + + Log(LogInformation, "cli", "Loading configuration file(s)."); + NotifyStatus("Loading configuration file(s)..."); + + { + std::vector<ConfigItem::Ptr> newItems; + + if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath)) { + Log(LogCritical, "cli", "Config validation failed. Re-run with 'icinga2 daemon -C' after fixing the config."); + NotifyStatus("Config validation failed."); + return EXIT_FAILURE; + } + +#ifndef _WIN32 + Log(LogNotice, "cli") + << "Notifying umbrella process (PID " << l_UmbrellaPid << ") about the config loading success"; + + (void)kill(l_UmbrellaPid, SIGUSR2); + + Log(LogNotice, "cli") + << "Waiting for the umbrella process to let us doing the actual work"; + + NotifyStatus("Waiting for the umbrella process to let us doing the actual work..."); + + if (closeConsoleLog) { + CloseStdIO(stderrFile); + Logger::DisableConsoleLog(); + } + + while (!l_AllowedToWork.load()) { + Utility::Sleep(0.2); + } + + Log(LogNotice, "cli") + << "The umbrella process let us continuing"; +#endif /* _WIN32 */ + + NotifyStatus("Restoring the previous program state..."); + + /* restore the previous program state */ + try { + ConfigObject::RestoreObjects(Configuration::StatePath); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to restore state file: " << DiagnosticInformation(ex); + + NotifyStatus("Failed to restore state file."); + + return EXIT_FAILURE; + } + + NotifyStatus("Activating config objects..."); + + // activate config only after daemonization: it starts threads and that is not compatible with fork() + if (!ConfigItem::ActivateItems(newItems, false, true, true)) { + Log(LogCritical, "cli", "Error activating configuration."); + + NotifyStatus("Error activating configuration."); + + return EXIT_FAILURE; + } + } + + /* Create the internal API object storage. Do this here too with setups without API. */ + ConfigObjectUtility::CreateStorage(); + + /* Remove ignored Downtime/Comment objects. */ + try { + String configDir = ConfigObjectUtility::GetConfigDir(); + ConfigItem::RemoveIgnoredItems(configDir); + } catch (const std::exception& ex) { + Log(LogNotice, "cli") + << "Cannot clean ignored downtimes/comments: " << ex.what(); + } + + ApiListener::UpdateObjectAuthority(); + + NotifyStatus("Startup finished."); + + return Application::GetInstance()->Run(); +} + +#ifndef _WIN32 +/** + * The possible states of a seamless worker being started by StartUnixWorker(). + */ +enum class UnixWorkerState : uint_fast8_t +{ + Pending, + LoadedConfig, + Failed +}; + +// The signals to block temporarily in StartUnixWorker(). +static const sigset_t l_UnixWorkerSignals = ([]() -> sigset_t { + sigset_t s; + + (void)sigemptyset(&s); + (void)sigaddset(&s, SIGCHLD); + (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<pid_t> l_CurrentlyStartingUnixWorkerPid (-1); + +// The state of the seamless worker currently being started by StartUnixWorker() +static Atomic<UnixWorkerState> l_CurrentlyStartingUnixWorkerState (UnixWorkerState::Pending); + +// The last temination signal we received +static Atomic<int> l_TermSignal (-1); + +// Whether someone requested to re-load config (and we didn't handle that request, yet) +static Atomic<bool> l_RequestedReload (false); + +// Whether someone requested to re-open logs (and we didn't handle that request, yet) +static Atomic<bool> l_RequestedReopenLogs (false); + +/** + * Umbrella process' signal handlers + */ +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_CurrentlyStartingUnixWorkerState.load() == UnixWorkerState::Pending + && (info->si_pid == 0 || info->si_pid == l_CurrentlyStartingUnixWorkerPid.load()) ) { + // The seamless worker currently being started by StartUnixWorker() successfully loaded its config + l_CurrentlyStartingUnixWorkerState.store(UnixWorkerState::LoadedConfig); + } + break; + case SIGCHLD: + if (l_CurrentlyStartingUnixWorkerState.load() == UnixWorkerState::Pending + && (info->si_pid == 0 || info->si_pid == l_CurrentlyStartingUnixWorkerPid.load()) ) { + // The seamless worker currently being started by StartUnixWorker() failed + l_CurrentlyStartingUnixWorkerState.store(UnixWorkerState::Failed); + } + break; + case SIGINT: + case SIGTERM: + // Someone requested our termination + + { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + + sa.sa_handler = SIG_DFL; + + (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"); + } +} + +/** + * Seamless worker's signal handlers + */ +static void WorkerSignalHandler(int num, siginfo_t *info, void*) +{ + 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"); + } +} + +#ifdef HAVE_SYSTEMD +// When we last notified the watchdog. +static Atomic<double> l_LastNotifiedWatchdog (0); + +/** + * Notify the watchdog if not notified during the last 2.5s. + */ +static void NotifyWatchdog() +{ + double now = Utility::GetTime(); + + if (now - l_LastNotifiedWatchdog.load() >= 2.5) { + sd_notify(0, "WATCHDOG=1"); + l_LastNotifiedWatchdog.store(now); + } +} +#endif /* HAVE_SYSTEMD */ + +/** + * Starts seamless worker process doing the actual work (config loading, ...) + * + * @param configs Files to read config from + * @param closeConsoleLog Whether to close the console log after config loading + * @param stderrFile Where to log errors + * + * @return The worker's PID on success, -1 on fork(2) failure, -2 if the worker couldn't load its config + */ +static pid_t StartUnixWorker(const std::vector<std::string>& configs, bool closeConsoleLog = false, const String& stderrFile = String()) +{ + Log(LogNotice, "cli") + << "Spawning seamless worker process doing the actual work"; + + try { + Application::UninitializeBase(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to stop thread pool before forking, unexpected error: " << DiagnosticInformation(ex); + exit(EXIT_FAILURE); + } + + /* Block the signal handlers we'd like to change in the child process until we changed them. + * Block SIGUSR2 and SIGCHLD handlers until we've set l_CurrentlyStartingUnixWorkerPid. + */ + (void)sigprocmask(SIG_BLOCK, &l_UnixWorkerSignals, nullptr); + + pid_t pid = fork(); + + switch (pid) { + case -1: + Log(LogCritical, "cli") + << "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + try { + Application::InitializeBase(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "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(SIGCHLD, &sa, nullptr); + (void)sigaction(SIGUSR1, &sa, nullptr); + (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); + + try { + Application::InitializeBase(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to re-initialize thread pool after forking (child): " << DiagnosticInformation(ex); + _exit(EXIT_FAILURE); + } + + try { + Process::InitializeSpawnHelper(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to initialize process spawn helper after forking (child): " << DiagnosticInformation(ex); + _exit(EXIT_FAILURE); + } + + _exit(RunWorker(configs, closeConsoleLog, stderrFile)); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") << "Exception in main process: " << DiagnosticInformation(ex); + _exit(EXIT_FAILURE); + } catch (...) { + _exit(EXIT_FAILURE); + } + + 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"; + + // Wait for the newly spawned process to either load its config or fail. + for (;;) { +#ifdef HAVE_SYSTEMD + NotifyWatchdog(); +#endif /* HAVE_SYSTEMD */ + + switch (l_CurrentlyStartingUnixWorkerState.load()) { + case UnixWorkerState::LoadedConfig: + Log(LogNotice, "cli") + << "Worker process successfully loaded its config"; + break; + case UnixWorkerState::Failed: + Log(LogNotice, "cli") + << "Worker process couldn't load its config"; + + while (waitpid(pid, nullptr, 0) == -1 && errno == EINTR) { +#ifdef HAVE_SYSTEMD + NotifyWatchdog(); +#endif /* HAVE_SYSTEMD */ + } + pid = -2; + break; + default: + Utility::Sleep(0.2); + continue; + } + + break; + } + + // Reset flags for the next time + l_CurrentlyStartingUnixWorkerPid.store(-1); + l_CurrentlyStartingUnixWorkerState.store(UnixWorkerState::Pending); + + try { + Application::InitializeBase(); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "Failed to re-initialize thread pool after forking (parent): " << DiagnosticInformation(ex); + exit(EXIT_FAILURE); + } + } + + return pid; +} + +/** + * Workaround to instantiate Application (which is abstract) in DaemonCommand#Run() + */ +class PidFileManagementApp : public Application +{ +public: + inline int Main() override + { + return EXIT_FAILURE; + } +}; +#endif /* _WIN32 */ + +/** + * The entry point for the "daemon" CLI command. + * + * @returns An exit status. + */ +int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const +{ +#ifdef _WIN32 + SetConsoleOutputCP(65001); +#endif /* _WIN32 */ + + Logger::EnableTimestamp(); + + Log(LogInformation, "cli") + << "Icinga application loader (version: " << Application::GetAppVersion() +#ifdef I2_DEBUG + << "; debug" +#endif /* I2_DEBUG */ + << ")"; + + std::vector<std::string> configs; + if (vm.count("config") > 0) + configs = vm["config"].as<std::vector<std::string> >(); + else if (!vm.count("no-config")) { + /* The implicit string assignment is needed for Windows builds. */ + String configDir = Configuration::ConfigDir; + configs.push_back(configDir + "/icinga2.conf"); + } + + if (vm.count("validate")) { + Log(LogInformation, "cli", "Loading configuration file(s)."); + + std::vector<ConfigItem::Ptr> newItems; + + if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath)) { + Log(LogCritical, "cli", "Config validation failed. Re-run with 'icinga2 daemon -C' after fixing the config."); + return EXIT_FAILURE; + } + + Log(LogInformation, "cli", "Finished validating the configuration file(s)."); + return EXIT_SUCCESS; + } + + { + pid_t runningpid = Application::ReadPidFile(Configuration::PidPath); + if (runningpid > 0) { + Log(LogCritical, "cli") + << "Another instance of Icinga already running with PID " << runningpid; + return EXIT_FAILURE; + } + } + + if (vm.count("daemonize")) { + // this subroutine either succeeds, or logs an error + // and terminates the process (does not return). + Daemonize(); + } + +#ifndef _WIN32 + /* The Application manages the PID file, + * but on *nix this process doesn't load any config + * so there's no central Application instance. + */ + PidFileManagementApp app; + + try { + app.UpdatePidFile(Configuration::PidPath); + } catch (const std::exception&) { + Log(LogCritical, "Application") + << "Cannot update PID file '" << Configuration::PidPath << "'. Aborting."; + return EXIT_FAILURE; + } + + Defer closePidFile ([&app]() { + app.ClosePidFile(true); + }); +#endif /* _WIN32 */ + + if (vm.count("daemonize")) { + // After disabling the console log, any further errors will go to the configured log only. + // Let's try to make this clear and say good bye. + Log(LogInformation, "cli", "Closing console log."); + + String errorLog; + if (vm.count("errorlog")) + errorLog = vm["errorlog"].as<std::string>(); + + CloseStdIO(errorLog); + Logger::DisableConsoleLog(); + } + +#ifdef _WIN32 + try { + return RunWorker(configs); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") << "Exception in main process: " << DiagnosticInformation(ex); + return EXIT_FAILURE; + } catch (...) { + return EXIT_FAILURE; + } +#else /* _WIN32 */ + l_UmbrellaPid = getpid(); + Application::SetUmbrellaProcess(l_UmbrellaPid); + + { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + + sa.sa_sigaction = &UmbrellaSignalHandler; + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO; + + (void)sigaction(SIGCHLD, &sa, nullptr); + (void)sigaction(SIGUSR1, &sa, nullptr); + (void)sigaction(SIGUSR2, &sa, nullptr); + (void)sigaction(SIGINT, &sa, nullptr); + (void)sigaction(SIGTERM, &sa, nullptr); + (void)sigaction(SIGHUP, &sa, nullptr); + } + + bool closeConsoleLog = !vm.count("daemonize") && vm.count("close-stdio"); + + String errorLog; + if (vm.count("errorlog")) + errorLog = vm["errorlog"].as<std::string>(); + + // The PID of the current seamless worker + pid_t currentWorker = StartUnixWorker(configs, closeConsoleLog, errorLog); + + if (currentWorker < 0) { + return EXIT_FAILURE; + } + + if (closeConsoleLog) { + // After disabling the console log, any further errors will go to the configured log only. + // Let's try to make this clear and say good bye. + Log(LogInformation, "cli", "Closing console log."); + + CloseStdIO(errorLog); + Logger::DisableConsoleLog(); + } + + // Immediately allow the first (non-reload) worker to continue working beyond config validation + (void)kill(currentWorker, SIGUSR2); + +#ifdef HAVE_SYSTEMD + sd_notify(0, "READY=1"); +#endif /* HAVE_SYSTEMD */ + + // Whether we already forwarded a termination signal to the seamless worker + bool requestedTermination = false; + + // Whether we already notified systemd about our termination + bool notifiedTermination = false; + + for (;;) { +#ifdef HAVE_SYSTEMD + NotifyWatchdog(); +#endif /* HAVE_SYSTEMD */ + + if (!requestedTermination) { + int termSig = l_TermSignal.load(); + if (termSig != -1) { + Log(LogNotice, "cli") + << "Got signal " << termSig << ", forwarding to seamless worker (PID " << currentWorker << ")"; + + (void)kill(currentWorker, termSig); + requestedTermination = true; + +#ifdef HAVE_SYSTEMD + if (!notifiedTermination) { + notifiedTermination = true; + sd_notify(0, "STOPPING=1"); + } +#endif /* HAVE_SYSTEMD */ + } + } + + if (l_RequestedReload.exchange(false)) { + Log(LogInformation, "Application") + << "Got reload command: Starting new instance."; + +#ifdef HAVE_SYSTEMD + sd_notify(0, "RELOADING=1"); +#endif /* HAVE_SYSTEMD */ + + pid_t nextWorker = StartUnixWorker(configs); + + switch (nextWorker) { + case -1: + break; + case -2: + Log(LogCritical, "Application", "Found error in config: reloading aborted"); + break; + default: + Log(LogInformation, "Application") + << "Reload done, old process shutting down. Child process with PID '" << nextWorker << "' is taking over."; + + NotifyStatus("Shutting down old instance..."); + + (void)kill(currentWorker, SIGTERM); + + { + double start = Utility::GetTime(); + + while (waitpid(currentWorker, nullptr, 0) == -1 && errno == EINTR) { + #ifdef HAVE_SYSTEMD + NotifyWatchdog(); + #endif /* HAVE_SYSTEMD */ + } + + Log(LogNotice, "cli") + << "Waited for " << Utility::FormatDuration(Utility::GetTime() - start) << " on old process to exit."; + } + + // Old instance shut down, allow the new one to continue working beyond config validation + (void)kill(nextWorker, SIGUSR2); + + NotifyStatus("Shut down old instance."); + + currentWorker = nextWorker; + } + +#ifdef HAVE_SYSTEMD + sd_notify(0, "READY=1"); +#endif /* HAVE_SYSTEMD */ + + } + + if (l_RequestedReopenLogs.exchange(false)) { + Log(LogNotice, "cli") + << "Got signal " << SIGUSR1 << ", forwarding to seamless worker (PID " << currentWorker << ")"; + + (void)kill(currentWorker, SIGUSR1); + } + + { + int status; + if (waitpid(currentWorker, &status, WNOHANG) > 0) { + Log(LogNotice, "cli") + << "Seamless worker (PID " << currentWorker << ") stopped, stopping as well"; + +#ifdef HAVE_SYSTEMD + if (!notifiedTermination) { + notifiedTermination = true; + sd_notify(0, "STOPPING=1"); + } +#endif /* HAVE_SYSTEMD */ + + // If killed by signal, forward it via the exit code (to be as seamless as possible) + return WIFSIGNALED(status) ? 128 + WTERMSIG(status) : WEXITSTATUS(status); + } + } + + Utility::Sleep(0.2); + } +#endif /* _WIN32 */ +} |