diff options
Diffstat (limited to 'src/zenserver/zenserver.cpp')
| -rw-r--r-- | src/zenserver/zenserver.cpp | 260 |
1 files changed, 210 insertions, 50 deletions
diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp index 5fd35d9b4..087b40d6a 100644 --- a/src/zenserver/zenserver.cpp +++ b/src/zenserver/zenserver.cpp @@ -23,9 +23,11 @@ #include <zencore/timer.h> #include <zencore/trace.h> #include <zencore/workthreadpool.h> +#include <zenhttp/httpclient.h> #include <zenhttp/httpserver.h> #include <zenhttp/security/passwordsecurityfilter.h> #include <zentelemetry/otlptrace.h> +#include <zenutil/authutils.h> #include <zenutil/service.h> #include <zenutil/workerpools.h> #include <zenutil/zenserverprocess.h> @@ -46,6 +48,20 @@ ZEN_THIRD_PARTY_INCLUDES_END ////////////////////////////////////////////////////////////////////////// +#ifndef ZEN_WITH_COMPUTE_SERVICES +# define ZEN_WITH_COMPUTE_SERVICES 0 +#endif + +#ifndef ZEN_WITH_HORDE +# define ZEN_WITH_HORDE 0 +#endif + +#ifndef ZEN_WITH_NOMAD +# define ZEN_WITH_NOMAD 0 +#endif + +////////////////////////////////////////////////////////////////////////// + #include "config/config.h" #include "diag/logging.h" @@ -85,6 +101,7 @@ ZenServerBase::Initialize(const ZenServerConfig& ServerOptions, ZenServerState:: ZEN_MEMSCOPE(GetZenserverTag()); m_IsPowerCycle = ServerOptions.IsPowerCycle; + m_NoNetwork = ServerOptions.HttpConfig.NoNetwork; const std::string MutexName = fmt::format("zen_{}", ServerOptions.BasePort); @@ -131,6 +148,14 @@ ZenServerBase::Initialize(const ZenServerConfig& ServerOptions, ZenServerState:: EnqueueSigIntTimer(); + // Configure HTTP client back-end + + const std::string HttpClientBackend = ToLower(ServerOptions.HttpClient.Backend); + zen::SetDefaultHttpClientBackend(HttpClientBackend); + ZEN_INFO("Using '{}' as HTTP client backend", HttpClientBackend); + + // Initialize HTTP server + m_Http = CreateHttpServer(ServerOptions.HttpConfig); int EffectiveBasePort = m_Http->Initialize(ServerOptions.BasePort, ServerOptions.DataDir); if (EffectiveBasePort == 0) @@ -155,6 +180,7 @@ ZenServerBase::Initialize(const ZenServerConfig& ServerOptions, ZenServerState:: m_StatusService.RegisterHandler("status", *this); m_Http->RegisterService(m_StatusService); + m_Http->RegisterService(m_StatsService); m_StatsReporter.Initialize(ServerOptions.StatsConfig); if (ServerOptions.StatsConfig.Enabled) @@ -162,10 +188,40 @@ ZenServerBase::Initialize(const ZenServerConfig& ServerOptions, ZenServerState:: EnqueueStatsReportingTimer(); } - m_HealthService.SetHealthInfo({.DataRoot = ServerOptions.DataDir, - .AbsLogPath = ServerOptions.LoggingConfig.AbsLogFile, - .HttpServerClass = std::string(ServerOptions.HttpConfig.ServerClass), - .BuildVersion = std::string(ZEN_CFG_VERSION_BUILD_STRING_FULL)}); + // clang-format off + HealthServiceInfo HealthInfo { + .DataRoot = ServerOptions.DataDir, + .AbsLogPath = ServerOptions.LoggingConfig.AbsLogFile, + .HttpServerClass = std::string(ServerOptions.HttpConfig.ServerClass), + .BuildVersion = std::string(ZEN_CFG_VERSION_BUILD_STRING_FULL), + .Port = EffectiveBasePort, + .Pid = GetCurrentProcessId(), + .IsDedicated = ServerOptions.IsDedicated, + .StartTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::system_clock::now().time_since_epoch()).count(), + .BuildOptions = { + {"ZEN_ADDRESS_SANITIZER", ZEN_ADDRESS_SANITIZER != 0}, + {"ZEN_THREAD_SANITIZER", ZEN_THREAD_SANITIZER != 0}, + {"ZEN_MEMORY_SANITIZER", ZEN_MEMORY_SANITIZER != 0}, + {"ZEN_LEAK_SANITIZER", ZEN_LEAK_SANITIZER != 0}, + {"ZEN_USE_SENTRY", ZEN_USE_SENTRY != 0}, + {"ZEN_WITH_TESTS", ZEN_WITH_TESTS != 0}, + {"ZEN_USE_MIMALLOC", ZEN_USE_MIMALLOC != 0}, + {"ZEN_USE_RPMALLOC", ZEN_USE_RPMALLOC != 0}, + {"ZEN_WITH_HTTPSYS", ZEN_WITH_HTTPSYS != 0}, + {"ZEN_WITH_MEMTRACK", ZEN_WITH_MEMTRACK != 0}, + {"ZEN_WITH_TRACE", ZEN_WITH_TRACE != 0}, + {"ZEN_WITH_COMPUTE_SERVICES", ZEN_WITH_COMPUTE_SERVICES != 0}, + {"ZEN_WITH_HORDE", ZEN_WITH_HORDE != 0}, + {"ZEN_WITH_NOMAD", ZEN_WITH_NOMAD != 0}, + }, + .RuntimeConfig = BuildSettingsList(ServerOptions), + }; + // clang-format on + + HealthInfo.RuntimeConfig.emplace(HealthInfo.RuntimeConfig.begin() + 2, "EffectivePort"sv, fmt::to_string(EffectiveBasePort)); + + m_HealthService.SetHealthInfo(std::move(HealthInfo)); LogSettingsSummary(ServerOptions); @@ -175,18 +231,35 @@ ZenServerBase::Initialize(const ZenServerConfig& ServerOptions, ZenServerState:: void ZenServerBase::Finalize() { + m_StatsService.RegisterHandler("http", *m_Http); + + m_Http->SetDefaultRedirect("/dashboard/"); + // Register health service last so if we return "OK" for health it means all services have been properly initialized m_Http->RegisterService(m_HealthService); } void +ZenServerBase::ShutdownServices() +{ + m_StatsService.UnregisterHandler("http", *m_Http); + m_StatsService.Shutdown(); +} + +void ZenServerBase::GetBuildOptions(StringBuilderBase& OutOptions, char Separator) const { ZEN_MEMSCOPE(GetZenserverTag()); OutOptions << "ZEN_ADDRESS_SANITIZER=" << (ZEN_ADDRESS_SANITIZER ? "1" : "0"); OutOptions << Separator; + OutOptions << "ZEN_THREAD_SANITIZER=" << (ZEN_THREAD_SANITIZER ? "1" : "0"); + OutOptions << Separator; + OutOptions << "ZEN_MEMORY_SANITIZER=" << (ZEN_MEMORY_SANITIZER ? "1" : "0"); + OutOptions << Separator; + OutOptions << "ZEN_LEAK_SANITIZER=" << (ZEN_LEAK_SANITIZER ? "1" : "0"); + OutOptions << Separator; OutOptions << "ZEN_USE_SENTRY=" << (ZEN_USE_SENTRY ? "1" : "0"); OutOptions << Separator; OutOptions << "ZEN_WITH_TESTS=" << (ZEN_WITH_TESTS ? "1" : "0"); @@ -199,6 +272,8 @@ ZenServerBase::GetBuildOptions(StringBuilderBase& OutOptions, char Separator) co OutOptions << Separator; OutOptions << "ZEN_WITH_MEMTRACK=" << (ZEN_WITH_MEMTRACK ? "1" : "0"); OutOptions << Separator; + OutOptions << "ZEN_WITH_COMPUTE_SERVICES=" << (ZEN_WITH_COMPUTE_SERVICES ? "1" : "0"); + OutOptions << Separator; OutOptions << "ZEN_WITH_TRACE=" << (ZEN_WITH_TRACE ? "1" : "0"); } @@ -386,32 +461,43 @@ ZenServerBase::CheckSigInt() void ZenServerBase::HandleStatusRequest(HttpServerRequest& Request) { + auto Metrics = m_MetricsTracker.Query(); + CbObjectWriter Cbo; Cbo << "ok" << true; Cbo << "state" << ToString(m_CurrentState); + Cbo << "hostname" << GetMachineName(); + Cbo << "cpuUsagePercent" << Metrics.CpuUsagePercent; + Cbo << "serverMode" << std::string_view(m_ServerMode); + + std::vector<std::string> IpAddresses = GetLocalIpAddresses(); + Cbo.BeginArray("ipAddresses"sv); + for (const std::string& Ip : IpAddresses) + { + Cbo << Ip; + } + Cbo.EndArray(); + Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); } -void -ZenServerBase::LogSettingsSummary(const ZenServerConfig& ServerConfig) +std::vector<std::pair<std::string_view, std::string>> +ZenServerBase::BuildSettingsList(const ZenServerConfig& ServerConfig) { // clang-format off - std::list<std::pair<std::string_view, std::string>> Settings = { - {"DataDir"sv, fmt::format("{}", ServerConfig.DataDir)}, - {"AbsLogFile"sv, fmt::format("{}", ServerConfig.LoggingConfig.AbsLogFile)}, + std::vector<std::pair<std::string_view, std::string>> Settings = { {"SystemRootDir"sv, fmt::format("{}", ServerConfig.SystemRootDir)}, {"ContentDir"sv, fmt::format("{}", ServerConfig.ContentDir)}, {"BasePort"sv, fmt::to_string(ServerConfig.BasePort)}, + {"CoreLimit"sv, fmt::to_string(ServerConfig.CoreLimit)}, + {"MemoryAllocator"sv, std::string(GMalloc->GetName())}, + {"AsioVersion"sv, fmt::format("{}.{}.{}", ASIO_VERSION / 100000, (ASIO_VERSION / 100) % 1000, ASIO_VERSION % 100)}, {"IsDebug"sv, fmt::to_string(ServerConfig.IsDebug)}, {"IsCleanStart"sv, fmt::to_string(ServerConfig.IsCleanStart)}, - {"IsPowerCycle"sv, fmt::to_string(ServerConfig.IsPowerCycle)}, {"IsTest"sv, fmt::to_string(ServerConfig.IsTest)}, {"Detach"sv, fmt::to_string(ServerConfig.Detach)}, {"NoConsoleOutput"sv, fmt::to_string(ServerConfig.LoggingConfig.NoConsoleOutput)}, {"QuietConsole"sv, fmt::to_string(ServerConfig.LoggingConfig.QuietConsole)}, - {"CoreLimit"sv, fmt::to_string(ServerConfig.CoreLimit)}, - {"IsDedicated"sv, fmt::to_string(ServerConfig.IsDedicated)}, - {"ShouldCrash"sv, fmt::to_string(ServerConfig.ShouldCrash)}, {"ChildId"sv, ServerConfig.ChildId}, {"LogId"sv, ServerConfig.LoggingConfig.LogId}, {"Sentry DSN"sv, ServerConfig.SentryConfig.Dsn.empty() ? "not set" : ServerConfig.SentryConfig.Dsn}, @@ -423,10 +509,29 @@ ZenServerBase::LogSettingsSummary(const ZenServerConfig& ServerConfig) if (ServerConfig.StatsConfig.Enabled) { - Settings.emplace_back("Statsd Host", ServerConfig.StatsConfig.StatsdHost); - Settings.emplace_back("Statsd Port", fmt::to_string(ServerConfig.StatsConfig.StatsdPort)); + Settings.emplace_back("Statsd Host"sv, ServerConfig.StatsConfig.StatsdHost); + Settings.emplace_back("Statsd Port"sv, fmt::to_string(ServerConfig.StatsConfig.StatsdPort)); } + return Settings; +} + +void +ZenServerBase::LogSettingsSummary(const ZenServerConfig& ServerConfig) +{ + auto Settings = BuildSettingsList(ServerConfig); + + // Log-only entries not needed in RuntimeConfig + // clang-format off + Settings.insert(Settings.begin(), { + {"DataDir"sv, fmt::format("{}", ServerConfig.DataDir)}, + {"AbsLogFile"sv, fmt::format("{}", ServerConfig.LoggingConfig.AbsLogFile)}, + }); + // clang-format on + Settings.emplace_back("IsDedicated"sv, fmt::to_string(ServerConfig.IsDedicated)); + Settings.emplace_back("AllowPortProbing"sv, fmt::to_string(ServerConfig.AllowPortProbing)); + Settings.emplace_back("ShouldCrash"sv, fmt::to_string(ServerConfig.ShouldCrash)); + size_t MaxWidth = 0; for (const auto& Setting : Settings) { @@ -538,6 +643,10 @@ ZenServerMain::Run() uint32_t AttachSponsorProcessRetriesLeft = 3; ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(m_ServerOptions.BasePort); + // NOTE: ZEN_CONSOLE_WARN/INFO is used in this block and the lock file block below + // (instead of ZEN_WARN/INFO) because InitializeLogging() has not been called yet at + // this point. ZEN_WARN/INFO would silently discard messages before the logging system + // is initialized. while (Entry) { if (m_ServerOptions.OwnerPid) @@ -547,27 +656,29 @@ ZenServerMain::Run() { if (Ec) { - ZEN_WARN(ZEN_APP_NAME - " exiting, sponsor owner pid {} can not be checked for running state, reason: '{}'. Will not add sponsor " - "to process " - "listening to port {} (pid: {})", - m_ServerOptions.OwnerPid, - Ec.message(), - m_ServerOptions.BasePort, - Entry->Pid.load()); + ZEN_CONSOLE_WARN( + ZEN_APP_NAME + " exiting, sponsor owner pid {} can not be checked for running state, reason: '{}'. Will not add sponsor " + "to process " + "listening to port {} (pid: {})", + m_ServerOptions.OwnerPid, + Ec.message(), + m_ServerOptions.BasePort, + Entry->Pid.load()); } else { - ZEN_WARN(ZEN_APP_NAME - " exiting, sponsor owner pid {} is no longer running, will not add sponsor to process listening to port " - "{} (pid: {})", - m_ServerOptions.OwnerPid, - m_ServerOptions.BasePort, - Entry->Pid.load()); + ZEN_CONSOLE_WARN( + ZEN_APP_NAME + " exiting, sponsor owner pid {} is no longer running, will not add sponsor to process listening to port " + "{} (pid: {})", + m_ServerOptions.OwnerPid, + m_ServerOptions.BasePort, + Entry->Pid.load()); } std::exit(1); } - ZEN_INFO( + ZEN_CONSOLE_INFO( "Looks like there is already a process listening to this port {} (pid: {}), attaching owner pid {} to running instance", m_ServerOptions.BasePort, Entry->Pid.load(), @@ -585,18 +696,40 @@ ZenServerMain::Run() } else { - ZEN_WARN(ZEN_APP_NAME " exiting, failed to add sponsor owner pid {} to process listening to port {} (pid: {})", - m_ServerOptions.OwnerPid, - m_ServerOptions.BasePort, - Entry->Pid.load()); - std::exit(1); + // The entry's process failed to pick up our sponsor request after + // multiple attempts. Before reclaiming the entry, verify that the + // PID does not still belong to a zenserver process. If it does, the + // server is alive but unresponsive – fall back to the original error + // path. If the PID is gone or belongs to a different executable the + // entry is genuinely stale and safe to reclaim. + const int StalePid = Entry->Pid.load(); + std::error_code ExeEc; + std::filesystem::path PidExePath = GetProcessExecutablePath(StalePid, ExeEc); + const bool PidIsZenServer = !ExeEc && (PidExePath.filename() == GetRunningExecutablePath().filename()); + if (PidIsZenServer) + { + ZEN_CONSOLE_WARN(ZEN_APP_NAME + " exiting, failed to add sponsor to process on port {} " + "(pid {}); that pid is still a running zenserver instance", + m_ServerOptions.BasePort, + StalePid); + std::exit(1); + } + ZEN_CONSOLE_WARN( + "Failed to add sponsor to process on port {} (pid {}); " + "pid belongs to '{}' – assuming stale entry and reclaiming", + m_ServerOptions.BasePort, + StalePid, + ExeEc ? "<unknown>" : PidExePath.filename().string()); + Entry->Reset(); + Entry = nullptr; } } else { - ZEN_WARN(ZEN_APP_NAME " exiting, there is already a process listening to port {} (pid: {})", - m_ServerOptions.BasePort, - Entry->Pid.load()); + ZEN_CONSOLE_WARN(ZEN_APP_NAME " exiting, there is already a process listening to port {} (pid: {})", + m_ServerOptions.BasePort, + Entry->Pid.load()); std::exit(1); } } @@ -609,19 +742,19 @@ ZenServerMain::Run() if (Ec) { - ZEN_INFO(ZEN_APP_NAME " unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message()); + ZEN_CONSOLE_INFO(ZEN_APP_NAME " unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message()); Sleep(100); m_LockFile.Create(LockFilePath, MakeLockData(false), Ec); if (Ec) { - ZEN_INFO(ZEN_APP_NAME " unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message()); + ZEN_CONSOLE_INFO(ZEN_APP_NAME " unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message()); Sleep(500); m_LockFile.Create(LockFilePath, MakeLockData(false), Ec); if (Ec) { - ZEN_WARN(ZEN_APP_NAME " exiting, unable to grab lock at '{}' (reason: '{}')", LockFilePath, Ec.message()); + ZEN_CONSOLE_WARN(ZEN_APP_NAME " exiting, unable to grab lock at '{}' (reason: '{}')", LockFilePath, Ec.message()); std::exit(99); } } @@ -640,9 +773,31 @@ ZenServerMain::Run() ZEN_INFO(ZEN_APP_NAME " - using lock file at '{}'", LockFilePath); ZEN_INFO(ZEN_APP_NAME " - starting on port {}, version '{}'", m_ServerOptions.BasePort, ZEN_CFG_VERSION_BUILD_STRING_FULL); + ZEN_INFO(ZEN_APP_NAME " - memory allocator: {}", GMalloc->GetName()); + ZEN_INFO(ZEN_APP_NAME " - asio: {}.{}.{}", ASIO_VERSION / 100000, (ASIO_VERSION / 100) % 1000, ASIO_VERSION % 100); Entry = ServerState.Register(m_ServerOptions.BasePort); + if (!Entry) + { + throw std::runtime_error( + fmt::format("Failed to register server on port {} in shared state (all slots occupied)", m_ServerOptions.BasePort)); + } + + // Publish per-instance extended info (e.g. UDS path) via a small shared memory + // section keyed by SessionId so clients can discover it during Snapshot() enumeration. + { + InstanceInfoData InstanceData; + InstanceData.UnixSocketPath = m_ServerOptions.HttpConfig.UnixSocketPath; + m_InstanceInfo.Create(GetSessionId(), InstanceData); + Entry->SignalHasInstanceInfo(); + } + + if (m_ServerOptions.HttpConfig.NoNetwork) + { + Entry->SignalNoNetwork(); + } + if (m_ServerOptions.OwnerPid) { // We are adding a sponsor process to our own entry, can't wait for pick since the code is not run until later @@ -655,25 +810,29 @@ ZenServerMain::Run() } catch (const AssertException& AssertEx) { - ZEN_CRITICAL(ZEN_APP_NAME " caught assert exception in main for process {}: {}", - zen::GetCurrentProcessId(), - AssertEx.FullDescription()); + ZEN_CONSOLE_CRITICAL(ZEN_APP_NAME " caught assert exception in main for process {}: {}", + zen::GetCurrentProcessId(), + AssertEx.FullDescription()); RequestApplicationExit(1); } catch (const std::system_error& e) { - ZEN_CRITICAL(ZEN_APP_NAME " caught system error exception in main for process {}: {} ({})", - zen::GetCurrentProcessId(), - e.what(), - e.code().value()); + ZEN_CONSOLE_CRITICAL(ZEN_APP_NAME " caught system error exception in main for process {}: {} ({})", + zen::GetCurrentProcessId(), + e.what(), + e.code().value()); RequestApplicationExit(1); } catch (const std::exception& e) { - ZEN_CRITICAL(ZEN_APP_NAME " caught exception in main for process {}: {}", zen::GetCurrentProcessId(), e.what()); + ZEN_CONSOLE_CRITICAL(ZEN_APP_NAME " caught exception in main for process {}: {}", zen::GetCurrentProcessId(), e.what()); RequestApplicationExit(1); } +#if ZEN_USE_SENTRY + Sentry.Close(); +#endif + ShutdownServerLogging(); ReportServiceStatus(ServiceStatus::Stopped); @@ -699,7 +858,8 @@ ZenServerMain::MakeLockData(bool IsReady) .EffectiveListenPort = gsl::narrow<uint16_t>(m_ServerOptions.BasePort), .Ready = IsReady, .DataDir = m_ServerOptions.DataDir, - .ExecutablePath = GetRunningExecutablePath()}); + .ExecutablePath = GetRunningExecutablePath(), + .UnixSocketPath = m_ServerOptions.HttpConfig.UnixSocketPath}); }; } // namespace zen |