aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-10-13 13:42:25 +0200
committerGitHub Enterprise <[email protected]>2025-10-13 13:42:25 +0200
commite5a05c01ba5f1b9517108fe95d54b4066190d66d (patch)
tree591d18ecf12753104ee05f868c1155b0a7941e4a /src/zenserver
parentrefactor builds cmd (#566) (diff)
downloadzen-e5a05c01ba5f1b9517108fe95d54b4066190d66d.tar.xz
zen-e5a05c01ba5f1b9517108fe95d54b4066190d66d.zip
extract storage server into separate source files (#569)
this change moves common base service code into `zenserver.(cpp|h)` and storage server code into `zenstorageserver.(cpp|h)` to more clearly separate concerns but also to make way for another service
Diffstat (limited to 'src/zenserver')
-rw-r--r--src/zenserver/main.cpp347
-rw-r--r--src/zenserver/zenserver.cpp889
-rw-r--r--src/zenserver/zenserver.h111
-rw-r--r--src/zenserver/zenstorageserver.cpp961
-rw-r--r--src/zenserver/zenstorageserver.h113
5 files changed, 1236 insertions, 1185 deletions
diff --git a/src/zenserver/main.cpp b/src/zenserver/main.cpp
index 0dc390797..f1c1a9394 100644
--- a/src/zenserver/main.cpp
+++ b/src/zenserver/main.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "zenserver.h"
+#include "zenstorageserver.h"
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinaryvalidation.h>
@@ -59,342 +59,13 @@ SignalCallbackHandler(int SigNum)
namespace zen {
-static const FLLMTag&
-GetZenserverTag()
-{
- static FLLMTag _("zenserver");
-
- return _;
-}
-
using namespace std::literals;
-////////////////////////////////////////////////////////////////////////////////
-
-class ZenServerMain
-{
-public:
- ZenServerMain(ZenServerOptions& ServerOptions);
- ~ZenServerMain();
-
- int Run();
-
- ZenServerMain(const ZenServerMain&) = delete;
- ZenServerMain& operator=(const ZenServerMain&) = delete;
-
-protected:
- ZenServerOptions& m_ServerOptions;
- LockFile m_LockFile;
-
- virtual void DoRun(ZenServerState::ZenServerEntry* Entry) = 0;
-
- void NotifyReady()
- {
- if (!m_ServerOptions.ChildId.empty())
- {
- NamedEvent ParentEvent{m_ServerOptions.ChildId};
- ParentEvent.Set();
- }
- }
-
- CbObject MakeLockData(bool IsReady)
- {
- return MakeLockFilePayload({.Pid = GetCurrentProcessId(),
- .SessionId = GetSessionId(),
- .EffectiveListenPort = gsl::narrow<uint16_t>(m_ServerOptions.BasePort),
- .Ready = IsReady,
- .DataDir = m_ServerOptions.DataDir,
- .ExecutablePath = GetRunningExecutablePath()});
- };
-};
-
-ZenServerMain::ZenServerMain(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions)
-{
-}
-
-ZenServerMain::~ZenServerMain()
-{
-}
-
-int
-ZenServerMain::Run()
-{
- // On Linux this has the unfortunate side effect of making `top` and other tools display
- // `main` as the program name since threads and processes have a closer relationship
- // there. So we don't name the main thread explicitly there.
-
-#ifndef ZEN_PLATFORM_LINUX
- zen::SetCurrentThreadName("main");
-#endif
-
-#if ZEN_USE_SENTRY
- SentryIntegration Sentry;
-
- if (m_ServerOptions.SentryConfig.Disable == false)
- {
- std::string SentryDatabasePath = (m_ServerOptions.DataDir / ".sentry-native").string();
- std::string SentryAttachmentPath = m_ServerOptions.AbsLogFile.string();
-
- Sentry.Initialize({.DatabasePath = SentryDatabasePath,
- .AttachmentsPath = SentryAttachmentPath,
- .Dsn = m_ServerOptions.SentryConfig.Dsn,
- .Environment = m_ServerOptions.SentryConfig.Environment,
- .AllowPII = m_ServerOptions.SentryConfig.AllowPII,
- .Debug = m_ServerOptions.SentryConfig.Debug},
- m_ServerOptions.CommandLine);
- }
-#endif
-
- try
- {
- // Mutual exclusion and synchronization
- ZenServerState ServerState;
- ServerState.Initialize();
- ServerState.Sweep();
-
- uint32_t AttachSponsorProcessRetriesLeft = 3;
- ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(m_ServerOptions.BasePort);
- while (Entry)
- {
- if (m_ServerOptions.OwnerPid)
- {
- std::error_code Ec;
- if (!IsProcessRunning(m_ServerOptions.OwnerPid, Ec))
- {
- 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());
- }
- 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());
- }
- std::exit(1);
- }
- ZEN_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(),
- m_ServerOptions.OwnerPid);
-
- // Sponsor processes are checked every second, so 2 second wait time should be enough
- if (Entry->AddSponsorProcess(m_ServerOptions.OwnerPid, 2000))
- {
- NotifyReady();
- std::exit(0);
- }
- if (AttachSponsorProcessRetriesLeft-- > 0)
- {
- Entry = ServerState.Lookup(m_ServerOptions.BasePort);
- }
- 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);
- }
- }
- else
- {
- ZEN_WARN(ZEN_APP_NAME " exiting, there is already a process listening to port {} (pid: {})",
- m_ServerOptions.BasePort,
- Entry->Pid.load());
- std::exit(1);
- }
- }
-
- std::error_code Ec;
-
- std::filesystem::path LockFilePath = m_ServerOptions.DataDir / ".lock";
-
- m_LockFile.Create(LockFilePath, MakeLockData(false), Ec);
-
- if (Ec)
- {
- ZEN_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());
- Sleep(500);
- if (Ec)
- {
- ZEN_WARN(ZEN_APP_NAME " exiting, unable to grab lock at '{}' (reason: '{}')", LockFilePath, Ec.message());
- std::exit(99);
- }
- }
- }
-
- InitializeServerLogging(m_ServerOptions);
-
- ZEN_INFO("Command line: {}", m_ServerOptions.CommandLine);
-
-#if ZEN_USE_SENTRY
- Sentry.LogStartupInformation();
-#endif
-
- MaximizeOpenFileCount();
-
- 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);
-
- Entry = ServerState.Register(m_ServerOptions.BasePort);
-
- 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
- Entry->AddSponsorProcess(m_ServerOptions.OwnerPid, 0);
- }
-
- // Run the actual application logic
-
- DoRun(Entry);
- }
- catch (const AssertException& AssertEx)
- {
- ZEN_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());
- RequestApplicationExit(1);
- }
- catch (const std::exception& e)
- {
- ZEN_CRITICAL(ZEN_APP_NAME " caught exception in main for process {}: {}", zen::GetCurrentProcessId(), e.what());
- RequestApplicationExit(1);
- }
-
- ShutdownServerLogging();
-
- ReportServiceStatus(ServiceStatus::Stopped);
-
- return ApplicationExitCode();
-}
-
-//////////////////////////////////////////////////////////////////////////
-class ZenStorageServerMain : public ZenServerMain
-{
-public:
- ZenStorageServerMain(ZenStorageServerOptions& ServerOptions);
- virtual void DoRun(ZenServerState::ZenServerEntry* Entry) override;
-
- ZenStorageServerMain(const ZenStorageServerMain&) = delete;
- ZenStorageServerMain& operator=(const ZenStorageServerMain&) = delete;
-
-private:
- ZenStorageServerOptions& m_ServerOptions;
-};
-
-ZenStorageServerMain::ZenStorageServerMain(ZenStorageServerOptions& ServerOptions)
-: ZenServerMain(ServerOptions)
-, m_ServerOptions(ServerOptions)
-{
-}
-
-void
-ZenStorageServerMain::DoRun(ZenServerState::ZenServerEntry* Entry)
-{
- ZenStorageServer Server;
- Server.SetDataRoot(m_ServerOptions.DataDir);
- Server.SetContentRoot(m_ServerOptions.ContentDir);
- Server.SetTestMode(m_ServerOptions.IsTest);
- Server.SetDedicatedMode(m_ServerOptions.IsDedicated);
-
- auto ServerCleanup = MakeGuard([&Server] { Server.Cleanup(); });
-
- int EffectiveBasePort = Server.Initialize(m_ServerOptions, Entry);
- if (EffectiveBasePort == -1)
- {
- // Server.Initialize has already logged what the issue is - just exit with failure code here.
- std::exit(1);
- }
-
- Entry->EffectiveListenPort = uint16_t(EffectiveBasePort);
- if (EffectiveBasePort != m_ServerOptions.BasePort)
- {
- ZEN_INFO(ZEN_APP_NAME " - relocated to base port {}", EffectiveBasePort);
- m_ServerOptions.BasePort = EffectiveBasePort;
- }
-
- std::unique_ptr<std::thread> ShutdownThread;
- std::unique_ptr<NamedEvent> ShutdownEvent;
-
- ExtendableStringBuilder<64> ShutdownEventName;
- ShutdownEventName << "Zen_" << m_ServerOptions.BasePort << "_Shutdown";
- ShutdownEvent.reset(new NamedEvent{ShutdownEventName});
-
- // Monitor shutdown signals
-
- ShutdownThread.reset(new std::thread{[&] {
- SetCurrentThreadName("shutdown_monitor");
-
- ZEN_INFO("shutdown monitor thread waiting for shutdown signal '{}' for process {}", ShutdownEventName, zen::GetCurrentProcessId());
-
- if (ShutdownEvent->Wait())
- {
- ZEN_INFO("shutdown signal for pid {} received", zen::GetCurrentProcessId());
- Server.RequestExit(0);
- }
- else
- {
- ZEN_INFO("shutdown signal wait() failed");
- }
- }});
-
- auto CleanupShutdown = MakeGuard([&ShutdownEvent, &ShutdownThread] {
- ReportServiceStatus(ServiceStatus::Stopping);
-
- if (ShutdownEvent)
- {
- ShutdownEvent->Set();
- }
- if (ShutdownThread && ShutdownThread->joinable())
- {
- ShutdownThread->join();
- }
- });
-
- // If we have a parent process, establish the mechanisms we need
- // to be able to communicate readiness with the parent
-
- Server.SetIsReadyFunc([&] {
- std::error_code Ec;
- m_LockFile.Update(MakeLockData(true), Ec);
- ReportServiceStatus(ServiceStatus::Running);
- NotifyReady();
- });
-
- Server.Run();
-}
-
//////////////////////////////////////////////////////////////////////////
#if ZEN_PLATFORM_WINDOWS
+template<class T>
class ZenWindowsService : public WindowsService
{
public:
@@ -403,18 +74,12 @@ public:
ZenWindowsService(const ZenWindowsService&) = delete;
ZenWindowsService& operator=(const ZenWindowsService&) = delete;
- virtual int Run() override;
+ virtual int Run() override { return m_EntryPoint.Run(); }
private:
- ZenStorageServerMain m_EntryPoint;
+ T m_EntryPoint;
};
-int
-ZenWindowsService::Run()
-{
- return m_EntryPoint.Run();
-}
-
#endif // ZEN_PLATFORM_WINDOWS
} // namespace zen
@@ -425,8 +90,6 @@ ZenWindowsService::Run()
int
test_main(int argc, char** argv)
{
- zen::zenserver_forcelinktests();
-
zen::logging::InitializeLogging();
zen::logging::SetLogLevel(zen::logging::level::Debug);
@@ -537,7 +200,7 @@ main(int argc, char* argv[])
std::exit(0);
}
- ZenWindowsService App(ServerOptions);
+ ZenWindowsService<ZenStorageServerMain> App(ServerOptions);
return App.ServiceMain();
#else
if (ServerOptions.InstallService || ServerOptions.UninstallService)
diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp
index 0f53e657f..bf59658c8 100644
--- a/src/zenserver/zenserver.cpp
+++ b/src/zenserver/zenserver.cpp
@@ -22,12 +22,7 @@
#include <zencore/trace.h>
#include <zencore/workthreadpool.h>
#include <zenhttp/httpserver.h>
-#include <zenremotestore/jupiter/jupiterclient.h>
-#include <zenstore/buildstore/buildstore.h>
-#include <zenstore/cidstore.h>
-#include <zenstore/scrubcontext.h>
-#include <zenstore/vfsimpl.h>
-#include <zenstore/workspaces.h>
+#include <zenutil/service.h>
#include <zenutil/workerpools.h>
#include <zenutil/zenserverprocess.h>
@@ -60,7 +55,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
-static const FLLMTag&
+const FLLMTag&
GetZenserverTag()
{
static FLLMTag _("zenserver");
@@ -74,37 +69,6 @@ namespace utils {
using namespace std::literals;
-namespace utils {
- asio::error_code ResolveHostname(asio::io_context& Ctx,
- std::string_view Host,
- std::string_view DefaultPort,
- std::vector<std::string>& OutEndpoints)
- {
- std::string_view Port = DefaultPort;
-
- if (const size_t Idx = Host.find(":"); Idx != std::string_view::npos)
- {
- Port = Host.substr(Idx + 1);
- Host = Host.substr(0, Idx);
- }
-
- asio::ip::tcp::resolver Resolver(Ctx);
-
- asio::error_code ErrorCode;
- asio::ip::tcp::resolver::results_type Endpoints = Resolver.resolve(Host, Port, ErrorCode);
-
- if (!ErrorCode)
- {
- for (const asio::ip::tcp::endpoint Ep : Endpoints)
- {
- OutEndpoints.push_back(fmt::format("http://{}:{}", Ep.address().to_string(), Ep.port()));
- }
- }
-
- return ErrorCode;
- }
-} // namespace utils
-
//////////////////////////////////////////////////////////////////////////
ZenServerBase::ZenServerBase()
@@ -391,793 +355,212 @@ ZenServerBase::HandleStatusRequest(HttpServerRequest& Request)
//////////////////////////////////////////////////////////////////////////
-ZenStorageServer::ZenStorageServer()
+ZenServerMain::ZenServerMain(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions)
{
}
-ZenStorageServer::~ZenStorageServer()
+ZenServerMain::~ZenServerMain()
{
}
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()
+ZenServerMain::Run()
{
- 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
+ // On Linux this has the unfortunate side effect of making `top` and other tools display
+ // `main` as the program name since threads and processes have a closer relationship
+ // there. So we don't name the main thread explicitly there.
-#if ZEN_WITH_TESTS
- m_Http->RegisterService(m_TestingService);
+#ifndef ZEN_PLATFORM_LINUX
+ zen::SetCurrentThreadName("main");
#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);
- }
-
- 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");
-
- CidStoreConfiguration Config;
- Config.RootDirectory = m_DataRoot / "cas";
-
- m_CidStore = std::make_unique<CidStore>(m_GcManager);
- m_CidStore->Initialize(Config);
-
- 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});
-
- if (ServerOptions.WorksSpacesConfig.Enabled)
- {
- m_Workspaces.reset(new Workspaces());
- m_HttpWorkspacesService.reset(
- new HttpWorkspacesService(m_StatusService,
- m_StatsService,
- {.SystemRootDir = ServerOptions.SystemRootDir,
- .AllowConfigurationChanges = ServerOptions.WorksSpacesConfig.AllowConfigurationChanges},
- *m_Workspaces));
- }
-
- if (ServerOptions.BuildStoreConfig.Enabled)
- {
- CidStoreConfiguration BuildCidConfig;
- BuildCidConfig.RootDirectory = m_DataRoot / "builds_cas";
- m_BuildCidStore = std::make_unique<CidStore>(m_GcManager);
- m_BuildCidStore->Initialize(BuildCidConfig);
-
- BuildStoreConfig BuildsCfg;
- BuildsCfg.RootDirectory = m_DataRoot / "builds";
- BuildsCfg.MaxDiskSpaceLimit = ServerOptions.BuildStoreConfig.MaxDiskSpaceLimit;
- m_BuildStore = std::make_unique<BuildStore>(std::move(BuildsCfg), m_GcManager, *m_BuildCidStore);
- }
-
- if (ServerOptions.StructuredCacheConfig.Enabled)
- {
- InitializeStructuredCache(ServerOptions);
- }
- else
- {
- ZEN_INFO("NOT instantiating structured cache service");
- }
-
- if (ServerOptions.ObjectStoreEnabled)
- {
- ObjectStoreConfig ObjCfg;
- ObjCfg.RootDirectory = m_DataRoot / "obj";
-
- for (const auto& Bucket : ServerOptions.ObjectStoreConfig.Buckets)
- {
- ObjectStoreConfig::BucketConfig NewBucket{.Name = Bucket.Name};
- NewBucket.Directory = Bucket.Directory.empty() ? (ObjCfg.RootDirectory / Bucket.Name) : Bucket.Directory;
- ObjCfg.Buckets.push_back(std::move(NewBucket));
- }
-
- m_ObjStoreService = std::make_unique<HttpObjectStoreService>(m_StatusService, std::move(ObjCfg));
- }
-
- if (ServerOptions.BuildStoreConfig.Enabled)
- {
- m_BuildStoreService = std::make_unique<HttpBuildStoreService>(m_StatusService, m_StatsService, *m_BuildStore);
- }
-
-#if ZEN_WITH_VFS
- m_VfsServiceImpl = std::make_unique<VfsServiceImpl>();
- m_VfsServiceImpl->AddService(Ref<ProjectStore>(m_ProjectStore));
- m_VfsServiceImpl->AddService(Ref<ZenCacheStore>(m_CacheStore));
-
- m_VfsService = std::make_unique<VfsService>(m_StatusService, m_VfsServiceImpl.get());
-#endif // ZEN_WITH_VFS
-
- ZEN_INFO("initializing GC, enabled '{}', interval {}, lightweight interval {}",
- ServerOptions.GcConfig.Enabled,
- NiceTimeSpanMs(ServerOptions.GcConfig.IntervalSeconds * 1000ull),
- NiceTimeSpanMs(ServerOptions.GcConfig.LightweightIntervalSeconds * 1000ull));
-
- GcSchedulerConfig GcConfig{.RootDirectory = m_DataRoot / "gc",
- .MonitorInterval = std::chrono::seconds(ServerOptions.GcConfig.MonitorIntervalSeconds),
- .Interval = std::chrono::seconds(ServerOptions.GcConfig.IntervalSeconds),
- .MaxCacheDuration = std::chrono::seconds(ServerOptions.GcConfig.Cache.MaxDurationSeconds),
- .MaxProjectStoreDuration = std::chrono::seconds(ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds),
- .MaxBuildStoreDuration = std::chrono::seconds(ServerOptions.GcConfig.BuildStore.MaxDurationSeconds),
- .CollectSmallObjects = ServerOptions.GcConfig.CollectSmallObjects,
- .Enabled = ServerOptions.GcConfig.Enabled,
- .DiskReserveSize = ServerOptions.GcConfig.DiskReserveSize,
- .DiskSizeSoftLimit = ServerOptions.GcConfig.DiskSizeSoftLimit,
- .MinimumFreeDiskSpaceToAllowWrites = ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites,
- .LightweightInterval = std::chrono::seconds(ServerOptions.GcConfig.LightweightIntervalSeconds),
- .UseGCVersion = ServerOptions.GcConfig.UseGCV2 ? GcVersion::kV2 : GcVersion::kV1_Deprecated,
- .CompactBlockUsageThresholdPercent = ServerOptions.GcConfig.CompactBlockUsageThresholdPercent,
- .Verbose = ServerOptions.GcConfig.Verbose,
- .SingleThreaded = ServerOptions.GcConfig.SingleThreaded,
- .AttachmentPassCount = ServerOptions.GcConfig.AttachmentPassCount};
- m_GcScheduler.Initialize(GcConfig);
-
- // Create and register admin interface last to make sure all is properly initialized
- m_AdminService = std::make_unique<HttpAdminService>(
- m_GcScheduler,
- *m_JobQueue,
- m_CacheStore.Get(),
- [this]() { Flush(); },
- HttpAdminService::LogPaths{.AbsLogPath = ServerOptions.AbsLogFile,
- .HttpLogPath = ServerOptions.DataDir / "logs" / "http.log",
- .CacheLogPath = ServerOptions.DataDir / "logs" / "z$.log"},
- ServerOptions);
-}
+#if ZEN_USE_SENTRY
+ SentryIntegration Sentry;
-void
-ZenStorageServer::InitializeAuthentication(const ZenStorageServerOptions& ServerOptions)
-{
- // Setup authentication manager
+ if (m_ServerOptions.SentryConfig.Disable == false)
{
- ZEN_TRACE_CPU("ZenStorageServer::InitAuth");
- std::string EncryptionKey = ServerOptions.EncryptionKey;
-
- if (EncryptionKey.empty())
- {
- EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456";
-
- if (ServerOptions.IsDedicated)
- {
- ZEN_WARN("Using default encryption key for authentication state");
- }
- }
-
- std::string EncryptionIV = ServerOptions.EncryptionIV;
-
- if (EncryptionIV.empty())
- {
- EncryptionIV = "0123456789abcdef";
-
- if (ServerOptions.IsDedicated)
- {
- ZEN_WARN("Using default encryption initialization vector for authentication state");
- }
- }
-
- m_AuthMgr = AuthMgr::Create({.RootDirectory = m_DataRoot / "auth",
- .EncryptionKey = AesKey256Bit::FromString(EncryptionKey),
- .EncryptionIV = AesIV128Bit::FromString(EncryptionIV)});
-
- for (const ZenOpenIdProviderConfig& OpenIdProvider : ServerOptions.AuthConfig.OpenIdProviders)
- {
- m_AuthMgr->AddOpenIdProvider({.Name = OpenIdProvider.Name, .Url = OpenIdProvider.Url, .ClientId = OpenIdProvider.ClientId});
- }
- }
-
- m_AuthService = std::make_unique<HttpAuthService>(*m_AuthMgr);
-}
-
-void
-ZenStorageServer::InitializeState(const ZenStorageServerOptions& ServerOptions)
-{
- ZEN_TRACE_CPU("ZenStorageServer::InitializeState");
-
- // Check root manifest to deal with schema versioning
+ std::string SentryDatabasePath = (m_ServerOptions.DataDir / ".sentry-native").string();
+ std::string SentryAttachmentPath = m_ServerOptions.AbsLogFile.string();
- bool WipeState = false;
- std::string WipeReason = "Unspecified";
-
- if (ServerOptions.IsCleanStart)
- {
- WipeState = true;
- WipeReason = "clean start requested";
+ Sentry.Initialize({.DatabasePath = SentryDatabasePath,
+ .AttachmentsPath = SentryAttachmentPath,
+ .Dsn = m_ServerOptions.SentryConfig.Dsn,
+ .Environment = m_ServerOptions.SentryConfig.Environment,
+ .AllowPII = m_ServerOptions.SentryConfig.AllowPII,
+ .Debug = m_ServerOptions.SentryConfig.Debug},
+ m_ServerOptions.CommandLine);
}
+#endif
- bool UpdateManifest = false;
- std::filesystem::path ManifestPath = m_DataRoot / "root_manifest";
- Oid StateId = Oid::Zero;
- DateTime CreatedWhen{0};
-
- if (!WipeState)
+ try
{
- FileContents ManifestData = ReadFile(ManifestPath);
+ // Mutual exclusion and synchronization
+ ZenServerState ServerState;
+ ServerState.Initialize();
+ ServerState.Sweep();
- if (ManifestData.ErrorCode)
+ uint32_t AttachSponsorProcessRetriesLeft = 3;
+ ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(m_ServerOptions.BasePort);
+ while (Entry)
{
- if (ServerOptions.IsFirstRun)
+ if (m_ServerOptions.OwnerPid)
{
- ZEN_INFO("Initializing state at '{}'", m_DataRoot);
-
- UpdateManifest = true;
- }
- else
- {
- WipeState = true;
- WipeReason = fmt::format("No manifest present at '{}'", ManifestPath);
- }
- }
- else
- {
- IoBuffer Manifest = ManifestData.Flatten();
-
- if (CbValidateError ValidationResult = ValidateCompactBinary(Manifest, CbValidateMode::All);
- ValidationResult != CbValidateError::None)
- {
- ZEN_WARN("Manifest validation failed: {}, state will be wiped", zen::ToString(ValidationResult));
-
- WipeState = true;
- WipeReason = fmt::format("Validation of manifest at '{}' failed: {}", ManifestPath, zen::ToString(ValidationResult));
- }
- else
- {
- m_RootManifest = LoadCompactBinaryObject(Manifest);
-
- const int32_t ManifestVersion = m_RootManifest["schema_version"].AsInt32(0);
- StateId = m_RootManifest["state_id"].AsObjectId();
- CreatedWhen = m_RootManifest["created"].AsDateTime();
-
- if (ManifestVersion != ZEN_CFG_SCHEMA_VERSION)
+ std::error_code Ec;
+ if (!IsProcessRunning(m_ServerOptions.OwnerPid, Ec))
{
- std::filesystem::path ManifestSkipSchemaChangePath = m_DataRoot / "root_manifest.ignore_schema_mismatch";
- if (ManifestVersion != 0 && IsFile(ManifestSkipSchemaChangePath))
+ if (Ec)
{
- ZEN_INFO(
- "Schema version {} found in '{}' does not match {}, ignoring mismatch due to existance of '{}' and updating "
- "schema version",
- ManifestVersion,
- ManifestPath,
- ZEN_CFG_SCHEMA_VERSION,
- ManifestSkipSchemaChangePath);
- UpdateManifest = true;
+ 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());
}
else
{
- WipeState = true;
- WipeReason =
- fmt::format("Manifest schema version: {}, differs from required: {}", ManifestVersion, ZEN_CFG_SCHEMA_VERSION);
+ 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());
}
+ std::exit(1);
}
- }
- }
- }
-
- if (StateId == Oid::Zero)
- {
- StateId = Oid::NewOid();
- UpdateManifest = true;
- }
-
- const DateTime Now = DateTime::Now();
-
- if (CreatedWhen.GetTicks() == 0)
- {
- CreatedWhen = Now;
- UpdateManifest = true;
- }
-
- // Handle any state wipe
-
- if (WipeState)
- {
- ZEN_WARN("Wiping state at '{}' - reason: '{}'", m_DataRoot, WipeReason);
-
- std::error_code Ec;
- for (const std::filesystem::directory_entry& DirEntry : std::filesystem::directory_iterator{m_DataRoot, Ec})
- {
- if (DirEntry.is_directory() && (DirEntry.path().filename() != "logs"))
- {
- ZEN_INFO("Deleting '{}'", DirEntry.path());
-
- DeleteDirectories(DirEntry.path(), Ec);
-
- if (Ec)
+ ZEN_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(),
+ m_ServerOptions.OwnerPid);
+
+ // Sponsor processes are checked every second, so 2 second wait time should be enough
+ if (Entry->AddSponsorProcess(m_ServerOptions.OwnerPid, 2000))
{
- ZEN_WARN("Delete of '{}' returned error: '{}'", DirEntry.path(), Ec.message());
+ NotifyReady();
+ std::exit(0);
}
- }
- }
-
- ZEN_INFO("Wiped all directories in data root");
-
- UpdateManifest = true;
- }
-
- // Write manifest
-
- {
- CbObjectWriter Cbo;
- Cbo << "schema_version" << ZEN_CFG_SCHEMA_VERSION << "created" << CreatedWhen << "updated" << Now << "state_id" << StateId;
-
- m_RootManifest = Cbo.Save();
-
- if (UpdateManifest)
- {
- TemporaryFile::SafeWriteFile(ManifestPath, m_RootManifest.GetBuffer().GetView());
- }
-
- if (!ServerOptions.IsTest)
- {
- try
- {
- EmitCentralManifest(ServerOptions.SystemRootDir, StateId, m_RootManifest, ManifestPath);
- }
- catch (const std::exception& Ex)
- {
- ZEN_WARN("Unable to emit central manifest: ", Ex.what());
- }
- }
- }
-
- // Write state marker
-
- {
- std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker";
- static const std::string_view StateMarkerContent = "deleting this file will cause " ZEN_APP_NAME " to exit"sv;
- WriteFile(StateMarkerPath, IoBuffer(IoBuffer::Wrap, StateMarkerContent.data(), StateMarkerContent.size()));
-
- EnqueueStateMarkerTimer();
- }
-
- EnqueueStateExitFlagTimer();
-}
-
-void
-ZenStorageServer::InitializeStructuredCache(const ZenStorageServerOptions& ServerOptions)
-{
- ZEN_TRACE_CPU("ZenStorageServer::InitializeStructuredCache");
-
- using namespace std::literals;
-
- ZEN_INFO("instantiating structured cache service");
- ZenCacheStore::Configuration Config;
- Config.AllowAutomaticCreationOfNamespaces = true;
- Config.Logging = {.EnableWriteLog = ServerOptions.StructuredCacheConfig.WriteLogEnabled,
- .EnableAccessLog = ServerOptions.StructuredCacheConfig.AccessLogEnabled};
-
- for (const auto& It : ServerOptions.StructuredCacheConfig.PerBucketConfigs)
- {
- const std::string& BucketName = It.first;
- const ZenStructuredCacheBucketConfig& ZenBucketConfig = It.second;
- ZenCacheDiskLayer::BucketConfiguration BucketConfig = {.MaxBlockSize = ZenBucketConfig.MaxBlockSize,
- .PayloadAlignment = ZenBucketConfig.PayloadAlignment,
- .MemCacheSizeThreshold = ZenBucketConfig.MemCacheSizeThreshold,
- .LargeObjectThreshold = ZenBucketConfig.LargeObjectThreshold,
- .LimitOverwrites = ZenBucketConfig.LimitOverwrites};
- Config.NamespaceConfig.DiskLayerConfig.BucketConfigMap.insert_or_assign(BucketName, BucketConfig);
- }
- Config.NamespaceConfig.DiskLayerConfig.BucketConfig.MaxBlockSize = ServerOptions.StructuredCacheConfig.BucketConfig.MaxBlockSize,
- Config.NamespaceConfig.DiskLayerConfig.BucketConfig.PayloadAlignment =
- ServerOptions.StructuredCacheConfig.BucketConfig.PayloadAlignment,
- Config.NamespaceConfig.DiskLayerConfig.BucketConfig.MemCacheSizeThreshold =
- ServerOptions.StructuredCacheConfig.BucketConfig.MemCacheSizeThreshold,
- Config.NamespaceConfig.DiskLayerConfig.BucketConfig.LargeObjectThreshold =
- ServerOptions.StructuredCacheConfig.BucketConfig.LargeObjectThreshold,
- Config.NamespaceConfig.DiskLayerConfig.BucketConfig.LimitOverwrites = ServerOptions.StructuredCacheConfig.BucketConfig.LimitOverwrites;
- Config.NamespaceConfig.DiskLayerConfig.MemCacheTargetFootprintBytes = ServerOptions.StructuredCacheConfig.MemTargetFootprintBytes;
- Config.NamespaceConfig.DiskLayerConfig.MemCacheTrimIntervalSeconds = ServerOptions.StructuredCacheConfig.MemTrimIntervalSeconds;
- Config.NamespaceConfig.DiskLayerConfig.MemCacheMaxAgeSeconds = ServerOptions.StructuredCacheConfig.MemMaxAgeSeconds;
-
- if (ServerOptions.IsDedicated)
- {
- Config.NamespaceConfig.DiskLayerConfig.BucketConfig.LargeObjectThreshold = 128 * 1024 * 1024;
- }
-
- m_CacheStore = new ZenCacheStore(m_GcManager, *m_JobQueue, m_DataRoot / "cache", Config, m_GcManager.GetDiskWriteBlocker());
- m_OpenProcessCache = std::make_unique<OpenProcessCache>();
-
- const ZenUpstreamCacheConfig& UpstreamConfig = ServerOptions.UpstreamCacheConfig;
-
- UpstreamCacheOptions UpstreamOptions;
- UpstreamOptions.ReadUpstream = (uint8_t(ServerOptions.UpstreamCacheConfig.CachePolicy) & uint8_t(UpstreamCachePolicy::Read)) != 0;
- UpstreamOptions.WriteUpstream = (uint8_t(ServerOptions.UpstreamCacheConfig.CachePolicy) & uint8_t(UpstreamCachePolicy::Write)) != 0;
-
- if (UpstreamConfig.UpstreamThreadCount < 32)
- {
- UpstreamOptions.ThreadCount = static_cast<uint32_t>(UpstreamConfig.UpstreamThreadCount);
- }
-
- m_UpstreamCache = CreateUpstreamCache(UpstreamOptions, *m_CacheStore, *m_CidStore);
- m_UpstreamService = std::make_unique<HttpUpstreamService>(*m_UpstreamCache, *m_AuthMgr);
- m_UpstreamCache->Initialize();
-
- if (ServerOptions.UpstreamCacheConfig.CachePolicy != UpstreamCachePolicy::Disabled)
- {
- // Zen upstream
- {
- std::vector<std::string> ZenUrls = UpstreamConfig.ZenConfig.Urls;
- if (!UpstreamConfig.ZenConfig.Dns.empty())
- {
- for (const std::string& Dns : UpstreamConfig.ZenConfig.Dns)
+ if (AttachSponsorProcessRetriesLeft-- > 0)
{
- if (!Dns.empty())
- {
- const asio::error_code Err = utils::ResolveHostname(m_IoContext, Dns, "8558"sv, ZenUrls);
- if (Err)
- {
- ZEN_ERROR("resolve of '{}' FAILED, reason '{}'", Dns, Err.message());
- }
- }
+ Entry = ServerState.Lookup(m_ServerOptions.BasePort);
+ }
+ 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);
}
}
-
- std::erase_if(ZenUrls, [](const auto& Url) { return Url.empty(); });
-
- if (!ZenUrls.empty())
+ else
{
- const auto ZenEndpointName = UpstreamConfig.ZenConfig.Name.empty() ? "Zen"sv : UpstreamConfig.ZenConfig.Name;
-
- std::unique_ptr<UpstreamEndpoint> ZenEndpoint = UpstreamEndpoint::CreateZenEndpoint(
- {.Name = ZenEndpointName,
- .Urls = ZenUrls,
- .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds),
- .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)});
-
- m_UpstreamCache->RegisterEndpoint(std::move(ZenEndpoint));
+ ZEN_WARN(ZEN_APP_NAME " exiting, there is already a process listening to port {} (pid: {})",
+ m_ServerOptions.BasePort,
+ Entry->Pid.load());
+ std::exit(1);
}
}
- // Jupiter upstream
- if (UpstreamConfig.JupiterConfig.Url.empty() == false)
- {
- std::string_view EndpointName = UpstreamConfig.JupiterConfig.Name.empty() ? "Jupiter"sv : UpstreamConfig.JupiterConfig.Name;
-
- auto Options = JupiterClientOptions{.Name = EndpointName,
- .ServiceUrl = UpstreamConfig.JupiterConfig.Url,
- .DdcNamespace = UpstreamConfig.JupiterConfig.DdcNamespace,
- .BlobStoreNamespace = UpstreamConfig.JupiterConfig.Namespace,
- .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds),
- .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)};
-
- auto AuthConfig = UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.JupiterConfig.OAuthUrl,
- .OAuthClientId = UpstreamConfig.JupiterConfig.OAuthClientId,
- .OAuthClientSecret = UpstreamConfig.JupiterConfig.OAuthClientSecret,
- .OpenIdProvider = UpstreamConfig.JupiterConfig.OpenIdProvider,
- .AccessToken = UpstreamConfig.JupiterConfig.AccessToken};
-
- std::unique_ptr<UpstreamEndpoint> JupiterEndpoint = UpstreamEndpoint::CreateJupiterEndpoint(Options, AuthConfig, *m_AuthMgr);
-
- m_UpstreamCache->RegisterEndpoint(std::move(JupiterEndpoint));
- }
- }
-
- m_StructuredCacheService = std::make_unique<HttpStructuredCacheService>(*m_CacheStore,
- *m_CidStore,
- m_StatsService,
- m_StatusService,
- *m_UpstreamCache,
- m_GcManager.GetDiskWriteBlocker(),
- *m_OpenProcessCache);
-
- m_StatsReporter.AddProvider(m_CacheStore.Get());
- m_StatsReporter.AddProvider(m_CidStore.get());
- m_StatsReporter.AddProvider(m_BuildCidStore.get());
-}
-
-void
-ZenStorageServer::Run()
-{
- if (m_ProcessMonitor.IsActive())
- {
- CheckOwnerPid();
- }
-
- if (!m_TestMode)
- {
- ZEN_INFO(
- "__________ _________ __ \n"
- "\\____ /____ ____ / _____// |_ ___________ ____ \n"
- " / // __ \\ / \\ \\_____ \\\\ __\\/ _ \\_ __ \\_/ __ \\ \n"
- " / /\\ ___/| | \\ / \\| | ( <_> ) | \\/\\ ___/ \n"
- "/_______ \\___ >___| / /_______ /|__| \\____/|__| \\___ >\n"
- " \\/ \\/ \\/ \\/ \\/ \n");
- }
-
- ZEN_INFO(ZEN_APP_NAME " now running (pid: {})", GetCurrentProcessId());
-
-#if ZEN_PLATFORM_WINDOWS
- if (zen::windows::IsRunningOnWine())
- {
- ZEN_INFO("detected Wine session - " ZEN_APP_NAME " is not formally tested on Wine and may therefore not work or perform well");
- }
-#endif
-
-#if ZEN_USE_SENTRY
- ZEN_INFO("sentry crash handler {}", m_UseSentry ? "ENABLED" : "DISABLED");
- if (m_UseSentry)
- {
- SentryIntegration::ClearCaches();
- }
-#endif
-
- if (m_DebugOptionForcedCrash)
- {
- ZEN_DEBUG_BREAK();
- }
-
- const bool IsInteractiveMode = IsInteractiveSession() && !m_TestMode;
-
- SetNewState(kRunning);
-
- OnReady();
-
- if (!m_StartupScrubOptions.empty())
- {
- using namespace std::literals;
-
- ZEN_INFO("triggering scrub with settings: '{}'", m_StartupScrubOptions);
+ std::error_code Ec;
- bool DoScrub = true;
- bool DoWait = false;
- GcScheduler::TriggerScrubParams ScrubParams;
+ std::filesystem::path LockFilePath = m_ServerOptions.DataDir / ".lock";
- ForEachStrTok(m_StartupScrubOptions, ',', [&](std::string_view Token) {
- if (Token == "nocas"sv)
- {
- ScrubParams.SkipCas = true;
- }
- else if (Token == "nodelete"sv)
- {
- ScrubParams.SkipDelete = true;
- }
- else if (Token == "nogc"sv)
- {
- ScrubParams.SkipGc = true;
- }
- else if (Token == "no"sv)
- {
- DoScrub = false;
- }
- else if (Token == "wait"sv)
- {
- DoWait = true;
- }
- return true;
- });
+ m_LockFile.Create(LockFilePath, MakeLockData(false), Ec);
- if (DoScrub)
+ if (Ec)
{
- m_GcScheduler.TriggerScrub(ScrubParams);
+ ZEN_INFO(ZEN_APP_NAME " unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message());
+ Sleep(100);
- if (DoWait)
+ m_LockFile.Create(LockFilePath, MakeLockData(false), Ec);
+ if (Ec)
{
- auto State = m_GcScheduler.Status();
-
- while ((State != GcSchedulerStatus::kRunning) && (State != GcSchedulerStatus::kStopped))
- {
- Sleep(500);
-
- State = m_GcScheduler.Status();
- }
-
- ZEN_INFO("waiting for Scrub/GC to complete...");
-
- while (State == GcSchedulerStatus::kRunning)
+ ZEN_INFO(ZEN_APP_NAME " unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message());
+ Sleep(500);
+ if (Ec)
{
- Sleep(500);
-
- State = m_GcScheduler.Status();
+ ZEN_WARN(ZEN_APP_NAME " exiting, unable to grab lock at '{}' (reason: '{}')", LockFilePath, Ec.message());
+ std::exit(99);
}
-
- ZEN_INFO("Scrub/GC completed");
}
}
- }
- if (m_IsPowerCycle)
- {
- ZEN_INFO("Power cycle mode enabled -- shutting down");
- RequestExit(0);
- }
+ InitializeServerLogging(m_ServerOptions);
- m_Http->Run(IsInteractiveMode);
+ ZEN_INFO("Command line: {}", m_ServerOptions.CommandLine);
- SetNewState(kShuttingDown);
+#if ZEN_USE_SENTRY
+ Sentry.LogStartupInformation();
+#endif
- ZEN_INFO(ZEN_APP_NAME " exiting");
-}
+ MaximizeOpenFileCount();
-void
-ZenStorageServer::Cleanup()
-{
- ZEN_TRACE_CPU("ZenStorageServer::Cleanup");
- ZEN_INFO(ZEN_APP_NAME " cleaning up");
- try
- {
- m_IoContext.stop();
- if (m_IoRunner.joinable())
- {
- m_IoRunner.join();
- }
+ ZEN_INFO(ZEN_APP_NAME " - using lock file at '{}'", LockFilePath);
- if (m_Http)
- {
- m_Http->Close();
- }
+ ZEN_INFO(ZEN_APP_NAME " - starting on port {}, version '{}'", m_ServerOptions.BasePort, ZEN_CFG_VERSION_BUILD_STRING_FULL);
+
+ Entry = ServerState.Register(m_ServerOptions.BasePort);
- if (m_JobQueue)
+ if (m_ServerOptions.OwnerPid)
{
- m_JobQueue->Stop();
+ // We are adding a sponsor process to our own entry, can't wait for pick since the code is not run until later
+ Entry->AddSponsorProcess(m_ServerOptions.OwnerPid, 0);
}
- m_StatsReporter.Shutdown();
- m_GcScheduler.Shutdown();
-
- Flush();
-
- m_AdminService.reset();
- m_VfsService.reset();
- m_VfsServiceImpl.reset();
- m_ObjStoreService.reset();
- m_FrontendService.reset();
+ // Run the actual application logic
- m_BuildStoreService.reset();
- m_BuildStore = {};
- m_BuildCidStore.reset();
-
- m_StructuredCacheService.reset();
- m_UpstreamService.reset();
- m_UpstreamCache.reset();
- m_CacheStore = {};
- m_OpenProcessCache.reset();
-
- m_HttpWorkspacesService.reset();
- m_Workspaces.reset();
- m_HttpProjectService.reset();
- m_ProjectStore = {};
- m_CidStore.reset();
- m_AuthService.reset();
- m_AuthMgr.reset();
- m_Http = {};
-
- ShutdownWorkerPools();
-
- m_JobQueue.reset();
+ DoRun(Entry);
}
- catch (const std::exception& Ex)
+ catch (const AssertException& AssertEx)
{
- ZEN_ERROR("exception thrown during Cleanup() in {}: '{}'", ZEN_APP_NAME, Ex.what());
+ ZEN_CRITICAL(ZEN_APP_NAME " caught assert exception in main for process {}: {}",
+ zen::GetCurrentProcessId(),
+ AssertEx.FullDescription());
+ RequestApplicationExit(1);
}
-}
-
-void
-ZenStorageServer::EnqueueStateMarkerTimer()
-{
- ZEN_MEMSCOPE(GetZenserverTag());
- m_StateMarkerTimer.expires_after(std::chrono::seconds(5));
- m_StateMarkerTimer.async_wait([this](const asio::error_code&) { CheckStateMarker(); });
- EnsureIoRunner();
-}
-
-void
-ZenStorageServer::CheckStateMarker()
-{
- ZEN_MEMSCOPE(GetZenserverTag());
- std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker";
- try
+ catch (const std::system_error& e)
{
- if (!IsFile(StateMarkerPath))
- {
- ZEN_WARN("state marker at {} has been deleted, exiting", StateMarkerPath);
- RequestExit(1);
- return;
- }
+ ZEN_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& Ex)
+ catch (const std::exception& e)
{
- ZEN_WARN("state marker at {} could not be checked, reason: '{}'", StateMarkerPath, Ex.what());
- RequestExit(1);
- return;
+ ZEN_CRITICAL(ZEN_APP_NAME " caught exception in main for process {}: {}", zen::GetCurrentProcessId(), e.what());
+ RequestApplicationExit(1);
}
- EnqueueStateMarkerTimer();
-}
-void
-ZenStorageServer::Flush()
-{
- ZEN_TRACE_CPU("ZenStorageServer::Flush");
-
- if (m_CidStore)
- m_CidStore->Flush();
+ ShutdownServerLogging();
- if (m_StructuredCacheService)
- m_StructuredCacheService->Flush();
+ ReportServiceStatus(ServiceStatus::Stopped);
- if (m_ProjectStore)
- m_ProjectStore->Flush();
-
- if (m_BuildCidStore)
- m_BuildCidStore->Flush();
+ return ApplicationExitCode();
}
-#if ZEN_WITH_TESTS
-
void
-zenserver_forcelinktests()
+ZenServerMain::NotifyReady()
{
+ if (!m_ServerOptions.ChildId.empty())
+ {
+ NamedEvent ParentEvent{m_ServerOptions.ChildId};
+ ParentEvent.Set();
+ }
}
-#endif
+CbObject
+ZenServerMain::MakeLockData(bool IsReady)
+{
+ return MakeLockFilePayload({.Pid = GetCurrentProcessId(),
+ .SessionId = GetSessionId(),
+ .EffectiveListenPort = gsl::narrow<uint16_t>(m_ServerOptions.BasePort),
+ .Ready = IsReady,
+ .DataDir = m_ServerOptions.DataDir,
+ .ExecutablePath = GetRunningExecutablePath()});
+};
} // namespace zen
diff --git a/src/zenserver/zenserver.h b/src/zenserver/zenserver.h
index 5447158ab..e8ada97ae 100644
--- a/src/zenserver/zenserver.h
+++ b/src/zenserver/zenserver.h
@@ -2,7 +2,9 @@
#pragma once
+#include <zencore/basicfile.h>
#include <zenhttp/httpserver.h>
+#include <zenhttp/httpstats.h>
#include <zenhttp/httpstatus.h>
#include <zenutil/zenserverprocess.h>
@@ -13,29 +15,8 @@ ZEN_THIRD_PARTY_INCLUDES_START
#include <asio.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
-//////////////////////////////////////////////////////////////////////////
-// Services
-//
-
-#include <zenhttp/auth/authmgr.h>
-#include <zenhttp/auth/authservice.h>
-#include <zenhttp/httpstats.h>
-#include <zenhttp/httpstatus.h>
-#include <zenhttp/httptest.h>
-#include <zenstore/cache/structuredcachestore.h>
-#include <zenstore/gc.h>
-#include <zenstore/projectstore.h>
-#include "admin/admin.h"
-#include "buildstore/httpbuildstore.h"
-#include "cache/httpstructuredcache.h"
#include "diag/diagsvcs.h"
-#include "frontend/frontend.h"
-#include "objectstore/objectstore.h"
-#include "projectstore/httpprojectstore.h"
#include "stats/statsreporter.h"
-#include "upstream/upstream.h"
-#include "vfs/vfsservice.h"
-#include "workspaces/httpworkspaces.h"
#ifndef ZEN_APP_NAME
# define ZEN_APP_NAME "Unreal Zen Storage Server"
@@ -43,6 +24,9 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
+struct FLLMTag;
+extern const FLLMTag& GetZenserverTag();
+
struct ZenStorageServerOptions;
class ZenServerBase : public IHttpStatusProvider
@@ -122,78 +106,25 @@ protected:
virtual void HandleStatusRequest(HttpServerRequest& Request) override;
};
-class ZenStorageServer : public ZenServerBase
+class ZenServerMain
{
- ZenStorageServer& operator=(ZenStorageServer&&) = delete;
- ZenStorageServer(ZenStorageServer&&) = delete;
-
public:
- ZenStorageServer();
- ~ZenStorageServer();
-
- void SetDedicatedMode(bool State) { m_IsDedicatedMode = State; }
- void SetTestMode(bool State) { m_TestMode = State; }
- void SetDataRoot(std::filesystem::path Root) { m_DataRoot = Root; }
- void SetContentRoot(std::filesystem::path Root) { m_ContentRoot = Root; }
-
- int Initialize(const ZenStorageServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry);
- void Run();
- void Cleanup();
-
-private:
- void InitializeState(const ZenStorageServerOptions& ServerOptions);
- void InitializeStructuredCache(const ZenStorageServerOptions& ServerOptions);
- void Flush();
-
- bool m_IsDedicatedMode = false;
- bool m_TestMode = false;
- bool m_DebugOptionForcedCrash = false;
- std::string m_StartupScrubOptions;
- CbObject m_RootManifest;
- std::filesystem::path m_DataRoot;
- std::filesystem::path m_ContentRoot;
- asio::steady_timer m_StateMarkerTimer{m_IoContext};
-
- void EnqueueStateMarkerTimer();
- void CheckStateMarker();
-
- std::unique_ptr<AuthMgr> m_AuthMgr;
- std::unique_ptr<HttpAuthService> m_AuthService;
- void InitializeAuthentication(const ZenStorageServerOptions& ServerOptions);
-
- void InitializeServices(const ZenStorageServerOptions& ServerOptions);
- void RegisterServices();
-
- HttpStatsService m_StatsService;
- std::unique_ptr<JobQueue> m_JobQueue;
- GcManager m_GcManager;
- GcScheduler m_GcScheduler{m_GcManager};
- std::unique_ptr<CidStore> m_CidStore;
- Ref<ZenCacheStore> m_CacheStore;
- std::unique_ptr<OpenProcessCache> m_OpenProcessCache;
- HttpTestService m_TestService;
- std::unique_ptr<CidStore> m_BuildCidStore;
- std::unique_ptr<BuildStore> m_BuildStore;
-
-#if ZEN_WITH_TESTS
- HttpTestingService m_TestingService;
-#endif
+ ZenServerMain(ZenServerOptions& ServerOptions);
+ ~ZenServerMain();
- RefPtr<ProjectStore> m_ProjectStore;
- std::unique_ptr<VfsServiceImpl> m_VfsServiceImpl;
- std::unique_ptr<HttpProjectService> m_HttpProjectService;
- std::unique_ptr<Workspaces> m_Workspaces;
- std::unique_ptr<HttpWorkspacesService> m_HttpWorkspacesService;
- std::unique_ptr<UpstreamCache> m_UpstreamCache;
- std::unique_ptr<HttpUpstreamService> m_UpstreamService;
- std::unique_ptr<HttpStructuredCacheService> m_StructuredCacheService;
- std::unique_ptr<HttpFrontendService> m_FrontendService;
- std::unique_ptr<HttpObjectStoreService> m_ObjStoreService;
- std::unique_ptr<HttpBuildStoreService> m_BuildStoreService;
- std::unique_ptr<VfsService> m_VfsService;
- std::unique_ptr<HttpAdminService> m_AdminService;
-};
+ int Run();
+
+ ZenServerMain(const ZenServerMain&) = delete;
+ ZenServerMain& operator=(const ZenServerMain&) = delete;
+
+protected:
+ ZenServerOptions& m_ServerOptions;
+ LockFile m_LockFile;
+
+ virtual void DoRun(ZenServerState::ZenServerEntry* Entry) = 0;
-void zenserver_forcelinktests();
+ void NotifyReady();
+ CbObject MakeLockData(bool IsReady);
+};
} // namespace zen
diff --git a/src/zenserver/zenstorageserver.cpp b/src/zenserver/zenstorageserver.cpp
new file mode 100644
index 000000000..a8dd65372
--- /dev/null
+++ b/src/zenserver/zenstorageserver.cpp
@@ -0,0 +1,961 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenstorageserver.h"
+
+#include <zenbase/refcount.h>
+#include <zencore/basicfile.h>
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryvalidation.h>
+#include <zencore/config.h>
+#include <zencore/except.h>
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/iobuffer.h>
+#include <zencore/jobqueue.h>
+#include <zencore/logging.h>
+#include <zencore/scopeguard.h>
+#include <zencore/sentryintegration.h>
+#include <zencore/session.h>
+#include <zencore/string.h>
+#include <zencore/thread.h>
+#include <zencore/timer.h>
+#include <zencore/trace.h>
+#include <zencore/workthreadpool.h>
+#include <zenhttp/httpserver.h>
+#include <zenremotestore/jupiter/jupiterclient.h>
+#include <zenstore/buildstore/buildstore.h>
+#include <zenstore/cidstore.h>
+#include <zenstore/scrubcontext.h>
+#include <zenstore/vfsimpl.h>
+#include <zenstore/workspaces.h>
+#include <zenutil/service.h>
+#include <zenutil/workerpools.h>
+#include <zenutil/zenserverprocess.h>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
+#if ZEN_PLATFORM_LINUX
+# include <pwd.h>
+#endif
+
+#if ZEN_PLATFORM_MAC
+# include <pwd.h>
+#endif
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <fmt/format.h>
+#include <asio.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+#include <exception>
+#include <set>
+
+//////////////////////////////////////////////////////////////////////////
+
+#include "config.h"
+#include "diag/logging.h"
+
+#include <zencore/memory/llm.h>
+
+namespace zen {
+
+namespace utils {
+ asio::error_code ResolveHostname(asio::io_context& Ctx,
+ std::string_view Host,
+ std::string_view DefaultPort,
+ std::vector<std::string>& OutEndpoints)
+ {
+ std::string_view Port = DefaultPort;
+
+ if (const size_t Idx = Host.find(":"); Idx != std::string_view::npos)
+ {
+ Port = Host.substr(Idx + 1);
+ Host = Host.substr(0, Idx);
+ }
+
+ asio::ip::tcp::resolver Resolver(Ctx);
+
+ asio::error_code ErrorCode;
+ asio::ip::tcp::resolver::results_type Endpoints = Resolver.resolve(Host, Port, ErrorCode);
+
+ if (!ErrorCode)
+ {
+ for (const asio::ip::tcp::endpoint Ep : Endpoints)
+ {
+ OutEndpoints.push_back(fmt::format("http://{}:{}", Ep.address().to_string(), Ep.port()));
+ }
+ }
+
+ return ErrorCode;
+ }
+} // namespace utils
+
+using namespace std::literals;
+
+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);
+ }
+
+ 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");
+
+ CidStoreConfiguration Config;
+ Config.RootDirectory = m_DataRoot / "cas";
+
+ m_CidStore = std::make_unique<CidStore>(m_GcManager);
+ m_CidStore->Initialize(Config);
+
+ 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});
+
+ if (ServerOptions.WorksSpacesConfig.Enabled)
+ {
+ m_Workspaces.reset(new Workspaces());
+ m_HttpWorkspacesService.reset(
+ new HttpWorkspacesService(m_StatusService,
+ m_StatsService,
+ {.SystemRootDir = ServerOptions.SystemRootDir,
+ .AllowConfigurationChanges = ServerOptions.WorksSpacesConfig.AllowConfigurationChanges},
+ *m_Workspaces));
+ }
+
+ if (ServerOptions.BuildStoreConfig.Enabled)
+ {
+ CidStoreConfiguration BuildCidConfig;
+ BuildCidConfig.RootDirectory = m_DataRoot / "builds_cas";
+ m_BuildCidStore = std::make_unique<CidStore>(m_GcManager);
+ m_BuildCidStore->Initialize(BuildCidConfig);
+
+ BuildStoreConfig BuildsCfg;
+ BuildsCfg.RootDirectory = m_DataRoot / "builds";
+ BuildsCfg.MaxDiskSpaceLimit = ServerOptions.BuildStoreConfig.MaxDiskSpaceLimit;
+ m_BuildStore = std::make_unique<BuildStore>(std::move(BuildsCfg), m_GcManager, *m_BuildCidStore);
+ }
+
+ if (ServerOptions.StructuredCacheConfig.Enabled)
+ {
+ InitializeStructuredCache(ServerOptions);
+ }
+ else
+ {
+ ZEN_INFO("NOT instantiating structured cache service");
+ }
+
+ if (ServerOptions.ObjectStoreEnabled)
+ {
+ ObjectStoreConfig ObjCfg;
+ ObjCfg.RootDirectory = m_DataRoot / "obj";
+
+ for (const auto& Bucket : ServerOptions.ObjectStoreConfig.Buckets)
+ {
+ ObjectStoreConfig::BucketConfig NewBucket{.Name = Bucket.Name};
+ NewBucket.Directory = Bucket.Directory.empty() ? (ObjCfg.RootDirectory / Bucket.Name) : Bucket.Directory;
+ ObjCfg.Buckets.push_back(std::move(NewBucket));
+ }
+
+ m_ObjStoreService = std::make_unique<HttpObjectStoreService>(m_StatusService, std::move(ObjCfg));
+ }
+
+ if (ServerOptions.BuildStoreConfig.Enabled)
+ {
+ m_BuildStoreService = std::make_unique<HttpBuildStoreService>(m_StatusService, m_StatsService, *m_BuildStore);
+ }
+
+#if ZEN_WITH_VFS
+ m_VfsServiceImpl = std::make_unique<VfsServiceImpl>();
+ m_VfsServiceImpl->AddService(Ref<ProjectStore>(m_ProjectStore));
+ m_VfsServiceImpl->AddService(Ref<ZenCacheStore>(m_CacheStore));
+
+ m_VfsService = std::make_unique<VfsService>(m_StatusService, m_VfsServiceImpl.get());
+#endif // ZEN_WITH_VFS
+
+ ZEN_INFO("initializing GC, enabled '{}', interval {}, lightweight interval {}",
+ ServerOptions.GcConfig.Enabled,
+ NiceTimeSpanMs(ServerOptions.GcConfig.IntervalSeconds * 1000ull),
+ NiceTimeSpanMs(ServerOptions.GcConfig.LightweightIntervalSeconds * 1000ull));
+
+ GcSchedulerConfig GcConfig{.RootDirectory = m_DataRoot / "gc",
+ .MonitorInterval = std::chrono::seconds(ServerOptions.GcConfig.MonitorIntervalSeconds),
+ .Interval = std::chrono::seconds(ServerOptions.GcConfig.IntervalSeconds),
+ .MaxCacheDuration = std::chrono::seconds(ServerOptions.GcConfig.Cache.MaxDurationSeconds),
+ .MaxProjectStoreDuration = std::chrono::seconds(ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds),
+ .MaxBuildStoreDuration = std::chrono::seconds(ServerOptions.GcConfig.BuildStore.MaxDurationSeconds),
+ .CollectSmallObjects = ServerOptions.GcConfig.CollectSmallObjects,
+ .Enabled = ServerOptions.GcConfig.Enabled,
+ .DiskReserveSize = ServerOptions.GcConfig.DiskReserveSize,
+ .DiskSizeSoftLimit = ServerOptions.GcConfig.DiskSizeSoftLimit,
+ .MinimumFreeDiskSpaceToAllowWrites = ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites,
+ .LightweightInterval = std::chrono::seconds(ServerOptions.GcConfig.LightweightIntervalSeconds),
+ .UseGCVersion = ServerOptions.GcConfig.UseGCV2 ? GcVersion::kV2 : GcVersion::kV1_Deprecated,
+ .CompactBlockUsageThresholdPercent = ServerOptions.GcConfig.CompactBlockUsageThresholdPercent,
+ .Verbose = ServerOptions.GcConfig.Verbose,
+ .SingleThreaded = ServerOptions.GcConfig.SingleThreaded,
+ .AttachmentPassCount = ServerOptions.GcConfig.AttachmentPassCount};
+ m_GcScheduler.Initialize(GcConfig);
+
+ // Create and register admin interface last to make sure all is properly initialized
+ m_AdminService = std::make_unique<HttpAdminService>(
+ m_GcScheduler,
+ *m_JobQueue,
+ m_CacheStore.Get(),
+ [this]() { Flush(); },
+ HttpAdminService::LogPaths{.AbsLogPath = ServerOptions.AbsLogFile,
+ .HttpLogPath = ServerOptions.DataDir / "logs" / "http.log",
+ .CacheLogPath = ServerOptions.DataDir / "logs" / "z$.log"},
+ ServerOptions);
+}
+
+void
+ZenStorageServer::InitializeAuthentication(const ZenStorageServerOptions& ServerOptions)
+{
+ // Setup authentication manager
+ {
+ ZEN_TRACE_CPU("ZenStorageServer::InitAuth");
+ std::string EncryptionKey = ServerOptions.EncryptionKey;
+
+ if (EncryptionKey.empty())
+ {
+ EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456";
+
+ if (ServerOptions.IsDedicated)
+ {
+ ZEN_WARN("Using default encryption key for authentication state");
+ }
+ }
+
+ std::string EncryptionIV = ServerOptions.EncryptionIV;
+
+ if (EncryptionIV.empty())
+ {
+ EncryptionIV = "0123456789abcdef";
+
+ if (ServerOptions.IsDedicated)
+ {
+ ZEN_WARN("Using default encryption initialization vector for authentication state");
+ }
+ }
+
+ m_AuthMgr = AuthMgr::Create({.RootDirectory = m_DataRoot / "auth",
+ .EncryptionKey = AesKey256Bit::FromString(EncryptionKey),
+ .EncryptionIV = AesIV128Bit::FromString(EncryptionIV)});
+
+ for (const ZenOpenIdProviderConfig& OpenIdProvider : ServerOptions.AuthConfig.OpenIdProviders)
+ {
+ m_AuthMgr->AddOpenIdProvider({.Name = OpenIdProvider.Name, .Url = OpenIdProvider.Url, .ClientId = OpenIdProvider.ClientId});
+ }
+ }
+
+ m_AuthService = std::make_unique<HttpAuthService>(*m_AuthMgr);
+}
+
+void
+ZenStorageServer::InitializeState(const ZenStorageServerOptions& ServerOptions)
+{
+ ZEN_TRACE_CPU("ZenStorageServer::InitializeState");
+
+ // Check root manifest to deal with schema versioning
+
+ bool WipeState = false;
+ std::string WipeReason = "Unspecified";
+
+ if (ServerOptions.IsCleanStart)
+ {
+ WipeState = true;
+ WipeReason = "clean start requested";
+ }
+
+ bool UpdateManifest = false;
+ std::filesystem::path ManifestPath = m_DataRoot / "root_manifest";
+ Oid StateId = Oid::Zero;
+ DateTime CreatedWhen{0};
+
+ if (!WipeState)
+ {
+ FileContents ManifestData = ReadFile(ManifestPath);
+
+ if (ManifestData.ErrorCode)
+ {
+ if (ServerOptions.IsFirstRun)
+ {
+ ZEN_INFO("Initializing state at '{}'", m_DataRoot);
+
+ UpdateManifest = true;
+ }
+ else
+ {
+ WipeState = true;
+ WipeReason = fmt::format("No manifest present at '{}'", ManifestPath);
+ }
+ }
+ else
+ {
+ IoBuffer Manifest = ManifestData.Flatten();
+
+ if (CbValidateError ValidationResult = ValidateCompactBinary(Manifest, CbValidateMode::All);
+ ValidationResult != CbValidateError::None)
+ {
+ ZEN_WARN("Manifest validation failed: {}, state will be wiped", zen::ToString(ValidationResult));
+
+ WipeState = true;
+ WipeReason = fmt::format("Validation of manifest at '{}' failed: {}", ManifestPath, zen::ToString(ValidationResult));
+ }
+ else
+ {
+ m_RootManifest = LoadCompactBinaryObject(Manifest);
+
+ const int32_t ManifestVersion = m_RootManifest["schema_version"].AsInt32(0);
+ StateId = m_RootManifest["state_id"].AsObjectId();
+ CreatedWhen = m_RootManifest["created"].AsDateTime();
+
+ if (ManifestVersion != ZEN_CFG_SCHEMA_VERSION)
+ {
+ std::filesystem::path ManifestSkipSchemaChangePath = m_DataRoot / "root_manifest.ignore_schema_mismatch";
+ if (ManifestVersion != 0 && IsFile(ManifestSkipSchemaChangePath))
+ {
+ ZEN_INFO(
+ "Schema version {} found in '{}' does not match {}, ignoring mismatch due to existance of '{}' and updating "
+ "schema version",
+ ManifestVersion,
+ ManifestPath,
+ ZEN_CFG_SCHEMA_VERSION,
+ ManifestSkipSchemaChangePath);
+ UpdateManifest = true;
+ }
+ else
+ {
+ WipeState = true;
+ WipeReason =
+ fmt::format("Manifest schema version: {}, differs from required: {}", ManifestVersion, ZEN_CFG_SCHEMA_VERSION);
+ }
+ }
+ }
+ }
+ }
+
+ if (StateId == Oid::Zero)
+ {
+ StateId = Oid::NewOid();
+ UpdateManifest = true;
+ }
+
+ const DateTime Now = DateTime::Now();
+
+ if (CreatedWhen.GetTicks() == 0)
+ {
+ CreatedWhen = Now;
+ UpdateManifest = true;
+ }
+
+ // Handle any state wipe
+
+ if (WipeState)
+ {
+ ZEN_WARN("Wiping state at '{}' - reason: '{}'", m_DataRoot, WipeReason);
+
+ std::error_code Ec;
+ for (const std::filesystem::directory_entry& DirEntry : std::filesystem::directory_iterator{m_DataRoot, Ec})
+ {
+ if (DirEntry.is_directory() && (DirEntry.path().filename() != "logs"))
+ {
+ ZEN_INFO("Deleting '{}'", DirEntry.path());
+
+ DeleteDirectories(DirEntry.path(), Ec);
+
+ if (Ec)
+ {
+ ZEN_WARN("Delete of '{}' returned error: '{}'", DirEntry.path(), Ec.message());
+ }
+ }
+ }
+
+ ZEN_INFO("Wiped all directories in data root");
+
+ UpdateManifest = true;
+ }
+
+ // Write manifest
+
+ {
+ CbObjectWriter Cbo;
+ Cbo << "schema_version" << ZEN_CFG_SCHEMA_VERSION << "created" << CreatedWhen << "updated" << Now << "state_id" << StateId;
+
+ m_RootManifest = Cbo.Save();
+
+ if (UpdateManifest)
+ {
+ TemporaryFile::SafeWriteFile(ManifestPath, m_RootManifest.GetBuffer().GetView());
+ }
+
+ if (!ServerOptions.IsTest)
+ {
+ try
+ {
+ EmitCentralManifest(ServerOptions.SystemRootDir, StateId, m_RootManifest, ManifestPath);
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("Unable to emit central manifest: ", Ex.what());
+ }
+ }
+ }
+
+ // Write state marker
+
+ {
+ std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker";
+ static const std::string_view StateMarkerContent = "deleting this file will cause " ZEN_APP_NAME " to exit"sv;
+ WriteFile(StateMarkerPath, IoBuffer(IoBuffer::Wrap, StateMarkerContent.data(), StateMarkerContent.size()));
+
+ EnqueueStateMarkerTimer();
+ }
+
+ EnqueueStateExitFlagTimer();
+}
+
+void
+ZenStorageServer::InitializeStructuredCache(const ZenStorageServerOptions& ServerOptions)
+{
+ ZEN_TRACE_CPU("ZenStorageServer::InitializeStructuredCache");
+
+ using namespace std::literals;
+
+ ZEN_INFO("instantiating structured cache service");
+ ZenCacheStore::Configuration Config;
+ Config.AllowAutomaticCreationOfNamespaces = true;
+ Config.Logging = {.EnableWriteLog = ServerOptions.StructuredCacheConfig.WriteLogEnabled,
+ .EnableAccessLog = ServerOptions.StructuredCacheConfig.AccessLogEnabled};
+
+ for (const auto& It : ServerOptions.StructuredCacheConfig.PerBucketConfigs)
+ {
+ const std::string& BucketName = It.first;
+ const ZenStructuredCacheBucketConfig& ZenBucketConfig = It.second;
+ ZenCacheDiskLayer::BucketConfiguration BucketConfig = {.MaxBlockSize = ZenBucketConfig.MaxBlockSize,
+ .PayloadAlignment = ZenBucketConfig.PayloadAlignment,
+ .MemCacheSizeThreshold = ZenBucketConfig.MemCacheSizeThreshold,
+ .LargeObjectThreshold = ZenBucketConfig.LargeObjectThreshold,
+ .LimitOverwrites = ZenBucketConfig.LimitOverwrites};
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfigMap.insert_or_assign(BucketName, BucketConfig);
+ }
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.MaxBlockSize = ServerOptions.StructuredCacheConfig.BucketConfig.MaxBlockSize,
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.PayloadAlignment =
+ ServerOptions.StructuredCacheConfig.BucketConfig.PayloadAlignment,
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.MemCacheSizeThreshold =
+ ServerOptions.StructuredCacheConfig.BucketConfig.MemCacheSizeThreshold,
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.LargeObjectThreshold =
+ ServerOptions.StructuredCacheConfig.BucketConfig.LargeObjectThreshold,
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.LimitOverwrites = ServerOptions.StructuredCacheConfig.BucketConfig.LimitOverwrites;
+ Config.NamespaceConfig.DiskLayerConfig.MemCacheTargetFootprintBytes = ServerOptions.StructuredCacheConfig.MemTargetFootprintBytes;
+ Config.NamespaceConfig.DiskLayerConfig.MemCacheTrimIntervalSeconds = ServerOptions.StructuredCacheConfig.MemTrimIntervalSeconds;
+ Config.NamespaceConfig.DiskLayerConfig.MemCacheMaxAgeSeconds = ServerOptions.StructuredCacheConfig.MemMaxAgeSeconds;
+
+ if (ServerOptions.IsDedicated)
+ {
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.LargeObjectThreshold = 128 * 1024 * 1024;
+ }
+
+ m_CacheStore = new ZenCacheStore(m_GcManager, *m_JobQueue, m_DataRoot / "cache", Config, m_GcManager.GetDiskWriteBlocker());
+ m_OpenProcessCache = std::make_unique<OpenProcessCache>();
+
+ const ZenUpstreamCacheConfig& UpstreamConfig = ServerOptions.UpstreamCacheConfig;
+
+ UpstreamCacheOptions UpstreamOptions;
+ UpstreamOptions.ReadUpstream = (uint8_t(ServerOptions.UpstreamCacheConfig.CachePolicy) & uint8_t(UpstreamCachePolicy::Read)) != 0;
+ UpstreamOptions.WriteUpstream = (uint8_t(ServerOptions.UpstreamCacheConfig.CachePolicy) & uint8_t(UpstreamCachePolicy::Write)) != 0;
+
+ if (UpstreamConfig.UpstreamThreadCount < 32)
+ {
+ UpstreamOptions.ThreadCount = static_cast<uint32_t>(UpstreamConfig.UpstreamThreadCount);
+ }
+
+ m_UpstreamCache = CreateUpstreamCache(UpstreamOptions, *m_CacheStore, *m_CidStore);
+ m_UpstreamService = std::make_unique<HttpUpstreamService>(*m_UpstreamCache, *m_AuthMgr);
+ m_UpstreamCache->Initialize();
+
+ if (ServerOptions.UpstreamCacheConfig.CachePolicy != UpstreamCachePolicy::Disabled)
+ {
+ // Zen upstream
+ {
+ std::vector<std::string> ZenUrls = UpstreamConfig.ZenConfig.Urls;
+ if (!UpstreamConfig.ZenConfig.Dns.empty())
+ {
+ for (const std::string& Dns : UpstreamConfig.ZenConfig.Dns)
+ {
+ if (!Dns.empty())
+ {
+ const asio::error_code Err = utils::ResolveHostname(m_IoContext, Dns, "8558"sv, ZenUrls);
+ if (Err)
+ {
+ ZEN_ERROR("resolve of '{}' FAILED, reason '{}'", Dns, Err.message());
+ }
+ }
+ }
+ }
+
+ std::erase_if(ZenUrls, [](const auto& Url) { return Url.empty(); });
+
+ if (!ZenUrls.empty())
+ {
+ const auto ZenEndpointName = UpstreamConfig.ZenConfig.Name.empty() ? "Zen"sv : UpstreamConfig.ZenConfig.Name;
+
+ std::unique_ptr<UpstreamEndpoint> ZenEndpoint = UpstreamEndpoint::CreateZenEndpoint(
+ {.Name = ZenEndpointName,
+ .Urls = ZenUrls,
+ .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds),
+ .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)});
+
+ m_UpstreamCache->RegisterEndpoint(std::move(ZenEndpoint));
+ }
+ }
+
+ // Jupiter upstream
+ if (UpstreamConfig.JupiterConfig.Url.empty() == false)
+ {
+ std::string_view EndpointName = UpstreamConfig.JupiterConfig.Name.empty() ? "Jupiter"sv : UpstreamConfig.JupiterConfig.Name;
+
+ auto Options = JupiterClientOptions{.Name = EndpointName,
+ .ServiceUrl = UpstreamConfig.JupiterConfig.Url,
+ .DdcNamespace = UpstreamConfig.JupiterConfig.DdcNamespace,
+ .BlobStoreNamespace = UpstreamConfig.JupiterConfig.Namespace,
+ .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds),
+ .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)};
+
+ auto AuthConfig = UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.JupiterConfig.OAuthUrl,
+ .OAuthClientId = UpstreamConfig.JupiterConfig.OAuthClientId,
+ .OAuthClientSecret = UpstreamConfig.JupiterConfig.OAuthClientSecret,
+ .OpenIdProvider = UpstreamConfig.JupiterConfig.OpenIdProvider,
+ .AccessToken = UpstreamConfig.JupiterConfig.AccessToken};
+
+ std::unique_ptr<UpstreamEndpoint> JupiterEndpoint = UpstreamEndpoint::CreateJupiterEndpoint(Options, AuthConfig, *m_AuthMgr);
+
+ m_UpstreamCache->RegisterEndpoint(std::move(JupiterEndpoint));
+ }
+ }
+
+ m_StructuredCacheService = std::make_unique<HttpStructuredCacheService>(*m_CacheStore,
+ *m_CidStore,
+ m_StatsService,
+ m_StatusService,
+ *m_UpstreamCache,
+ m_GcManager.GetDiskWriteBlocker(),
+ *m_OpenProcessCache);
+
+ m_StatsReporter.AddProvider(m_CacheStore.Get());
+ m_StatsReporter.AddProvider(m_CidStore.get());
+ m_StatsReporter.AddProvider(m_BuildCidStore.get());
+}
+
+void
+ZenStorageServer::Run()
+{
+ if (m_ProcessMonitor.IsActive())
+ {
+ CheckOwnerPid();
+ }
+
+ if (!m_TestMode)
+ {
+ ZEN_INFO(
+ "__________ _________ __ \n"
+ "\\____ /____ ____ / _____// |_ ___________ ____ \n"
+ " / // __ \\ / \\ \\_____ \\\\ __\\/ _ \\_ __ \\_/ __ \\ \n"
+ " / /\\ ___/| | \\ / \\| | ( <_> ) | \\/\\ ___/ \n"
+ "/_______ \\___ >___| / /_______ /|__| \\____/|__| \\___ >\n"
+ " \\/ \\/ \\/ \\/ \\/ \n");
+ }
+
+ ZEN_INFO(ZEN_APP_NAME " now running (pid: {})", GetCurrentProcessId());
+
+#if ZEN_PLATFORM_WINDOWS
+ if (zen::windows::IsRunningOnWine())
+ {
+ ZEN_INFO("detected Wine session - " ZEN_APP_NAME " is not formally tested on Wine and may therefore not work or perform well");
+ }
+#endif
+
+#if ZEN_USE_SENTRY
+ ZEN_INFO("sentry crash handler {}", m_UseSentry ? "ENABLED" : "DISABLED");
+ if (m_UseSentry)
+ {
+ SentryIntegration::ClearCaches();
+ }
+#endif
+
+ if (m_DebugOptionForcedCrash)
+ {
+ ZEN_DEBUG_BREAK();
+ }
+
+ const bool IsInteractiveMode = IsInteractiveSession() && !m_TestMode;
+
+ SetNewState(kRunning);
+
+ OnReady();
+
+ if (!m_StartupScrubOptions.empty())
+ {
+ using namespace std::literals;
+
+ ZEN_INFO("triggering scrub with settings: '{}'", m_StartupScrubOptions);
+
+ bool DoScrub = true;
+ bool DoWait = false;
+ GcScheduler::TriggerScrubParams ScrubParams;
+
+ ForEachStrTok(m_StartupScrubOptions, ',', [&](std::string_view Token) {
+ if (Token == "nocas"sv)
+ {
+ ScrubParams.SkipCas = true;
+ }
+ else if (Token == "nodelete"sv)
+ {
+ ScrubParams.SkipDelete = true;
+ }
+ else if (Token == "nogc"sv)
+ {
+ ScrubParams.SkipGc = true;
+ }
+ else if (Token == "no"sv)
+ {
+ DoScrub = false;
+ }
+ else if (Token == "wait"sv)
+ {
+ DoWait = true;
+ }
+ return true;
+ });
+
+ if (DoScrub)
+ {
+ m_GcScheduler.TriggerScrub(ScrubParams);
+
+ if (DoWait)
+ {
+ auto State = m_GcScheduler.Status();
+
+ while ((State != GcSchedulerStatus::kRunning) && (State != GcSchedulerStatus::kStopped))
+ {
+ Sleep(500);
+
+ State = m_GcScheduler.Status();
+ }
+
+ ZEN_INFO("waiting for Scrub/GC to complete...");
+
+ while (State == GcSchedulerStatus::kRunning)
+ {
+ Sleep(500);
+
+ State = m_GcScheduler.Status();
+ }
+
+ ZEN_INFO("Scrub/GC completed");
+ }
+ }
+ }
+
+ if (m_IsPowerCycle)
+ {
+ ZEN_INFO("Power cycle mode enabled -- shutting down");
+ RequestExit(0);
+ }
+
+ m_Http->Run(IsInteractiveMode);
+
+ SetNewState(kShuttingDown);
+
+ ZEN_INFO(ZEN_APP_NAME " exiting");
+}
+
+void
+ZenStorageServer::Cleanup()
+{
+ ZEN_TRACE_CPU("ZenStorageServer::Cleanup");
+ ZEN_INFO(ZEN_APP_NAME " cleaning up");
+ try
+ {
+ m_IoContext.stop();
+ if (m_IoRunner.joinable())
+ {
+ m_IoRunner.join();
+ }
+
+ if (m_Http)
+ {
+ m_Http->Close();
+ }
+
+ if (m_JobQueue)
+ {
+ m_JobQueue->Stop();
+ }
+
+ m_StatsReporter.Shutdown();
+ m_GcScheduler.Shutdown();
+
+ Flush();
+
+ m_AdminService.reset();
+ m_VfsService.reset();
+ m_VfsServiceImpl.reset();
+ m_ObjStoreService.reset();
+ m_FrontendService.reset();
+
+ m_BuildStoreService.reset();
+ m_BuildStore = {};
+ m_BuildCidStore.reset();
+
+ m_StructuredCacheService.reset();
+ m_UpstreamService.reset();
+ m_UpstreamCache.reset();
+ m_CacheStore = {};
+ m_OpenProcessCache.reset();
+
+ m_HttpWorkspacesService.reset();
+ m_Workspaces.reset();
+ m_HttpProjectService.reset();
+ m_ProjectStore = {};
+ m_CidStore.reset();
+ m_AuthService.reset();
+ m_AuthMgr.reset();
+ m_Http = {};
+
+ ShutdownWorkerPools();
+
+ m_JobQueue.reset();
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_ERROR("exception thrown during Cleanup() in {}: '{}'", ZEN_APP_NAME, Ex.what());
+ }
+}
+
+void
+ZenStorageServer::EnqueueStateMarkerTimer()
+{
+ ZEN_MEMSCOPE(GetZenserverTag());
+ m_StateMarkerTimer.expires_after(std::chrono::seconds(5));
+ m_StateMarkerTimer.async_wait([this](const asio::error_code&) { CheckStateMarker(); });
+ EnsureIoRunner();
+}
+
+void
+ZenStorageServer::CheckStateMarker()
+{
+ ZEN_MEMSCOPE(GetZenserverTag());
+ std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker";
+ try
+ {
+ if (!IsFile(StateMarkerPath))
+ {
+ ZEN_WARN("state marker at {} has been deleted, exiting", StateMarkerPath);
+ RequestExit(1);
+ return;
+ }
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("state marker at {} could not be checked, reason: '{}'", StateMarkerPath, Ex.what());
+ RequestExit(1);
+ return;
+ }
+ EnqueueStateMarkerTimer();
+}
+
+void
+ZenStorageServer::Flush()
+{
+ ZEN_TRACE_CPU("ZenStorageServer::Flush");
+
+ if (m_CidStore)
+ m_CidStore->Flush();
+
+ if (m_StructuredCacheService)
+ m_StructuredCacheService->Flush();
+
+ if (m_ProjectStore)
+ m_ProjectStore->Flush();
+
+ if (m_BuildCidStore)
+ m_BuildCidStore->Flush();
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+ZenStorageServerMain::ZenStorageServerMain(ZenStorageServerOptions& ServerOptions)
+: ZenServerMain(ServerOptions)
+, m_ServerOptions(ServerOptions)
+{
+}
+
+void
+ZenStorageServerMain::DoRun(ZenServerState::ZenServerEntry* Entry)
+{
+ ZenStorageServer Server;
+ Server.SetDataRoot(m_ServerOptions.DataDir);
+ Server.SetContentRoot(m_ServerOptions.ContentDir);
+ Server.SetTestMode(m_ServerOptions.IsTest);
+ Server.SetDedicatedMode(m_ServerOptions.IsDedicated);
+
+ auto ServerCleanup = MakeGuard([&Server] { Server.Cleanup(); });
+
+ int EffectiveBasePort = Server.Initialize(m_ServerOptions, Entry);
+ if (EffectiveBasePort == -1)
+ {
+ // Server.Initialize has already logged what the issue is - just exit with failure code here.
+ std::exit(1);
+ }
+
+ Entry->EffectiveListenPort = uint16_t(EffectiveBasePort);
+ if (EffectiveBasePort != m_ServerOptions.BasePort)
+ {
+ ZEN_INFO(ZEN_APP_NAME " - relocated to base port {}", EffectiveBasePort);
+ m_ServerOptions.BasePort = EffectiveBasePort;
+ }
+
+ std::unique_ptr<std::thread> ShutdownThread;
+ std::unique_ptr<NamedEvent> ShutdownEvent;
+
+ ExtendableStringBuilder<64> ShutdownEventName;
+ ShutdownEventName << "Zen_" << m_ServerOptions.BasePort << "_Shutdown";
+ ShutdownEvent.reset(new NamedEvent{ShutdownEventName});
+
+ // Monitor shutdown signals
+
+ ShutdownThread.reset(new std::thread{[&] {
+ SetCurrentThreadName("shutdown_monitor");
+
+ ZEN_INFO("shutdown monitor thread waiting for shutdown signal '{}' for process {}", ShutdownEventName, zen::GetCurrentProcessId());
+
+ if (ShutdownEvent->Wait())
+ {
+ ZEN_INFO("shutdown signal for pid {} received", zen::GetCurrentProcessId());
+ Server.RequestExit(0);
+ }
+ else
+ {
+ ZEN_INFO("shutdown signal wait() failed");
+ }
+ }});
+
+ auto CleanupShutdown = MakeGuard([&ShutdownEvent, &ShutdownThread] {
+ ReportServiceStatus(ServiceStatus::Stopping);
+
+ if (ShutdownEvent)
+ {
+ ShutdownEvent->Set();
+ }
+ if (ShutdownThread && ShutdownThread->joinable())
+ {
+ ShutdownThread->join();
+ }
+ });
+
+ // If we have a parent process, establish the mechanisms we need
+ // to be able to communicate readiness with the parent
+
+ Server.SetIsReadyFunc([&] {
+ std::error_code Ec;
+ m_LockFile.Update(MakeLockData(true), Ec);
+ ReportServiceStatus(ServiceStatus::Running);
+ NotifyReady();
+ });
+
+ Server.Run();
+}
+
+} // namespace zen
diff --git a/src/zenserver/zenstorageserver.h b/src/zenserver/zenstorageserver.h
new file mode 100644
index 000000000..e4c31399d
--- /dev/null
+++ b/src/zenserver/zenstorageserver.h
@@ -0,0 +1,113 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "zenserver.h"
+
+#include <zenhttp/auth/authmgr.h>
+#include <zenhttp/auth/authservice.h>
+#include <zenhttp/httptest.h>
+#include <zenstore/cache/structuredcachestore.h>
+#include <zenstore/gc.h>
+#include <zenstore/projectstore.h>
+
+#include "admin/admin.h"
+#include "buildstore/httpbuildstore.h"
+#include "cache/httpstructuredcache.h"
+#include "diag/diagsvcs.h"
+#include "frontend/frontend.h"
+#include "objectstore/objectstore.h"
+#include "projectstore/httpprojectstore.h"
+#include "stats/statsreporter.h"
+#include "upstream/upstream.h"
+#include "vfs/vfsservice.h"
+#include "workspaces/httpworkspaces.h"
+
+namespace zen {
+
+class ZenStorageServer : public ZenServerBase
+{
+ ZenStorageServer& operator=(ZenStorageServer&&) = delete;
+ ZenStorageServer(ZenStorageServer&&) = delete;
+
+public:
+ ZenStorageServer();
+ ~ZenStorageServer();
+
+ void SetDedicatedMode(bool State) { m_IsDedicatedMode = State; }
+ void SetTestMode(bool State) { m_TestMode = State; }
+ void SetDataRoot(std::filesystem::path Root) { m_DataRoot = Root; }
+ void SetContentRoot(std::filesystem::path Root) { m_ContentRoot = Root; }
+
+ int Initialize(const ZenStorageServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry);
+ void Run();
+ void Cleanup();
+
+private:
+ void InitializeState(const ZenStorageServerOptions& ServerOptions);
+ void InitializeStructuredCache(const ZenStorageServerOptions& ServerOptions);
+ void Flush();
+
+ bool m_IsDedicatedMode = false;
+ bool m_TestMode = false;
+ bool m_DebugOptionForcedCrash = false;
+ std::string m_StartupScrubOptions;
+ CbObject m_RootManifest;
+ std::filesystem::path m_DataRoot;
+ std::filesystem::path m_ContentRoot;
+ asio::steady_timer m_StateMarkerTimer{m_IoContext};
+
+ void EnqueueStateMarkerTimer();
+ void CheckStateMarker();
+
+ std::unique_ptr<AuthMgr> m_AuthMgr;
+ std::unique_ptr<HttpAuthService> m_AuthService;
+ void InitializeAuthentication(const ZenStorageServerOptions& ServerOptions);
+
+ void InitializeServices(const ZenStorageServerOptions& ServerOptions);
+ void RegisterServices();
+
+ HttpStatsService m_StatsService;
+ std::unique_ptr<JobQueue> m_JobQueue;
+ GcManager m_GcManager;
+ GcScheduler m_GcScheduler{m_GcManager};
+ std::unique_ptr<CidStore> m_CidStore;
+ Ref<ZenCacheStore> m_CacheStore;
+ std::unique_ptr<OpenProcessCache> m_OpenProcessCache;
+ HttpTestService m_TestService;
+ std::unique_ptr<CidStore> m_BuildCidStore;
+ std::unique_ptr<BuildStore> m_BuildStore;
+
+#if ZEN_WITH_TESTS
+ HttpTestingService m_TestingService;
+#endif
+
+ RefPtr<ProjectStore> m_ProjectStore;
+ std::unique_ptr<VfsServiceImpl> m_VfsServiceImpl;
+ std::unique_ptr<HttpProjectService> m_HttpProjectService;
+ std::unique_ptr<Workspaces> m_Workspaces;
+ std::unique_ptr<HttpWorkspacesService> m_HttpWorkspacesService;
+ std::unique_ptr<UpstreamCache> m_UpstreamCache;
+ std::unique_ptr<HttpUpstreamService> m_UpstreamService;
+ std::unique_ptr<HttpStructuredCacheService> m_StructuredCacheService;
+ std::unique_ptr<HttpFrontendService> m_FrontendService;
+ std::unique_ptr<HttpObjectStoreService> m_ObjStoreService;
+ std::unique_ptr<HttpBuildStoreService> m_BuildStoreService;
+ std::unique_ptr<VfsService> m_VfsService;
+ std::unique_ptr<HttpAdminService> m_AdminService;
+};
+
+class ZenStorageServerMain : public ZenServerMain
+{
+public:
+ ZenStorageServerMain(ZenStorageServerOptions& ServerOptions);
+ virtual void DoRun(ZenServerState::ZenServerEntry* Entry) override;
+
+ ZenStorageServerMain(const ZenStorageServerMain&) = delete;
+ ZenStorageServerMain& operator=(const ZenStorageServerMain&) = delete;
+
+private:
+ ZenStorageServerOptions& m_ServerOptions;
+};
+
+} // namespace zen