diff --git a/console/executor.hpp b/console/executor.hpp index 900359d3..5376f508 100644 --- a/console/executor.hpp +++ b/console/executor.hpp @@ -190,7 +190,8 @@ class executor // Service installation, platform-specific. Returns a system error code // (win32 or errno), where zero is success. static uint32_t create_service(const std::filesystem::path& config, - const std::string& account, const std::string& password) NOEXCEPT; + const std::filesystem::path& logs, const std::string& account, + const std::string& password) NOEXCEPT; static uint32_t delete_service() NOEXCEPT; static void initialize_stop(); diff --git a/console/executor_daemon.cpp b/console/executor_daemon.cpp index 1e960a43..003d790c 100644 --- a/console/executor_daemon.cpp +++ b/console/executor_daemon.cpp @@ -230,7 +230,8 @@ DWORD executor::grant_logon_right(const std::string& account) NOEXCEPT // Returns a win32 error code, where zero is success. uint32_t executor::create_service(const std::filesystem::path& config, - const std::string& account, const std::string& password) NOEXCEPT + const std::filesystem::path&, const std::string& account, + const std::string& password) NOEXCEPT { using namespace system; const auto command = command_line(config); @@ -328,9 +329,19 @@ uint32_t executor::delete_service() NOEXCEPT #if defined(HAVE_POSIX) -// The drain exceeds the default stop timeout on both platforms. +// The drain exceeds the default stop timeout on both platforms, and can run +// to minutes, depending on dirty page volume and disk speed. constexpr auto stop_timeout_seconds = 600; +// Console redirection file, within the configured log directory. Distinct +// from the rotated log files, which the node writes and buffers itself. +constexpr auto console_file = "console.log"; + +// launchd clamps a positive ExitTimeOut to 60 seconds (600 reports as 60), so +// this is the effective drain ceiling on osx. Do not "fix" this with zero, +// which launchd documents as infinite but which kills without draining. +constexpr auto exit_timeout_seconds = stop_timeout_seconds; + #if defined(HAVE_LINUX) // A systemd unit, and the symlink by which the unit is enabled. @@ -345,10 +356,13 @@ static std::filesystem::path enabled_path(const std::string& name) NOEXCEPT } // Type=notify defers dependent units until the node reports ready, which is -// the posix counterpart to reporting SERVICE_RUNNING to the manager. +// the posix counterpart to reporting SERVICE_RUNNING to the manager. Restart +// is not configured, as a store that fails to open does so deterministically, +// and retrying only obscures the fault (as does the manager on windows). static std::string unit_text(const std::string& command, - const std::string& account) NOEXCEPT + const std::string&, const std::string& account) NOEXCEPT { + // Console output is captured to the journal, so it is not redirected. return "[Unit]\n" "Description=" BS_SERVICE_DESCRIPTION "\n" @@ -360,7 +374,6 @@ static std::string unit_text(const std::string& command, "ExecStart=" + command + "\n" + (account.empty() ? "" : "User=" + account + "\n") + "TimeoutStopSec=" + std::to_string(stop_timeout_seconds) + "\n" - "Restart=on-failure\n" "\n" "[Install]\n" "WantedBy=multi-user.target\n"; @@ -374,8 +387,12 @@ static std::filesystem::path unit_path(const std::string&) NOEXCEPT } // launchd has no readiness protocol, so the job is simply run at load. +// KeepAlive is not configured, as it restarts upon any exit, and a store that +// fails to open does so deterministically (as with the other two managers). +// launchd discards console output, and the log file is buffered, so redirect +// it to obtain the live view that the journal provides on linux. static std::string unit_text(const std::string& command, - const std::string& account) NOEXCEPT + const std::string& console, const std::string& account) NOEXCEPT { return R"()" "\n" @@ -389,10 +406,13 @@ static std::string unit_text(const std::string& command, " \n" + (account.empty() ? "" : " UserName" + account + "\n") + + (console.empty() ? "" : + " StandardOutPath" + console + "\n" + " StandardErrorPath" + console + + "\n") + " RunAtLoad\n" - " KeepAlive\n" " ExitTimeOut" + - std::to_string(stop_timeout_seconds) + "\n" + std::to_string(exit_timeout_seconds) + "\n" "\n" "\n"; } @@ -401,12 +421,17 @@ static std::string unit_text(const std::string& command, // Returns an errno value, where zero is success. uint32_t executor::create_service(const std::filesystem::path& config, - const std::string& account, const std::string&) NOEXCEPT + const std::filesystem::path& logs, const std::string& account, + const std::string&) NOEXCEPT { const auto command = command_line(config); if (command.empty()) return EINVAL; + // Unconfigured logging implies no console redirection (linux ignores it). + const auto console = logs.empty() ? std::string{} : + system::from_path(system::qualified_path(logs) / console_file); + code ec{}; const auto unit = unit_path(name_); if (std::filesystem::exists(unit, ec)) @@ -420,7 +445,7 @@ uint32_t executor::create_service(const std::filesystem::path& config, if (!file.good()) return EACCES; - file << unit_text(command, account); + file << unit_text(command, console, account); file.flush(); if (!file.good()) return EIO; @@ -633,7 +658,8 @@ bool executor::do_daemon() const auto install = metadata_.configured.daemon.value(); const auto result = install ? - create_service(metadata_.configured.file, account, password) : + create_service(metadata_.configured.file, metadata_.configured.log.path, + account, password) : delete_service(); switch (result) diff --git a/console/executor_logging.cpp b/console/executor_logging.cpp index 4181ffac..fdd6dce7 100644 --- a/console/executor_logging.cpp +++ b/console/executor_logging.cpp @@ -87,7 +87,11 @@ void executor::subscribe_log(std::ostream& sink) output_ << prefix << message << std::endl; sink << prefix << BS_NODE_FOOTER << std::endl; output_ << prefix << BS_NODE_FOOTER << std::endl; - output_ << prefix << BS_NODE_TERMINATE << std::endl; + + // A service has no console to await, and posix captures + // console output to the service log, where this is noise. + if (!service_) + output_ << prefix << BS_NODE_TERMINATE << std::endl; // Release subscription and signal termination of log. // Log destruct blocks on its own threadpool join, which occurs