From 7d8d6acb59b23ba0b0b6e5d7213e797b6b514d66 Mon Sep 17 00:00:00 2001 From: Dan Thompson Date: Tue, 21 May 2024 23:50:08 -0700 Subject: [PATCH] Console.ReadKey should not throw when read returns 0 records (#98684) This change fixes a problem where Console.ReadKey inappropriately throws an InvalidOperationException. The Console.ReadKey function can throw an InvalidOperationException when the "application does not have a console or when console input has been redirected". That makes sense. However, there is *another* case where it throws an InvalidOperationException, which happens when there are multiple processes attached to the same console, which are waiting for input, and one dies or is killed. In this case, the native read function returns a "success" return code, but the "out" parameter indicating the number of records read is 0, and in this case, Console.ReadKey is throwing, but it should not be. Note that this behavior of the underlying platform is "almost certainly" a bug (https://github.com/microsoft/terminal/issues/15859); however, it is longstanding behavior, so we would like to avoid blowing up apps who have the misfortune of stumbling into this situation. More details in the linked Issue: Fix #88697 --- .../src/System/ConsolePal.Windows.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Console/src/System/ConsolePal.Windows.cs b/src/libraries/System.Console/src/System/ConsolePal.Windows.cs index ee25d844de866d..f51fed48b02630 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Windows.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @@ -347,7 +347,7 @@ public static ConsoleKeyInfo ReadKey(bool intercept) while (true) { r = Interop.Kernel32.ReadConsoleInput(InputHandle, out ir, 1, out int numEventsRead); - if (!r || numEventsRead == 0) + if (!r) { // This will fail when stdin is redirected from a file or pipe. // We could theoretically call Console.Read here, but I @@ -355,6 +355,28 @@ public static ConsoleKeyInfo ReadKey(bool intercept) throw new InvalidOperationException(SR.InvalidOperation_ConsoleReadKeyOnFile); } + if (numEventsRead == 0) + { + // This can happen when there are multiple console-attached + // processes waiting for input, and another one is terminated + // while we are waiting for input. + // + // (This is "almost certainly" a bug, but behavior has been + // this way for a long time, so we should handle it: + // https://github.com/microsoft/terminal/issues/15859) + // + // (It's a rare case to have multiple console-attached + // processes waiting for input, but it can happen sometimes, + // such as when ctrl+c'ing a build process that is spawning + // tons of child processes--sometimes, due to the order in + // which processes exit, a managed shell process (like pwsh) + // might get back to the prompt and start trying to read input + // while there are still child processes getting cleaned up.) + // + // In this case, we just need to retry the read. + continue; + } + ushort keyCode = ir.keyEvent.wVirtualKeyCode; // First check for non-keyboard events & discard them. Generally we tap into only KeyDown events and ignore the KeyUp events