// Copyright Epic Games, Inc. All Rights Reserved. #include "zenserver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "diag/logging.h" #include "sentryintegration.h" #if ZEN_USE_MIMALLOC ZEN_THIRD_PARTY_INCLUDES_START # include # include ZEN_THIRD_PARTY_INCLUDES_END #endif #if ZEN_PLATFORM_WINDOWS # include # include "windows/service.h" #endif ////////////////////////////////////////////////////////////////////////// // 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 ZEN_TEST_WITH_RUNNER 1 # include # include #endif #include namespace zen::utils { std::atomic_uint32_t SignalCounter[NSIG] = {0}; static void SignalCallbackHandler(int SigNum) { if (SigNum >= 0 && SigNum < NSIG) { SignalCounter[SigNum].fetch_add(1); } } } // namespace zen::utils namespace zen { using namespace std::literals; //////////////////////////////////////////////////////////////////////////////// class ZenEntryPoint { public: ZenEntryPoint(ZenServerOptions& ServerOptions); ZenEntryPoint(const ZenEntryPoint&) = delete; ZenEntryPoint& operator=(const ZenEntryPoint&) = delete; int Run(); private: ZenServerOptions& m_ServerOptions; LockFile m_LockFile; }; ZenEntryPoint::ZenEntryPoint(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions) { } int ZenEntryPoint::Run() { zen::SetCurrentThreadName("main"); #if ZEN_USE_SENTRY SentryIntegration Sentry; if (m_ServerOptions.NoSentry == false) { std::string SentryDatabasePath = PathToUtf8(m_ServerOptions.DataDir / ".sentry-native"); std::string SentryAttachmentPath = PathToUtf8(m_ServerOptions.AbsLogFile); Sentry.Initialize(SentryDatabasePath, SentryAttachmentPath, m_ServerOptions.SentryAllowPII); } #endif try { // Mutual exclusion and synchronization ZenServerState ServerState; ServerState.Initialize(); ServerState.Sweep(); ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(m_ServerOptions.BasePort); if (Entry) { if (m_ServerOptions.OwnerPid) { 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); Entry->AddSponsorProcess(m_ServerOptions.OwnerPid); std::exit(0); } else { ZEN_WARN("Exiting since 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"; bool IsReady = false; auto MakeLockData = [&] { CbObjectWriter Cbo; Cbo << "pid" << GetCurrentProcessId() << "data" << PathToUtf8(m_ServerOptions.DataDir) << "port" << m_ServerOptions.BasePort << "session_id" << GetSessionId() << "ready" << IsReady; return Cbo.Save(); }; m_LockFile.Create(LockFilePath, MakeLockData(), Ec); if (Ec) { ZEN_WARN("ERROR: Unable to grab lock at '{}' (error: '{}')", LockFilePath, Ec.message()); std::exit(99); } InitializeServerLogging(m_ServerOptions); #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) { Entry->AddSponsorProcess(m_ServerOptions.OwnerPid); } ZenServer 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); 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 ShutdownThread; std::unique_ptr 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()) { if (!IsApplicationExitRequested()) { ZEN_INFO("shutdown signal for pid {} received", zen::GetCurrentProcessId()); Server.RequestExit(0); } } else { ZEN_INFO("shutdown signal wait() failed"); } }}); auto CleanupShutdown = MakeGuard([&ShutdownEvent, &ShutdownThread] { 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([&] { IsReady = true; m_LockFile.Update(MakeLockData(), Ec); if (!m_ServerOptions.ChildId.empty()) { NamedEvent ParentEvent{m_ServerOptions.ChildId}; ParentEvent.Set(); } }); Server.Run(); } catch (std::exception& e) { ZEN_CRITICAL("Caught exception in main for process {}: {}", zen::GetCurrentProcessId(), e.what()); if (!IsApplicationExitRequested()) { RequestApplicationExit(1); } } ShutdownServerLogging(); return ApplicationExitCode(); } ////////////////////////////////////////////////////////////////////////// #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: ZenEntryPoint m_EntryPoint; }; int ZenWindowsService::Run() { return m_EntryPoint.Run(); } #endif // ZEN_PLATFORM_WINDOWS } // namespace zen ////////////////////////////////////////////////////////////////////////// #if ZEN_WITH_TESTS int test_main(int argc, char** argv) { zen::zencore_forcelinktests(); zen::zenhttp_forcelinktests(); zen::zenstore_forcelinktests(); zen::zenutil_forcelinktests(); zen::z$_forcelink(); zen::z$service_forcelink(); zen::logging::InitializeLogging(); zen::logging::SetLogLevel(zen::logging::level::Debug); zen::MaximizeOpenFileCount(); return ZEN_RUN_TESTS(argc, argv); } #endif int main(int argc, char* argv[]) { using namespace zen; #if ZEN_USE_MIMALLOC mi_version(); #endif if (argc >= 2) { if (argv[1] == "test"sv) { #if ZEN_WITH_TESTS return test_main(argc, argv); #else fprintf(stderr, "test option not available in release mode!\n"); exit(5); #endif } } signal(SIGINT, utils::SignalCallbackHandler); try { ZenServerOptions ServerOptions; ParseCliOptions(argc, argv, ServerOptions); std::string_view DeleteReason; if (ServerOptions.IsCleanStart) { DeleteReason = "clean start requested"sv; } else if (!ServerOptions.BaseSnapshotDir.empty()) { DeleteReason = "will initialize state from base snapshot"sv; } if (!DeleteReason.empty()) { if (std::filesystem::exists(ServerOptions.DataDir)) { ZEN_CONSOLE_INFO("deleting files from '{}' ({})", ServerOptions.DataDir, DeleteReason); DeleteDirectories(ServerOptions.DataDir); } } if (!std::filesystem::exists(ServerOptions.DataDir)) { ServerOptions.IsFirstRun = true; std::filesystem::create_directories(ServerOptions.DataDir); } if (!ServerOptions.BaseSnapshotDir.empty()) { ZEN_CONSOLE_INFO("copying snapshot from '{}' into '{}", ServerOptions.BaseSnapshotDir, ServerOptions.DataDir); CopyTree(ServerOptions.BaseSnapshotDir, ServerOptions.DataDir, {.EnableClone = true}); } #if ZEN_WITH_TRACE if (ServerOptions.TraceHost.size()) { TraceStart("zenserver", ServerOptions.TraceHost.c_str(), TraceType::Network); } else if (ServerOptions.TraceFile.size()) { TraceStart("zenserver", ServerOptions.TraceFile.c_str(), TraceType::File); } else { TraceInit("zenserver"); } atexit(TraceShutdown); #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 } catch (std::exception& Ex) { fprintf(stderr, "ERROR: Caught exception in main: '%s'", Ex.what()); return 1; } }