// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ZEN_PLATFORM_WINDOWS # include #endif #if ZEN_USE_MIMALLOC ZEN_THIRD_PARTY_INCLUDES_START # include # include ZEN_THIRD_PARTY_INCLUDES_END #endif ZEN_THIRD_PARTY_INCLUDES_START #include #include #include ZEN_THIRD_PARTY_INCLUDES_END #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// // We don't have any doctest code in this file but this is needed to bring // in some shared code into the executable #if ZEN_WITH_TESTS # define DOCTEST_CONFIG_IMPLEMENT # include # undef DOCTEST_CONFIG_IMPLEMENT #endif ////////////////////////////////////////////////////////////////////////// #include "casstore.h" #include "config.h" #include "diag/logging.h" #if ZEN_PLATFORM_WINDOWS # include "windows/service.h" #endif ////////////////////////////////////////////////////////////////////////// // Sentry // #define USE_SENTRY 1 #if USE_SENTRY # define SENTRY_BUILD_STATIC 1 ZEN_THIRD_PARTY_INCLUDES_START # include ZEN_THIRD_PARTY_INCLUDES_END // Sentry currently does not automatically add all required Windows // libraries to the linker when consumed via vcpkg # if ZEN_PLATFORM_WINDOWS # pragma comment(lib, "sentry.lib") # pragma comment(lib, "dbghelp.lib") # pragma comment(lib, "winhttp.lib") # pragma comment(lib, "version.lib") # endif #endif ////////////////////////////////////////////////////////////////////////// // Services // #include "admin/admin.h" #include "cache/structuredcache.h" #include "cache/structuredcachestore.h" #include "compute/apply.h" #include "diag/diagsvcs.h" #include "experimental/frontend.h" #include "experimental/usnjournal.h" #include "monitoring/httpstats.h" #include "monitoring/httpstatus.h" #include "projectstore.h" #include "testing/httptest.h" #include "testing/launch.h" #include "upstream/jupiter.h" #include "upstream/upstreamcache.h" #include "upstream/zen.h" #include "zenstore/gc.h" #include "zenstore/scrub.h" #define ZEN_APP_NAME "Zen store" namespace zen { using namespace std::literals; namespace utils { asio::error_code ResolveHostname(asio::io_context& Ctx, std::string_view Host, std::string_view DefaultPort, std::vector& 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 class ZenServer : public IHttpStatusProvider { public: void Initialize(const ZenServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry) { m_ServerEntry = ServerEntry; m_DebugOptionForcedCrash = ServerOptions.ShouldCrash; const int ParentPid = ServerOptions.OwnerPid; if (ParentPid) { zen::ProcessHandle OwnerProcess; OwnerProcess.Initialize(ParentPid); if (!OwnerProcess.IsValid()) { ZEN_WARN("Unable to initialize process handle for specified parent pid #{}", ParentPid); // If the pid is not reachable should we just shut down immediately? the intended owner process // could have been killed or somehow crashed already } else { ZEN_INFO("Using parent pid #{} to control process lifetime", ParentPid); } m_ProcessMonitor.AddPid(ParentPid); } // Initialize/check mutex based on base port std::string MutexName = fmt::format("zen_{}", ServerOptions.BasePort); if (zen::NamedMutex::Exists(MutexName) || ((m_ServerMutex.Create(MutexName) == false))) { throw std::runtime_error(fmt::format("Failed to create mutex '{}' - is another instance already running?", MutexName).c_str()); } InitializeState(ServerOptions); m_HealthService.SetHealthInfo({.DataRoot = m_DataRoot, .AbsLogPath = ServerOptions.AbsLogFile, .HttpServerClass = std::string(ServerOptions.HttpServerClass), .BuildVersion = std::string(ZEN_CFG_VERSION_BUILD_STRING_FULL)}); // Ok so now we're configured, let's kick things off m_Http = zen::CreateHttpServer(ServerOptions.HttpServerClass); m_Http->Initialize(ServerOptions.BasePort); m_Http->RegisterService(m_HealthService); m_Http->RegisterService(m_StatsService); m_Http->RegisterService(m_StatusService); m_StatusService.RegisterHandler("status", *this); // Initialize storage and services ZEN_INFO("initializing storage"); zen::CasStoreConfiguration Config; Config.RootDirectory = m_DataRoot / "cas"; m_CasStore->Initialize(Config); m_CidStore = std::make_unique(*m_CasStore, m_DataRoot / "cid"); m_CasGc.SetCidStore(m_CidStore.get()); ZEN_INFO("instantiating project service"); m_ProjectStore = new zen::ProjectStore(*m_CidStore, m_DataRoot / "projects", m_CasGc); m_HttpProjectService.reset(new zen::HttpProjectService{*m_CidStore, m_ProjectStore}); #if ZEN_USE_NAMED_PIPES m_LocalProjectService = zen::LocalProjectService::New(*m_CasStore, m_ProjectStore); #endif #if ZEN_USE_EXEC std::filesystem::path SandboxDir = m_DataRoot / "exec" / "sandbox"; zen::CreateDirectories(SandboxDir); m_HttpLaunchService = std::make_unique(*m_CasStore, SandboxDir); #endif #if ZEN_WITH_COMPUTE_SERVICES ZEN_INFO("instantiating compute services"); std::filesystem::path SandboxDir = m_DataRoot / "exec" / "sandbox"; zen::CreateDirectories(SandboxDir); m_HttpLaunchService = std::make_unique(*m_CasStore, SandboxDir); std::filesystem::path ApplySandboxDir = m_DataRoot / "exec" / "apply"; zen::CreateDirectories(ApplySandboxDir); m_HttpFunctionService = std::make_unique(*m_CasStore, *m_CidStore, ApplySandboxDir); #endif // ZEN_WITH_COMPUTE_SERVICES if (ServerOptions.StructuredCacheEnabled) { InitializeStructuredCache(ServerOptions); } else { ZEN_INFO("NOT instantiating structured cache service"); } #if ZEN_ENABLE_MESH if (ServerOptions.MeshEnabled) { StartMesh(BasePort); } else { ZEN_INFO("NOT starting mesh"); } #endif m_Http->RegisterService(m_TestService); // NOTE: this is intentionally not limited to test mode as it's useful for diagnostics m_Http->RegisterService(m_TestingService); m_Http->RegisterService(m_AdminService); if (m_HttpProjectService) { m_Http->RegisterService(*m_HttpProjectService); } m_Http->RegisterService(m_CasService); if (m_StructuredCacheService) { m_Http->RegisterService(*m_StructuredCacheService); } #if ZEN_WITH_COMPUTE_SERVICES if (m_HttpLaunchService) { m_Http->RegisterService(*m_HttpLaunchService); } if (m_HttpFunctionService) { m_Http->RegisterService(*m_HttpFunctionService); } #endif // ZEN_WITH_COMPUTE_SERVICES m_FrontendService = std::make_unique(m_ContentRoot); if (m_FrontendService) { m_Http->RegisterService(*m_FrontendService); } ZEN_INFO("initializing GC, enabled '{}', interval {}s", ServerOptions.GcConfig.Enabled, ServerOptions.GcConfig.IntervalSeconds); zen::GcSchedulerConfig GcConfig{ .RootDirectory = m_DataRoot / "gc", .Interval = std::chrono::seconds(ServerOptions.GcConfig.IntervalSeconds), .MaxCacheDuration = std::chrono::seconds(ServerOptions.GcConfig.Cache.MaxDurationSeconds), .CollectSmallObjects = ServerOptions.GcConfig.CollectSmallObjects, .Enabled = ServerOptions.GcConfig.Enabled, }; m_GcScheduler.Initialize(GcConfig); } void InitializeState(const ZenServerOptions& ServerOptions); void InitializeStructuredCache(const ZenServerOptions& ServerOptions); #if ZEN_ENABLE_MESH void StartMesh(int BasePort) { ZEN_INFO("initializing mesh discovery"); m_ZenMesh.Start(uint16_t(BasePort)); } #endif void Run() { // This is disabled for now, awaiting better scheduling // // Scrub(); if (m_ProcessMonitor.IsActive()) { EnqueueTimer(); } if (!m_TestMode) { ZEN_INFO("__________ _________ __ "); ZEN_INFO("\\____ /____ ____ / _____// |_ ___________ ____ "); ZEN_INFO(" / // __ \\ / \\ \\_____ \\\\ __\\/ _ \\_ __ \\_/ __ \\ "); ZEN_INFO(" / /\\ ___/| | \\ / \\| | ( <_> ) | \\/\\ ___/ "); ZEN_INFO("/_______ \\___ >___| / /_______ /|__| \\____/|__| \\___ >"); ZEN_INFO(" \\/ \\/ \\/ \\/ \\/ "); } ZEN_INFO(ZEN_APP_NAME " now running (pid: {})", zen::GetCurrentProcessId()); #if USE_SENTRY sentry_clear_modulecache(); #endif if (m_DebugOptionForcedCrash) { ZEN_DEBUG_BREAK(); } const bool IsInteractiveMode = zen::IsInteractiveSession() && !m_TestMode; SetNewState(kRunning); OnReady(); m_Http->Run(IsInteractiveMode); SetNewState(kShuttingDown); ZEN_INFO(ZEN_APP_NAME " exiting"); m_IoContext.stop(); Flush(); } void RequestExit(int ExitCode) { RequestApplicationExit(ExitCode); m_Http->RequestExit(); } void Cleanup() { ZEN_INFO(ZEN_APP_NAME " cleaning up"); } 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; } std::function m_IsReadyFunc; void SetIsReadyFunc(std::function&& IsReadyFunc) { m_IsReadyFunc = std::move(IsReadyFunc); } void OnReady(); void EnsureIoRunner() { if (!m_IoRunner.joinable()) { m_IoRunner = std::move(std::jthread{[this] { m_IoContext.run(); }}); } } void EnqueueTimer() { m_PidCheckTimer.expires_after(std::chrono::seconds(1)); m_PidCheckTimer.async_wait([this](const asio::error_code&) { CheckOwnerPid(); }); EnsureIoRunner(); } void CheckOwnerPid() { // Pick up any new "owner" processes std::set 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); } } } } if (m_ProcessMonitor.IsRunning()) { EnqueueTimer(); } else { ZEN_INFO(ZEN_APP_NAME " exiting since sponsor processes are all gone"); RequestExit(0); } } void Scrub() { Stopwatch Timer; ZEN_INFO("Storage validation STARTING"); ScrubContext Ctx; m_CasStore->Scrub(Ctx); m_CidStore->Scrub(Ctx); m_ProjectStore->Scrub(Ctx); m_StructuredCacheService->Scrub(Ctx); const uint64_t ElapsedTimeMs = Timer.GetElapsedTimeMs(); ZEN_INFO("Storage validation DONE in {}, ({} in {} chunks - {})", NiceTimeSpanMs(ElapsedTimeMs), NiceBytes(Ctx.ScrubbedBytes()), Ctx.ScrubbedChunks(), NiceByteRate(Ctx.ScrubbedBytes(), ElapsedTimeMs)); } void Flush() { if (m_CasStore) m_CasStore->Flush(); if (m_CidStore) m_CidStore->Flush(); if (m_StructuredCacheService) m_StructuredCacheService->Flush(); if (m_ProjectStore) m_ProjectStore->Flush(); } virtual void HandleStatusRequest(HttpServerRequest& Request) override { CbObjectWriter Cbo; Cbo << "ok" << true; Cbo << "state" << ToString(m_CurrentState); Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); } private: ZenServerState::ZenServerEntry* m_ServerEntry = nullptr; bool m_IsDedicatedMode = false; bool m_TestMode = false; CbObject m_RootManifest; std::filesystem::path m_DataRoot; std::filesystem::path m_ContentRoot; std::jthread m_IoRunner; asio::io_context m_IoContext; asio::steady_timer m_PidCheckTimer{m_IoContext}; zen::ProcessMonitor m_ProcessMonitor; zen::NamedMutex m_ServerMutex; enum ServerState { kInitializing, kRunning, kShuttingDown } m_CurrentState = kInitializing; inline void SetNewState(ServerState NewState) { m_CurrentState = NewState; } std::string_view ToString(ServerState Value) { switch (Value) { case kInitializing: return "initializing"sv; case kRunning: return "running"sv; case kShuttingDown: return "shutdown"sv; default: return "unknown"sv; } } zen::Ref m_Http; zen::HttpStatusService m_StatusService; zen::HttpStatsService m_StatsService; zen::CasGc m_CasGc; zen::GcScheduler m_GcScheduler{m_CasGc}; std::unique_ptr m_CasStore{zen::CreateCasStore(m_CasGc)}; std::unique_ptr m_CidStore; std::unique_ptr m_CacheStore; zen::CasScrubber m_Scrubber{*m_CasStore}; zen::HttpTestService m_TestService; zen::HttpTestingService m_TestingService; zen::HttpCasService m_CasService{*m_CasStore}; zen::RefPtr m_ProjectStore; zen::Ref m_LocalProjectService; std::unique_ptr m_HttpProjectService; std::unique_ptr m_StructuredCacheService; zen::HttpAdminService m_AdminService{m_GcScheduler}; zen::HttpHealthService m_HealthService; zen::Mesh m_ZenMesh{m_IoContext}; #if ZEN_WITH_COMPUTE_SERVICES std::unique_ptr m_HttpLaunchService; std::unique_ptr m_HttpFunctionService; #endif std::unique_ptr m_FrontendService; #if ZEN_USE_EXEC std::unique_ptr m_HttpLaunchService; #endif #if ZEN_USE_NAMED_PIPES zen::Ref m_LocalProjectService; #endif bool m_DebugOptionForcedCrash = false; }; void ZenServer::OnReady() { m_ServerEntry->SignalReady(); if (m_IsReadyFunc) { m_IsReadyFunc(); } } void ZenServer::InitializeState(const ZenServerOptions& ServerOptions) { // Check root manifest to deal with schema versioning bool WipeState = false; std::string WipeReason = "Unspecified"; bool UpdateManifest = false; std::filesystem::path ManifestPath = m_DataRoot / "root_manifest"; FileContents ManifestData = zen::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_ERROR("Manifest validation failed: {}, state will be wiped", ValidationResult); WipeState = true; WipeReason = fmt::format("Validation of manifest at '{}' failed: {}", ManifestPath, ValidationResult); } else { m_RootManifest = LoadCompactBinaryObject(Manifest); const int32_t ManifestVersion = m_RootManifest["schema_version"].AsInt32(0); if (ManifestVersion != ZEN_CFG_SCHEMA_VERSION) { WipeState = true; WipeReason = fmt::format("Manifest schema version: {}, differs from required: {}", ManifestVersion, ZEN_CFG_SCHEMA_VERSION); } } } // Release any open handles so we can overwrite the manifest ManifestData = {}; // 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()); std::filesystem::remove_all(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; } if (UpdateManifest) { // Write new manifest const DateTime Now = DateTime::Now(); CbObjectWriter Cbo; Cbo << "schema_version" << ZEN_CFG_SCHEMA_VERSION << "created" << Now << "updated" << Now << "state_id" << Oid::NewOid(); m_RootManifest = Cbo.Save(); WriteFile(ManifestPath, m_RootManifest.GetBuffer().AsIoBuffer()); } } void ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) { using namespace std::literals; auto ValueOrDefault = [](std::string_view Value, std::string_view Default) { return Value.empty() ? Default : Value; }; ZEN_INFO("instantiating structured cache service"); m_CacheStore = std::make_unique(m_CasGc, m_DataRoot / "cache"); std::unique_ptr UpstreamCache; if (ServerOptions.UpstreamCacheConfig.CachePolicy != UpstreamCachePolicy::Disabled) { const ZenUpstreamCacheConfig& UpstreamConfig = ServerOptions.UpstreamCacheConfig; zen::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(UpstreamConfig.UpstreamThreadCount); } UpstreamOptions.StatsEnabled = UpstreamConfig.StatsEnabled; UpstreamCache = zen::MakeUpstreamCache(UpstreamOptions, *m_CacheStore, *m_CidStore); // Zen upstream { std::vector 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 = zen::utils::ResolveHostname(m_IoContext, Dns, "1337"sv, ZenUrls); if (Err) { ZEN_ERROR("resolve '{}' FAILED, reason '{}'", Err.message()); } } } } std::erase_if(ZenUrls, [](const auto& Url) { return Url.empty(); }); if (!ZenUrls.empty()) { std::unique_ptr ZenEndpoint = zen::MakeZenUpstreamEndpoint({.Urls = ZenUrls, .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}); UpstreamCache->RegisterEndpoint(std::move(ZenEndpoint)); } } // Jupiter upstream { zen::CloudCacheClientOptions Options; if (UpstreamConfig.JupiterConfig.UseProductionSettings) { Options = zen::CloudCacheClientOptions{.ServiceUrl = "https://jupiter.devtools.epicgames.com"sv, .DdcNamespace = "ue.ddc"sv, .BlobStoreNamespace = "ue.ddc"sv, .OAuthProvider = "https://epicgames.okta.com/oauth2/auso645ojjWVdRI3d0x7/v1/token"sv, .OAuthClientId = "0oao91lrhqPiAlaGD0x7"sv, .OAuthSecret = "-GBWjjenhCgOwhxL5yBKNJECVIoDPH0MK4RDuN7d"sv, .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds), .UseLegacyDdc = false}; } else if (UpstreamConfig.JupiterConfig.UseDevelopmentSettings) { Options = zen::CloudCacheClientOptions{.ServiceUrl = "https://jupiter.devtools-dev.epicgames.com"sv, .DdcNamespace = "ue4.ddc"sv, .BlobStoreNamespace = "test.ddc"sv, .OAuthProvider = "https://epicgames.okta.com/oauth2/auso645ojjWVdRI3d0x7/v1/token"sv, .OAuthClientId = "0oao91lrhqPiAlaGD0x7"sv, .OAuthSecret = "-GBWjjenhCgOwhxL5yBKNJECVIoDPH0MK4RDuN7d"sv, .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds), .UseLegacyDdc = false}; } Options.ServiceUrl = ValueOrDefault(UpstreamConfig.JupiterConfig.Url, Options.ServiceUrl); Options.DdcNamespace = ValueOrDefault(UpstreamConfig.JupiterConfig.DdcNamespace, Options.DdcNamespace); Options.BlobStoreNamespace = ValueOrDefault(UpstreamConfig.JupiterConfig.Namespace, Options.BlobStoreNamespace); Options.OAuthProvider = ValueOrDefault(UpstreamConfig.JupiterConfig.OAuthProvider, Options.OAuthProvider); Options.OAuthClientId = ValueOrDefault(UpstreamConfig.JupiterConfig.OAuthClientId, Options.OAuthClientId); Options.OAuthSecret = ValueOrDefault(UpstreamConfig.JupiterConfig.OAuthClientSecret, Options.OAuthSecret); Options.UseLegacyDdc |= UpstreamConfig.JupiterConfig.UseLegacyDdc; if (!Options.ServiceUrl.empty()) { std::unique_ptr JupiterEndpoint = zen::MakeJupiterUpstreamEndpoint(Options); UpstreamCache->RegisterEndpoint(std::move(JupiterEndpoint)); } } if (UpstreamCache->Initialize()) { ZEN_INFO("upstream cache active ({})", UpstreamOptions.ReadUpstream && UpstreamOptions.WriteUpstream ? "READ|WRITE" : UpstreamOptions.ReadUpstream ? "READONLY" : UpstreamOptions.WriteUpstream ? "WRITEONLY" : "DISABLED"); } else { UpstreamCache.reset(); ZEN_INFO("NOT using upstream cache"); } } m_StructuredCacheService.reset( new zen::HttpStructuredCacheService(*m_CacheStore, *m_CidStore, m_StatsService, m_StatusService, std::move(UpstreamCache))); } //////////////////////////////////////////////////////////////////////////////// class ZenEntryPoint { public: ZenEntryPoint(ZenServerOptions& ServerOptions); ZenEntryPoint(const ZenEntryPoint&) = delete; ZenEntryPoint& operator=(const ZenEntryPoint&) = delete; int Run(); private: ZenServerOptions& m_ServerOptions; zen::LockFile m_LockFile; }; ZenEntryPoint::ZenEntryPoint(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions) { } int ZenEntryPoint::Run() { #if USE_SENTRY // Initialize sentry.io client sentry_options_t* SentryOptions = sentry_options_new(); sentry_options_set_dsn(SentryOptions, "https://8ba3441bebc941c1ae24b8cd2fd25d55@o10593.ingest.sentry.io/5919284"); sentry_options_set_database_path(SentryOptions, PathToUtf8(m_ServerOptions.DataDir / ".sentry-native").c_str()); sentry_init(SentryOptions); auto _ = zen::MakeGuard([] { sentry_close(); }); #endif auto& ServerOptions = m_ServerOptions; try { // Mutual exclusion and synchronization std::error_code Ec; std::filesystem::path LockFilePath = ServerOptions.DataDir / ".lock"; bool IsReady = false; auto MakeLockData = [&] { CbObjectWriter Cbo; Cbo << "pid" << zen::GetCurrentProcessId() << "data" << PathToUtf8(ServerOptions.DataDir) << "port" << ServerOptions.BasePort << "session_id" << GetSessionId() << "ready" << IsReady; return Cbo.Save(); }; m_LockFile.Create(LockFilePath, MakeLockData(), Ec); if (Ec) { ConsoleLog().error("ERROR: Unable to grab lock at '{}' (error: '{}')", LockFilePath, Ec.message()); std::exit(99); } InitializeLogging(ServerOptions); ZEN_INFO(ZEN_APP_NAME " - starting on port {}, version '{}'", ServerOptions.BasePort, ZEN_CFG_VERSION_BUILD_STRING_FULL); ZenServerState ServerState; ServerState.Initialize(); ServerState.Sweep(); ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(ServerOptions.BasePort); if (Entry) { // Instance already running for this port? Should double check pid ZEN_WARN("Looks like there is already a process listening to this port (pid: {})", Entry->Pid); if (ServerOptions.OwnerPid) { Entry->AddSponsorProcess(ServerOptions.OwnerPid); std::exit(0); } } Entry = ServerState.Register(ServerOptions.BasePort); if (ServerOptions.OwnerPid) { Entry->AddSponsorProcess(ServerOptions.OwnerPid); } std::unique_ptr ShutdownThread; std::unique_ptr ShutdownEvent; zen::ExtendableStringBuilder<64> ShutdownEventName; ShutdownEventName << "Zen_" << ServerOptions.BasePort << "_Shutdown"; ShutdownEvent.reset(new zen::NamedEvent{ShutdownEventName}); ZenServer Server; Server.SetDataRoot(ServerOptions.DataDir); Server.SetContentRoot(ServerOptions.ContentDir); Server.SetTestMode(ServerOptions.IsTest); Server.SetDedicatedMode(ServerOptions.IsDedicated); Server.Initialize(ServerOptions, Entry); // Monitor shutdown signals ShutdownThread.reset(new std::thread{[&] { ZEN_INFO("shutdown monitor thread waiting for shutdown signal '{}'", ShutdownEventName); if (ShutdownEvent->Wait()) { ZEN_INFO("shutdown signal received"); Server.RequestExit(0); } else { ZEN_INFO("shutdown signal wait() failed"); } }}); // If we have a parent process, establish the mechanisms we need // to be able to communicate readiness with the parent Server.SetIsReadyFunc([&] { IsReady = true; m_LockFile.Update(MakeLockData(), Ec); if (!ServerOptions.ChildId.empty()) { zen::NamedEvent ParentEvent{ServerOptions.ChildId}; ParentEvent.Set(); } }); Server.Run(); Server.Cleanup(); ShutdownEvent->Set(); ShutdownThread->join(); } catch (std::exception& e) { SPDLOG_CRITICAL("Caught exception in main: {}", e.what()); } ShutdownLogging(); return 0; } } // namespace zen //////////////////////////////////////////////////////////////////////////////// #if ZEN_PLATFORM_WINDOWS class ZenWindowsService : public WindowsService { public: ZenWindowsService(ZenServerOptions& ServerOptions) : m_EntryPoint(ServerOptions) {} ZenWindowsService(const ZenWindowsService&) = delete; ZenWindowsService& operator=(const ZenWindowsService&) = delete; virtual int Run() override; private: zen::ZenEntryPoint m_EntryPoint; }; int ZenWindowsService::Run() { return m_EntryPoint.Run(); } #endif // ZEN_PLATFORM_WINDOWS //////////////////////////////////////////////////////////////////////////////// #if ZEN_WITH_TESTS int test_main(int argc, char** argv) { zen::zencore_forcelinktests(); zen::zenhttp_forcelinktests(); zen::zenstore_forcelinktests(); zen::z$_forcelink(); zen::logging::InitializeLogging(); spdlog::set_level(spdlog::level::debug); return doctest::Context(argc, argv).run(); } #endif int main(int argc, char* argv[]) { using namespace zen; #if ZEN_USE_MIMALLOC mi_version(); #endif #if ZEN_WITH_TESTS if (argc >= 2) { if (argv[1] == "test"sv) { return test_main(argc, argv); } } #endif try { ZenServerOptions ServerOptions; ParseCliOptions(argc, argv, ServerOptions); if (!std::filesystem::exists(ServerOptions.DataDir)) { ServerOptions.IsFirstRun = true; std::filesystem::create_directories(ServerOptions.DataDir); } #if ZEN_WITH_TRACE if (ServerOptions.TraceHost.size()) { TraceInit(ServerOptions.TraceHost.c_str(), TraceType::Network); } else if (ServerOptions.TraceFile.size()) { TraceInit(ServerOptions.TraceFile.c_str(), TraceType::File); } #endif // ZEN_WITH_TRACE #if ZEN_PLATFORM_WINDOWS if (ServerOptions.InstallService) { WindowsService::Install(); std::exit(0); } if (ServerOptions.UninstallService) { WindowsService::Delete(); std::exit(0); } ZenWindowsService App(ServerOptions); return App.ServiceMain(); #else if (ServerOptions.InstallService || ServerOptions.UninstallService) { throw std::runtime_error("Service mode is not supported on this platform"); } ZenEntryPoint App(ServerOptions); return App.Run(); #endif // ZEN_PLATFORM_WINDOWS } catch (std::exception& Ex) { fprintf(stderr, "ERROR: Caught exception in main: '%s'", Ex.what()); return 1; } }