From eff9de028136b11e544fd81375d1e0e710e22dd6 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:18:25 -0700 Subject: [PATCH] feat: optional restart exit code so a supervisor owns the relaunch (#2705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #2700 (with #2704) A restart (`restart` console command, `AutoRestart`, `CrashGuard`) launches a copy of the server and exits. That copy is detached from the console it was started from: the shell takes the terminal back and console commands stop working. Under systemd or in a container it's killed as soon as the original process exits. - New setting `server.restartExitCode` (off by default). When it's set, a restart saves and shuts down as usual and then exits with that code, so a shell loop, systemd unit or container restart policy can start the server again on the same console. Values outside 1–255 log a warning and fall back to the current behaviour. - Fixes `CrashGuard` crash recovery never restarting the server. Since #1809, `Kill()` only records the request, and the unhandled-exception path called `DoKill()` without it. - Documents systemd, Docker and shell-loop setups in `dev-docs/server-requirements.md`. --- Projects/Server/Main.cs | 25 +++++++++++++++++++++- Projects/UOContent/Misc/CrashGuard.cs | 2 +- dev-docs/server-requirements.md | 30 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) 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 |