diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index 2daef9a2d..9a9668de4 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -45,6 +45,10 @@ public static class Core private static volatile bool _performSnapshot; private static string _snapshotPath; + // 0 = relaunch in-process. Otherwise a restart exits with this code and leaves the relaunch to a + // supervisor; a spawned child loses the console and dies with a systemd unit or container. + private static int _restartExitCode; + // A backstop, not a latency control: the wheel's tick rate already bounds the sleep. // Measured across 1/2/4/8ms; 2 is optimal. private static int _eventLoopIdleWaitMs = 2; @@ -434,7 +438,7 @@ public static class Core ConsoleInputHandler.ReadLine(); } - DoKill(); + DoKill(_restartOnKill); } } @@ -468,6 +472,12 @@ public static class Core HandleClosed(); + if (restart && _restartExitCode != 0) + { + logger.Information("Restarting (exiting with code {ExitCode} for the supervisor)", _restartExitCode); + Environment.Exit(_restartExitCode); + } + if (restart) { try @@ -625,6 +635,19 @@ public static class Core _lateWakePercent = Math.Clamp(lateWakePercent, 0, 100); + // Unix truncates exit codes to a byte, so anything outside 1-255 cannot be told apart reliably. + var restartExitCode = ServerConfiguration.GetSetting("server.restartExitCode", 0); + if (restartExitCode is < 0 or > 255) + { + logger.Warning( + "server.restartExitCode {Value} is outside 1-255; restarts will relaunch the server directly", + restartExitCode + ); + restartExitCode = 0; + } + + _restartExitCode = restartExitCode; + var assemblyPath = Path.Join(BaseDirectory, AssembliesConfiguration); // Load UOContent.dll diff --git a/Projects/UOContent/Misc/CrashGuard.cs b/Projects/UOContent/Misc/CrashGuard.cs index 1a6e12cc1..522fd755e 100644 --- a/Projects/UOContent/Misc/CrashGuard.cs +++ b/Projects/UOContent/Misc/CrashGuard.cs @@ -13,7 +13,7 @@ namespace Server.Misc private static bool Enabled; private static bool SaveBackup; - private static bool RestartServer; // Disable this if using a daemon/service + private static bool RestartServer; // Under a daemon/service, set server.restartExitCode rather than disabling this private static bool GenerateReport; public static void Configure() diff --git a/dev-docs/server-requirements.md b/dev-docs/server-requirements.md index 1447ebf67..2bbfc2f25 100644 --- a/dev-docs/server-requirements.md +++ b/dev-docs/server-requirements.md @@ -126,6 +126,36 @@ See the README for the full supported list. Two things are worth calling out: - **Linux kernel 6.1** or newer (Debian 12 and equivalents). io_uring is used where available, with automatic epoll fallback. +## Restarting under a supervisor + +By default a restart (`restart` console command, `AutoRestart`, `CrashGuard` crash recovery) +launches a fresh copy of the server and exits. That copy is detached from the console you started +the server in, and a systemd unit or container kills it as soon as the original process exits. + +If something else keeps the server running, let it do the relaunch instead. Set a restart exit code +in `Distribution/Configuration/modernuo.json`: + +```json +"settings": { + "server.restartExitCode": "75" +} +``` + +A restart then shuts down and saves as usual and exits with that code (1–255) instead of launching +a copy. A normal `shutdown` still exits with `0`. `75` is a suggestion; any code your supervisor +does not use for something else works. Then restart on that code: + +- **systemd**: `Restart=on-failure` in the `[Service]` section. +- **Docker**: `restart: on-failure` in Compose, or `--restart on-failure`. +- **Shell loop** (Linux/macOS), keeps the server on the same terminal: + ```sh + while :; do dotnet ModernUO.dll; [ $? -eq 75 ] || break; done + ``` +- **PowerShell** (Windows): + ```powershell + do { dotnet ModernUO.dll } while ($LASTEXITCODE -eq 75) + ``` + ## Tuning for a small host | Setting | Default | Why change it |