// Copyright Epic Games, Inc. All Rights Reserved. #include "zenserver.h" #include #include #include #include #include #include #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_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 { static const FLLMTag& GetZenserverTag() { static FLLMTag _("zenserver"); return _; } 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 = (m_ServerOptions.DataDir / ".sentry-native").string(); std::string SentryAttachmentPath = m_ServerOptions.AbsLogFile.string(); Sentry.Initialize(SentryDatabasePath, SentryAttachmentPath, m_ServerOptions.SentryAllowPII, 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( "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( "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)) { std::exit(0); } if (AttachSponsorProcessRetriesLeft-- > 0) { Entry = ServerState.Lookup(m_ServerOptions.BasePort); } else { ZEN_WARN("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("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"; auto MakeLockData = [&](bool IsReady) { return MakeLockFilePayload({.Pid = GetCurrentProcessId(), .SessionId = GetSessionId(), .EffectiveListenPort = gsl::narrow(m_ServerOptions.BasePort), .Ready = IsReady, .DataDir = m_ServerOptions.DataDir, .ExecutablePath = GetRunningExecutablePath()}); }; m_LockFile.Create(LockFilePath, MakeLockData(false), Ec); if (Ec) { ZEN_WARN("Unable to grab lock at '{}' (reason: '{}'), retrying", LockFilePath, Ec.message()); Sleep(500); m_LockFile.Create(LockFilePath, MakeLockData(false), Ec); if (Ec) { ZEN_WARN("ERROR: 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); } 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); 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 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()) { 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([&] { m_LockFile.Update(MakeLockData(true), Ec); if (!m_ServerOptions.ChildId.empty()) { NamedEvent ParentEvent{m_ServerOptions.ChildId}; ParentEvent.Set(); } }); Server.Run(); } catch (const AssertException& AssertEx) { ZEN_CRITICAL("Caught assert exception in main for process {}: {}", zen::GetCurrentProcessId(), AssertEx.FullDescription()); RequestApplicationExit(1); } catch (const std::exception& e) { ZEN_CRITICAL("Caught exception in main for process {}: {}", zen::GetCurrentProcessId(), e.what()); 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::zenserver_forcelinktests(); 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 (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); signal(SIGTERM, utils::SignalCallbackHandler); try { ZenServerOptions ServerOptions; ParseCliOptions(argc, argv, ServerOptions); if (ServerOptions.Detach) { #if ZEN_PLATFORM_LINUX | ZEN_PLATFORM_MAC // Detach ourselves from any parent process setsid(); #endif } 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 (IsDir(ServerOptions.DataDir)) { ZEN_CONSOLE_INFO("deleting files from '{}' ({})", ServerOptions.DataDir, DeleteReason); DeleteDirectories(ServerOptions.DataDir); } } if (!IsDir(ServerOptions.DataDir)) { ServerOptions.IsFirstRun = true; CreateDirectories(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"); } #endif // ZEN_WITH_TRACE #if ZEN_WITH_MEMTRACK FMalloc* TraceMalloc = MemoryTrace_Create(GMalloc); if (TraceMalloc != GMalloc) { GMalloc = TraceMalloc; MemoryTrace_Initialize(); } #endif ZEN_MEMSCOPE(GetZenserverTag()); #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 (const OptionParseException& ParseEx) { // The parsing error already outputs all the details so no need to output the command line here fprintf(stderr, "ERROR: %s\n", ParseEx.what()); return 1; } catch (const AssertException& AssertEx) { fprintf(stderr, "ERROR: Caught assert exception in main: '%s'", AssertEx.FullDescription().c_str()); return 1; } catch (const std::exception& Ex) { fprintf(stderr, "ERROR: Caught exception in main: '%s'", Ex.what()); return 1; } }