diff options
| author | Stefan Boberg <[email protected]> | 2025-10-13 12:01:55 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-10-13 12:01:55 +0200 |
| commit | d86ca9ac1c76e2841c44ede6b39930b6f934ef93 (patch) | |
| tree | ec2c6a936229d343c843ac5f1962cb2ba96c8db0 /src/zenserver/zenserver.cpp | |
| parent | hide http.sys options when unavailable (#568) (diff) | |
| download | zen-d86ca9ac1c76e2841c44ede6b39930b6f934ef93.tar.xz zen-d86ca9ac1c76e2841c44ede6b39930b6f934ef93.zip | |
move service common code into base class (#567)
Separates storage server code and generic server code into two classes.
This is a change to prepare for different services to be implemented using the same framework, into the same executable
Diffstat (limited to 'src/zenserver/zenserver.cpp')
| -rw-r--r-- | src/zenserver/zenserver.cpp | 642 |
1 files changed, 344 insertions, 298 deletions
diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp index 82ebc1711..0f53e657f 100644 --- a/src/zenserver/zenserver.cpp +++ b/src/zenserver/zenserver.cpp @@ -107,31 +107,22 @@ namespace utils { ////////////////////////////////////////////////////////////////////////// -ZenServer::ZenServer() +ZenServerBase::ZenServerBase() { } -ZenServer::~ZenServer() +ZenServerBase::~ZenServerBase() { } -void -ZenServer::OnReady() -{ - m_ServerEntry->SignalReady(); - - if (m_IsReadyFunc) - { - m_IsReadyFunc(); - } -} - int -ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry) +ZenServerBase::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry) { - ZEN_TRACE_CPU("ZenServer::Initialize"); - + ZEN_TRACE_CPU("ZenServerBase::Initialize"); ZEN_MEMSCOPE(GetZenserverTag()); + + m_IsPowerCycle = ServerOptions.IsPowerCycle; + const std::string MutexName = fmt::format("zen_{}", ServerOptions.BasePort); if (NamedMutex::Exists(MutexName)) @@ -140,14 +131,12 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen return -1; } - m_UseSentry = ServerOptions.SentryConfig.Disable == false; - m_ServerEntry = ServerEntry; - m_DebugOptionForcedCrash = ServerOptions.ShouldCrash; - m_IsPowerCycle = ServerOptions.IsPowerCycle; - const int ParentPid = ServerOptions.OwnerPid; - m_StartupScrubOptions = ServerOptions.ScrubOptions; + m_UseSentry = ServerOptions.SentryConfig.Disable == false; + m_ServerEntry = ServerEntry; + + // Initialize parent (sponsor) process monitoring - if (ParentPid) + if (const int ParentPid = ServerOptions.OwnerPid) { std::error_code Ec; ProcessHandle OwnerProcess; @@ -175,16 +164,7 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen ThrowLastError(fmt::format("Failed to create mutex '{}'", MutexName).c_str()); } - InitializeState(ServerOptions); - - m_JobQueue = MakeJobQueue(8, "bgjobs"); - - m_HealthService.SetHealthInfo({.DataRoot = m_DataRoot, - .AbsLogPath = ServerOptions.AbsLogFile, - .HttpServerClass = std::string(ServerOptions.HttpServerConfig.ServerClass), - .BuildVersion = std::string(ZEN_CFG_VERSION_BUILD_STRING_FULL)}); - - // Ok so now we're configured, let's kick things off + EnqueueSigIntTimer(); m_Http = CreateHttpServer(ServerOptions.HttpServerConfig); int EffectiveBasePort = m_Http->Initialize(ServerOptions.BasePort, ServerOptions.DataDir); @@ -197,56 +177,311 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen return -1; } - // Setup authentication manager + ZEN_INFO("Effective concurrency: {} (hw: {})", GetHardwareConcurrency(), std::thread::hardware_concurrency()); + + m_StatusService.RegisterHandler("status", *this); + m_Http->RegisterService(m_StatusService); + + m_StatsReporter.Initialize(ServerOptions.StatsConfig); + if (ServerOptions.StatsConfig.Enabled) { - ZEN_TRACE_CPU("Zenserver::InitAuth"); - std::string EncryptionKey = ServerOptions.EncryptionKey; + EnqueueStatsReportingTimer(); + } - if (EncryptionKey.empty()) + return EffectiveBasePort; +} + +void +ZenServerBase::Finalize() +{ + // 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::EnsureIoRunner() +{ + ZEN_MEMSCOPE(GetZenserverTag()); + if (!m_IoRunner.joinable()) + { + m_IoRunner = std::thread{[this] { + SetCurrentThreadName("timer_io"); + m_IoContext.run(); + }}; + } +} + +void +ZenServerBase::OnReady() +{ + if (m_ServerEntry) + { + m_ServerEntry->SignalReady(); + } + + if (m_IsReadyFunc) + { + m_IsReadyFunc(); + } +} + +void +ZenServerBase::RequestExit(int ExitCode) +{ + if (RequestApplicationExit(ExitCode)) + { + if (m_Http) { - EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456"; + m_Http->RequestExit(); + } + } +} - if (ServerOptions.IsDedicated) - { - ZEN_WARN("Using default encryption key for authentication state"); - } +std::string_view +ZenServerBase::ToString(ServerState Value) +{ + switch (Value) + { + case kInitializing: + return "initializing"sv; + case kRunning: + return "running"sv; + case kShuttingDown: + return "shutdown"sv; + default: + return "unknown"sv; + } +} + +void +ZenServerBase::EnqueueStatsReportingTimer() +{ + ZEN_MEMSCOPE(GetZenserverTag()); + m_StatsReportingTimer.expires_after(std::chrono::milliseconds(500)); + m_StatsReportingTimer.async_wait([this](const asio::error_code& Ec) { + if (!Ec) + { + m_StatsReporter.ReportStats(); + EnqueueStatsReportingTimer(); } + }); + EnsureIoRunner(); +} - std::string EncryptionIV = ServerOptions.EncryptionIV; +void +ZenServerBase::EnqueueProcessMonitorTimer() +{ + ZEN_MEMSCOPE(GetZenserverTag()); + m_PidCheckTimer.expires_after(std::chrono::seconds(1)); + m_PidCheckTimer.async_wait([this](const asio::error_code&) { CheckOwnerPid(); }); - if (EncryptionIV.empty()) + EnsureIoRunner(); +} + +void +ZenServerBase::CheckOwnerPid() +{ + bool IsRunning = UpdateProcessMonitor(); + + if (IsRunning) + { + m_FoundNoActiveSponsors = false; + EnqueueProcessMonitorTimer(); + } + else + { + // Delay exit one iteration to avoid race conditions where one process detaches + // and another attaches + if (m_FoundNoActiveSponsors) { - EncryptionIV = "0123456789abcdef"; + ZEN_INFO(ZEN_APP_NAME " exiting since sponsor processes are all gone"); + RequestExit(0); + } + else + { + m_FoundNoActiveSponsors = true; + EnqueueProcessMonitorTimer(); + } + } +} - if (ServerOptions.IsDedicated) +bool +ZenServerBase::UpdateProcessMonitor() +{ + if (m_ServerEntry) + { + // Pick up any new "owner" processes + std::set<uint32_t> AddedPids; + + for (auto& PidEntry : m_ServerEntry->SponsorPids) + { + if (uint32_t ThisPid = PidEntry.load(std::memory_order_relaxed)) { - ZEN_WARN("Using default encryption initialization vector for authentication state"); + if (PidEntry.compare_exchange_strong(ThisPid, 0)) + { + if (AddedPids.insert(ThisPid).second) + { + m_ProcessMonitor.AddPid(ThisPid); + + ZEN_INFO("added process with pid {} as a sponsor process", ThisPid); + } + } } } + } + return m_ProcessMonitor.IsRunning(); +} - m_AuthMgr = AuthMgr::Create({.RootDirectory = m_DataRoot / "auth", - .EncryptionKey = AesKey256Bit::FromString(EncryptionKey), - .EncryptionIV = AesIV128Bit::FromString(EncryptionIV)}); +void +ZenServerBase::EnqueueStateExitFlagTimer() +{ + ZEN_MEMSCOPE(GetZenserverTag()); + m_StateExitFlagTimer.expires_after(std::chrono::milliseconds(500)); + m_StateExitFlagTimer.async_wait([this](const asio::error_code&) { CheckStateExitFlag(); }); + EnsureIoRunner(); +} - for (const ZenOpenIdProviderConfig& OpenIdProvider : ServerOptions.AuthConfig.OpenIdProviders) - { - m_AuthMgr->AddOpenIdProvider({.Name = OpenIdProvider.Name, .Url = OpenIdProvider.Url, .ClientId = OpenIdProvider.ClientId}); - } +void +ZenServerBase::CheckStateExitFlag() +{ + if (m_ServerEntry && m_ServerEntry->IsShutdownRequested()) + { + RequestExit(0); + return; } + EnqueueStateExitFlagTimer(); +} - m_AuthService = std::make_unique<HttpAuthService>(*m_AuthMgr); +void +ZenServerBase::EnqueueSigIntTimer() +{ + ZEN_MEMSCOPE(GetZenserverTag()); + m_SigIntTimer.expires_after(std::chrono::milliseconds(500)); + m_SigIntTimer.async_wait([this](const asio::error_code&) { CheckSigInt(); }); + EnsureIoRunner(); +} - m_StatsReporter.Initialize(ServerOptions.StatsConfig); - if (ServerOptions.StatsConfig.Enabled) +void +ZenServerBase::CheckSigInt() +{ + if (utils::SignalCounter[SIGINT] > 0) { - EnqueueStatsReportingTimer(); + ZEN_INFO("SIGINT triggered (Ctrl+C) for process {}, exiting", zen::GetCurrentProcessId()); + RequestExit(128 + SIGINT); + return; + } + if (utils::SignalCounter[SIGTERM] > 0) + { + ZEN_INFO("SIGTERM triggered for process {}, exiting", zen::GetCurrentProcessId()); + RequestExit(128 + SIGTERM); + return; } + EnqueueSigIntTimer(); +} - m_StatusService.RegisterHandler("status", *this); +void +ZenServerBase::HandleStatusRequest(HttpServerRequest& Request) +{ + CbObjectWriter Cbo; + Cbo << "ok" << true; + Cbo << "state" << ToString(m_CurrentState); + Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); +} - ZEN_INFO("Effective concurrency: {} (hw: {})", GetHardwareConcurrency(), std::thread::hardware_concurrency()); +////////////////////////////////////////////////////////////////////////// + +ZenStorageServer::ZenStorageServer() +{ +} + +ZenStorageServer::~ZenStorageServer() +{ +} + +int +ZenStorageServer::Initialize(const ZenStorageServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry) +{ + ZEN_TRACE_CPU("ZenStorageServer::Initialize"); + ZEN_MEMSCOPE(GetZenserverTag()); + + const int EffectiveBasePort = ZenServerBase::Initialize(ServerOptions, ServerEntry); + if (EffectiveBasePort < 0) + { + return EffectiveBasePort; + } + + m_DebugOptionForcedCrash = ServerOptions.ShouldCrash; + m_StartupScrubOptions = ServerOptions.ScrubOptions; + + InitializeState(ServerOptions); + InitializeServices(ServerOptions); + RegisterServices(); + + ZenServerBase::Finalize(); + + return EffectiveBasePort; +} + +void +ZenStorageServer::RegisterServices() +{ + m_Http->RegisterService(*m_AuthService); + m_Http->RegisterService(m_StatsService); + m_Http->RegisterService(m_TestService); // NOTE: this is intentionally not limited to test mode as it's useful for diagnostics + +#if ZEN_WITH_TESTS + m_Http->RegisterService(m_TestingService); +#endif + + if (m_StructuredCacheService) + { + m_Http->RegisterService(*m_StructuredCacheService); + } + + if (m_UpstreamService) + { + m_Http->RegisterService(*m_UpstreamService); + } + + if (m_HttpProjectService) + { + m_Http->RegisterService(*m_HttpProjectService); + } + + if (m_HttpWorkspacesService) + { + m_Http->RegisterService(*m_HttpWorkspacesService); + } - // Initialize storage and services + m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot, m_StatusService); + + if (m_FrontendService) + { + m_Http->RegisterService(*m_FrontendService); + } + + if (m_ObjStoreService) + { + m_Http->RegisterService(*m_ObjStoreService); + } + + if (m_BuildStoreService) + { + m_Http->RegisterService(*m_BuildStoreService); + } + +#if ZEN_WITH_VFS + m_Http->RegisterService(*m_VfsService); +#endif // ZEN_WITH_VFS + + m_Http->RegisterService(*m_AdminService); +} + +void +ZenStorageServer::InitializeServices(const ZenStorageServerOptions& ServerOptions) +{ + InitializeAuthentication(ServerOptions); ZEN_INFO("initializing storage"); @@ -258,6 +493,8 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen ZEN_INFO("instantiating project service"); + m_JobQueue = MakeJobQueue(8, "bgjobs"); + m_ProjectStore = new ProjectStore(*m_CidStore, m_DataRoot / "projects", m_GcManager, ProjectStore::Configuration{}); m_HttpProjectService.reset( new HttpProjectService{*m_CidStore, m_ProjectStore, m_StatusService, m_StatsService, *m_AuthMgr, *m_OpenProcessCache, *m_JobQueue}); @@ -357,74 +594,55 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen .HttpLogPath = ServerOptions.DataDir / "logs" / "http.log", .CacheLogPath = ServerOptions.DataDir / "logs" / "z$.log"}, ServerOptions); +} - // Register all services when all initialization for all services are done - - m_Http->RegisterService(*m_AuthService); - - m_Http->RegisterService(m_StatsService); - m_Http->RegisterService(m_StatusService); - m_Http->RegisterService(m_TestService); // NOTE: this is intentionally not limited to test mode as it's useful for diagnostics - -#if ZEN_WITH_TESTS - m_Http->RegisterService(m_TestingService); -#endif - - if (m_StructuredCacheService) +void +ZenStorageServer::InitializeAuthentication(const ZenStorageServerOptions& ServerOptions) +{ + // Setup authentication manager { - m_Http->RegisterService(*m_StructuredCacheService); - } + ZEN_TRACE_CPU("ZenStorageServer::InitAuth"); + std::string EncryptionKey = ServerOptions.EncryptionKey; - if (m_UpstreamService) - { - m_Http->RegisterService(*m_UpstreamService); - } + if (EncryptionKey.empty()) + { + EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456"; - if (m_HttpProjectService) - { - m_Http->RegisterService(*m_HttpProjectService); - } + if (ServerOptions.IsDedicated) + { + ZEN_WARN("Using default encryption key for authentication state"); + } + } - if (m_HttpWorkspacesService) - { - m_Http->RegisterService(*m_HttpWorkspacesService); - } + std::string EncryptionIV = ServerOptions.EncryptionIV; - m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot, m_StatusService); + if (EncryptionIV.empty()) + { + EncryptionIV = "0123456789abcdef"; - if (m_FrontendService) - { - m_Http->RegisterService(*m_FrontendService); - } + if (ServerOptions.IsDedicated) + { + ZEN_WARN("Using default encryption initialization vector for authentication state"); + } + } - if (m_ObjStoreService) - { - m_Http->RegisterService(*m_ObjStoreService); - } + m_AuthMgr = AuthMgr::Create({.RootDirectory = m_DataRoot / "auth", + .EncryptionKey = AesKey256Bit::FromString(EncryptionKey), + .EncryptionIV = AesIV128Bit::FromString(EncryptionIV)}); - if (m_BuildStoreService) - { - m_Http->RegisterService(*m_BuildStoreService); + for (const ZenOpenIdProviderConfig& OpenIdProvider : ServerOptions.AuthConfig.OpenIdProviders) + { + m_AuthMgr->AddOpenIdProvider({.Name = OpenIdProvider.Name, .Url = OpenIdProvider.Url, .ClientId = OpenIdProvider.ClientId}); + } } -#if ZEN_WITH_VFS - m_Http->RegisterService(*m_VfsService); -#endif // ZEN_WITH_VFS - - m_Http->RegisterService(*m_AdminService); - - // Register health service last so if we return "OK" for health it means all services have been properly initialized - m_Http->RegisterService(m_HealthService); - - return EffectiveBasePort; + m_AuthService = std::make_unique<HttpAuthService>(*m_AuthMgr); } void -ZenServer::InitializeState(const ZenServerOptions& ServerOptions) +ZenStorageServer::InitializeState(const ZenStorageServerOptions& ServerOptions) { - ZEN_TRACE_CPU("ZenServer::InitializeState"); - - EnqueueSigIntTimer(); + ZEN_TRACE_CPU("ZenStorageServer::InitializeState"); // Check root manifest to deal with schema versioning @@ -586,9 +804,9 @@ ZenServer::InitializeState(const ZenServerOptions& ServerOptions) } void -ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) +ZenStorageServer::InitializeStructuredCache(const ZenStorageServerOptions& ServerOptions) { - ZEN_TRACE_CPU("ZenServer::InitializeStructuredCache"); + ZEN_TRACE_CPU("ZenStorageServer::InitializeStructuredCache"); using namespace std::literals; @@ -718,7 +936,7 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) } void -ZenServer::Run() +ZenStorageServer::Run() { if (m_ProcessMonitor.IsActive()) { @@ -841,21 +1059,9 @@ ZenServer::Run() } void -ZenServer::RequestExit(int ExitCode) +ZenStorageServer::Cleanup() { - if (RequestApplicationExit(ExitCode)) - { - if (m_Http) - { - m_Http->RequestExit(); - } - } -} - -void -ZenServer::Cleanup() -{ - ZEN_TRACE_CPU("ZenServer::Cleanup"); + ZEN_TRACE_CPU("ZenStorageServer::Cleanup"); ZEN_INFO(ZEN_APP_NAME " cleaning up"); try { @@ -869,6 +1075,7 @@ ZenServer::Cleanup() { m_Http->Close(); } + if (m_JobQueue) { m_JobQueue->Stop(); @@ -915,72 +1122,16 @@ ZenServer::Cleanup() } void -ZenServer::EnsureIoRunner() -{ - ZEN_MEMSCOPE(GetZenserverTag()); - if (!m_IoRunner.joinable()) - { - m_IoRunner = std::thread{[this] { - SetCurrentThreadName("timer_io"); - m_IoContext.run(); - }}; - } -} - -void -ZenServer::EnqueueProcessMonitorTimer() -{ - ZEN_MEMSCOPE(GetZenserverTag()); - m_PidCheckTimer.expires_after(std::chrono::seconds(1)); - m_PidCheckTimer.async_wait([this](const asio::error_code&) { CheckOwnerPid(); }); - - EnsureIoRunner(); -} - -void -ZenServer::EnqueueStateMarkerTimer() -{ - ZEN_MEMSCOPE(GetZenserverTag()); - m_StateMakerTimer.expires_after(std::chrono::seconds(5)); - m_StateMakerTimer.async_wait([this](const asio::error_code&) { CheckStateMarker(); }); - EnsureIoRunner(); -} - -void -ZenServer::EnqueueSigIntTimer() -{ - ZEN_MEMSCOPE(GetZenserverTag()); - m_SigIntTimer.expires_after(std::chrono::milliseconds(500)); - m_SigIntTimer.async_wait([this](const asio::error_code&) { CheckSigInt(); }); - EnsureIoRunner(); -} - -void -ZenServer::EnqueueStateExitFlagTimer() -{ - ZEN_MEMSCOPE(GetZenserverTag()); - m_StateExitFlagTimer.expires_after(std::chrono::milliseconds(500)); - m_StateExitFlagTimer.async_wait([this](const asio::error_code&) { CheckStateExitFlag(); }); - EnsureIoRunner(); -} - -void -ZenServer::EnqueueStatsReportingTimer() +ZenStorageServer::EnqueueStateMarkerTimer() { ZEN_MEMSCOPE(GetZenserverTag()); - m_StatsReportingTimer.expires_after(std::chrono::milliseconds(500)); - m_StatsReportingTimer.async_wait([this](const asio::error_code& Ec) { - if (!Ec) - { - m_StatsReporter.ReportStats(); - EnqueueStatsReportingTimer(); - } - }); + m_StateMarkerTimer.expires_after(std::chrono::seconds(5)); + m_StateMarkerTimer.async_wait([this](const asio::error_code&) { CheckStateMarker(); }); EnsureIoRunner(); } void -ZenServer::CheckStateMarker() +ZenStorageServer::CheckStateMarker() { ZEN_MEMSCOPE(GetZenserverTag()); std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker"; @@ -1003,89 +1154,9 @@ ZenServer::CheckStateMarker() } void -ZenServer::CheckSigInt() -{ - if (utils::SignalCounter[SIGINT] > 0) - { - ZEN_INFO("SIGINT triggered (Ctrl+C) for process {}, exiting", zen::GetCurrentProcessId()); - RequestExit(128 + SIGINT); - return; - } - if (utils::SignalCounter[SIGTERM] > 0) - { - ZEN_INFO("SIGTERM triggered for process {}, exiting", zen::GetCurrentProcessId()); - RequestExit(128 + SIGTERM); - return; - } - EnqueueSigIntTimer(); -} - -void -ZenServer::CheckStateExitFlag() -{ - if (m_ServerEntry && m_ServerEntry->IsShutdownRequested()) - { - RequestExit(0); - return; - } - EnqueueStateExitFlagTimer(); -} - -bool -ZenServer::UpdateProcessMonitor() -{ - // Pick up any new "owner" processes - std::set<uint32_t> AddedPids; - - for (auto& PidEntry : m_ServerEntry->SponsorPids) - { - if (uint32_t ThisPid = PidEntry.load(std::memory_order_relaxed)) - { - if (PidEntry.compare_exchange_strong(ThisPid, 0)) - { - if (AddedPids.insert(ThisPid).second) - { - m_ProcessMonitor.AddPid(ThisPid); - - ZEN_INFO("added process with pid {} as a sponsor process", ThisPid); - } - } - } - } - return m_ProcessMonitor.IsRunning(); -} - -void -ZenServer::CheckOwnerPid() -{ - bool IsRunning = UpdateProcessMonitor(); - - if (IsRunning) - { - m_FoundNoActiveSponsors = false; - EnqueueProcessMonitorTimer(); - } - else - { - // Delay exit one iteration to avoid race conditions where one process detaches - // and another attaches - if (m_FoundNoActiveSponsors) - { - ZEN_INFO(ZEN_APP_NAME " exiting since sponsor processes are all gone"); - RequestExit(0); - } - else - { - m_FoundNoActiveSponsors = true; - EnqueueProcessMonitorTimer(); - } - } -} - -void -ZenServer::Flush() +ZenStorageServer::Flush() { - ZEN_TRACE_CPU("ZenServer::Flush"); + ZEN_TRACE_CPU("ZenStorageServer::Flush"); if (m_CidStore) m_CidStore->Flush(); @@ -1100,31 +1171,6 @@ ZenServer::Flush() m_BuildCidStore->Flush(); } -void -ZenServer::HandleStatusRequest(HttpServerRequest& Request) -{ - CbObjectWriter Cbo; - Cbo << "ok" << true; - Cbo << "state" << ToString(m_CurrentState); - Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); -} - -std::string_view -ZenServer::ToString(ServerState Value) -{ - switch (Value) - { - case kInitializing: - return "initializing"sv; - case kRunning: - return "running"sv; - case kShuttingDown: - return "shutdown"sv; - default: - return "unknown"sv; - } -} - #if ZEN_WITH_TESTS void |