From 77391538d445a3b0b022162a4e093795b5c93e20 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 28 May 2026 19:49:56 +0100 Subject: [PATCH] Fixes #5442. Windows VT input can hang during shutdown when ReadFile is blocked --- Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs | 5 +++ .../WindowsHelpers/WindowsVTInputHelper.cs | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs b/Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs index 0f0b0253fb..d548c2c04c 100644 --- a/Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs +++ b/Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs @@ -415,6 +415,11 @@ public void Dispose () { Trace.Lifecycle (nameof (AnsiOutput), "Dispose", "Flushing output and releasing resources."); + if (_platform == AnsiPlatform.WindowsVT) + { + WindowsVTInputHelper.WakePendingRead (); + } + _windowsVTOutput?.Dispose (); } } diff --git a/Terminal.Gui/Drivers/WindowsHelpers/WindowsVTInputHelper.cs b/Terminal.Gui/Drivers/WindowsHelpers/WindowsVTInputHelper.cs index a122dfeac3..931b78918f 100644 --- a/Terminal.Gui/Drivers/WindowsHelpers/WindowsVTInputHelper.cs +++ b/Terminal.Gui/Drivers/WindowsHelpers/WindowsVTInputHelper.cs @@ -45,11 +45,17 @@ internal sealed class WindowsVTInputHelper : IDisposable [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool ReadFile (nint hFile, byte [] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, nint lpOverlapped); + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool CancelIoEx (nint hFile, nint lpOverlapped); + [DllImport ("kernel32.dll")] private static extern uint GetConsoleCP (); #endregion + private const int ERROR_NOT_FOUND = 1168; + private const int ERROR_OPERATION_ABORTED = 995; + // Console mode flags private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200; private const uint ENABLE_PROCESSED_INPUT = 0x0001; @@ -183,6 +189,12 @@ public bool TryRead (byte [] buffer, out int bytesRead) if (!success) { int error = Marshal.GetLastWin32Error (); + + if (error == ERROR_OPERATION_ABORTED) + { + return false; + } + Logging.Warning ($"{nameof (WindowsVTInputHelper)}: ReadFile failed with error code: {error}"); return false; @@ -253,6 +265,36 @@ public void Dispose () /// true if there is at least one input event available. public bool Peek () => GetNumberOfConsoleInputEvents (InputHandle, out uint count) && count > 0; + /// + /// Cancels any pending synchronous on the Windows console input handle. + /// + public static void WakePendingRead () + { + if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) + { + return; + } + + nint inputHandle = TerminalDevice.InputHandle; + + if (inputHandle == nint.Zero || inputHandle == new nint (-1)) + { + return; + } + + if (CancelIoEx (inputHandle, nint.Zero)) + { + return; + } + + int error = Marshal.GetLastWin32Error (); + + if (error != ERROR_NOT_FOUND) + { + Logging.Warning ($"{nameof (WindowsVTInputHelper)}: CancelIoEx failed with error code: {error}"); + } + } + [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);