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