From cd62856767dd1f2d4a24e1351f0514f6a24ef49e Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:58:00 -0700 Subject: [PATCH] refactor(console): headless-aware facade over ConsoleInputPump; remove async-void handshake ConsoleInputHandler.Initialize() now checks Core.Headless and skips starting the input thread entirely when stdin isn't a TTY, logging instead. When not headless, it delegates the read loop to ConsoleInputPump (Task 2) rather than running its own async void loop with a two-AutoResetEvent handshake. ReadLine() throws HeadlessConsoleInputException when headless, falls back to a direct Console.ReadLine() during early startup or after the pump loop has ended, and otherwise blocks on the pump. --- .../Server/Console/ConsoleInputHandler.cs | 82 ++++--------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/Projects/Server/Console/ConsoleInputHandler.cs b/Projects/Server/Console/ConsoleInputHandler.cs index ca3d0ce58..0347a6524 100644 --- a/Projects/Server/Console/ConsoleInputHandler.cs +++ b/Projects/Server/Console/ConsoleInputHandler.cs @@ -24,13 +24,9 @@ namespace Server; public static class ConsoleInputHandler { - private static readonly AutoResetEvent _receivedUserInput = new(false); - private static readonly AutoResetEvent _endUserInput = new(false); - private static bool _initialized; - private static bool _expectUserInput; + private static ConsoleInputPump _pump; private static readonly Dictionary _inputCommands = new(); private static string[] _commandDescriptions; - private static string _input; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RegisterCommand(string command, string description, Action function) => @@ -86,9 +82,15 @@ public static class ConsoleInputHandler [CallPriority(0)] public static void Initialize() { - _initialized = true; + if (Core.Headless) + { + logger.Information("Console input disabled (headless: stdin is not a TTY)."); + return; + } - new Thread(ProcessConsoleInput) + _pump = new ConsoleInputPump(Console.In, GetInputCommand); + + new Thread(_pump.Run) { IsBackground = true, Name = "Console Input Handler" @@ -150,69 +152,21 @@ public static class ConsoleInputHandler private static readonly ILogger logger = LogFactory.GetLogger(typeof(ConsoleInputHandler)); - private static async void ProcessConsoleInput() - { - while (!Core.Closing) - { - string input; - try - { - input = Console.ReadLine()?.Trim(); - } - catch - { - logger.Warning("Console commands have been disabled due to an error."); - _initialized = false; - return; - } - - if (Volatile.Read(ref _expectUserInput)) - { - _input = input; - _receivedUserInput.Set(); - _endUserInput.WaitOne(); - continue; - } - - if (string.IsNullOrEmpty(input)) - { - continue; - } - - var splitInput = input.Split(' ', 2); - var command = splitInput[0].ToLower(); - - try - { - Action action; - lock (_inputCommands) - { - action = _inputCommands.GetValueOrDefault(command)?.Function; - } - - action?.Invoke(splitInput.Length > 1 ? splitInput[1] : string.Empty); - } - catch (Exception e) - { - logger.Error(e, "Failed to execute console command: {Command}", input); - } - } - } - public static string ReadLine() { - if (!_initialized) + if (Core.Headless) + { + throw new HeadlessConsoleInputException("ConsoleInputHandler.ReadLine"); + } + + // Early startup (before Initialize) or after the loop ended: read directly. + var pump = _pump; + if (pump is not { Running: true }) { return Console.ReadLine(); } - Volatile.Write(ref _expectUserInput, true); - _receivedUserInput.WaitOne(); - var line = _input; - Volatile.Write(ref _expectUserInput, false); - _endUserInput.Set(); - - return line; + return pump.ReadLine(); } private class ConsoleCommand(string[] commands, string description, Action function)