PSReadLine 2.4.5 crashes pwsh on durable console-input loss — #3744's retry guard is too short to help
还没有人认领这个 Issue。
评估
调研方向
Start with PSReadLine/ConsoleLib.cs, focusing on _TryIgnoreIOE and its callers ReadKey, KeyAvailable, and ReadKeyThreadProc. Use the suggested shared-console FreeConsole or abrupt-exit scenario to investigate durable console-input loss; done means exhausted failures do not terminate pwsh and the ReadLine session ends or reports a recoverable error while transient loss can recover.
由索引模型根据 Issue 内容生成。
描述
Prerequisites
- Write a descriptive title.
- Make sure you are able to repro it on the latest released version
- Search the existing issues, especially the pinned issues.
Exception report
## Summary
`PSConsoleReadLine.ReadKeyThreadProc` terminated the whole `pwsh.exe` process with an unhandled
`System.InvalidOperationException` ("Cannot read keys when either application does not have a console
or when console input has been redirected"), **with the `_TryIgnoreIOE` mitigation from #3744 present
in the stack**.
This is not a duplicate of #3744 — it is evidence that the fix for #3744 is **incomplete by
construction**. That fix assumes the `InvalidOperationException` is a *transient* race (a
co-attached process exiting), and retries 10 times **with no delay**. When console input is
invalidated *durably* rather than momentarily, all 10 attempts fail within microseconds, the
exception is rethrown on a background thread with no top-level handler, and the process dies.
The user-visible failure is severe and gives no diagnostic: the terminal window keeps its last
painted frame, **stops accepting keyboard input entirely** (PSReadLine's reader thread is the thing
that consumes keystrokes), does not respond to close or minimize, and several minutes later the
process simply vanishes. No error is printed. No `Application Hang` (Event ID 1002) is logged,
because nothing hung — so anyone triaging this from Event Viewer is looking for the wrong class of
fault. Every other process on the machine is unaffected.
## Actual behaviour
`.NET Runtime` Event ID 1026:
Application: pwsh.exe
CoreCLR Version: 10.0.1126.37416
.NET Version: 10.0.11
Description: The process was terminated due to an unhandled exception.
Exception Info: System.InvalidOperationException: Cannot read keys when either application does not
have a console or when console input has been redirected. Try Console.Read.
at System.ConsolePal.ReadKey(Boolean intercept)
at Microsoft.PowerShell.Internal.VirtualTerminal._TryIgnoreIOE[T](Func`1 f)
at Microsoft.PowerShell.PSConsoleReadLine.ReadOneOrMoreKeys()
at Microsoft.PowerShell.PSConsoleReadLine.ReadKeyThreadProc()
at System.Threading.Thread.StartHelper.Callback(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
Paired `Application Error` Event ID 1000:
Faulting application name: pwsh.exe, version: 7.6.5.500, time stamp: 0x6a630000
Faulting module name: KERNELBASE.dll, version: 10.0.26100.8875, time stamp: 0xca32cd54
Exception code: 0xe0434352
Fault offset: 0x00000000000c1ada
Faulting process id: 0xBE4C
Faulting application path: C:\Program Files\PowerShell\7\pwsh.exe
Report Id: 2b8c89f0-5813-429f-b72f-5699da29aa42
A full user-mode crash dump was captured (9.3 MB) and is available on request.
## Expected behaviour
Two separate expectations, in priority order:
1. **An exhausted retry in `_TryIgnoreIOE` must not terminate the host process.** `ReadKeyThreadProc`
is a background thread with no top-level handler, so any escape is fatal and silent. Even when
the retries are legitimately exhausted, the correct outcome is to end the current `ReadLine`
session / surface a recoverable error — not to destroy the session and every child process
running under that console. Losing an entire working session (and, for console-hosted agents and
CI harnesses, everything running beneath it) to a keystroke-reader thread is disproportionate.
2. **The retry policy should cover a state change, not just a nanosecond race.** See below.
## Analysis — where the #3744 guard has a hole
Current implementation (`PSReadLine/ConsoleLib.cs`):
private static T _TryIgnoreIOE<T>(Func<T> f)
{
int triesLeft = 10;
while (true)
{
try
{
triesLeft--;
return f();
}
catch (InvalidOperationException)
{
if (triesLeft <= 0)
{
throw;
}
}
}
}
used as:
public ConsoleKeyInfo ReadKey() => _TryIgnoreIOE(() => _readKeyMethod.Value(true));
public bool KeyAvailable => _TryIgnoreIOE(() => Console.KeyAvailable);
Three defects:
1. **No backoff.** Ten immediate iterations of a tight loop complete in microseconds. That covers the
original #3744 race (.NET returning 0 records because a co-attached process was terminated
mid-read) but cannot cover a console state transition that takes milliseconds to settle. A short
delay between attempts — even 10 ms — would cover ~100 ms of instability for no practical cost on
a thread that is otherwise blocked waiting for a human.
2. **No distinction between transient and terminal.** If the console input handle has been
invalidated for good (`FreeConsole`, handle closed, input genuinely redirected), retrying 10 times
is futile by construction. The two cases need different handling: retry the race, gracefully end
the ReadLine session on the durable case.
3. **Rethrow is fatal and silent.** As in (1) of *Expected behaviour*. The user gets no message at
all — only an unresponsive window and, minutes later, a disappeared process.
## Steps to reproduce
I do not have a deterministic minimal repro, and I would rather say so than dress up a guess. What I
have is a measured timeline with a strong and mechanistically plausible trigger:
1. A console-hosted agent harness runs in a Windows Terminal `pwsh` session and spawns child `pwsh`
processes for tool calls; those children **share the parent's console**.
2. In one such child, `Connect-MgGraph` (`Microsoft.Graph.Authentication` 2.37.0) performed an
interactive Entra sign-in via `Azure.Identity`'s `InteractiveBrowserCredential` / WAM. The broker
dialog opened behind other windows and was cancelled, returning
`InteractiveBrowserCredential authentication failed: User canceled authentication.`
3. From approximately that moment, the parent console **accepted no keyboard input**. The window
still painted, but did not respond to typing, to close, or to minimize.
4. About 8 minutes later, `pwsh.exe` terminated with the unhandled exception above.
**Measured:** the exception, the stack, the fault code, the absence of any hang event, the timings,
and the version set below. **Inferred, not proven:** that the WAM/broker interactive flow attaching
to and detaching from the *shared* console is what invalidated the input handle. I note that this
mechanism is a direct match for the root cause described in #3744 — "if a second process attached to
the console is also waiting for input, and then is terminated, .NET gets back 0 records, and decides
to throw" — except that here the invalidation appears to persist, which is exactly why 10 immediate
retries do not save it.
A likely synthetic repro for maintainers: from a `pwsh` session with PSReadLine loaded, have a second
process attached to the same console take console input and then `FreeConsole` / exit abruptly while
PSReadLine's reader thread is blocked in `ReadKey`.
## Environment
| | |
|---|---|
| PSReadLine | **2.4.5** (`C:\program files\powershell\7\Modules\PSReadLine`, as shipped with PS 7.6.5) |
| PowerShell | 7.6.5 (`GitCommitId` 7.6.5), `pwsh.exe` 7.6.5.500 |
| .NET / CoreCLR | .NET 10.0.11 / CoreCLR 10.0.1126.37416 |
| OS | Windows 11 Enterprise 25H2, build **26200.9106** |
| Terminal | Windows Terminal 1.24.11911.0 |
| Faulting module | `KERNELBASE.dll` 10.0.26100.8875 |
| Third party in the trigger path | `Microsoft.Graph.Authentication` 2.37.0 → `Azure.Identity` `InteractiveBrowserCredential` (WAM) |
No `oh-my-posh`, `posh-git`, or `Terminal-Icons` in this session — the module set reported in
PowerShell/PowerShell#23979 is **not** required to hit this.
## Why this belongs here rather than in PowerShell/PowerShell
PowerShell/PowerShell#23979 was closed `Resolution-External`, and correctly so — every frame in the
stack below `Thread.StartHelper` belongs to PSReadLine, and the retry policy that decides whether
this crashes lives in `PSReadLine/ConsoleLib.cs`. The mitigation is here, so the gap in the
mitigation is here too.
Screenshot
No screenshot available, all pertinent information has been supplied.
Environment data
PS Version: 7.6.5
PS HostName: ConsoleHost (Windows Terminal)
PSReadLine Version: 2.4.5
PSReadLine EditMode: Windows
OS: 10.0.26100.1 (WinBuild.160101.0800)
BufferWidth: 179
BufferHeight: 52
Steps to reproduce
I do not have a deterministic minimal repro and would rather say so than dress up a guess.
What I have is a measured timeline with a mechanistically plausible trigger.
- A console-hosted agent harness runs in a Windows Terminal
pwshsession and spawns child
pwshprocesses for tool calls. Those children SHARE the parent's console. - In one such child,
Connect-MgGraph(Microsoft.Graph.Authentication 2.37.0) performed an
interactive Entra sign-in via Azure.Identity's InteractiveBrowserCredential / WAM. The broker
dialog opened behind other windows and was cancelled, returning:
InteractiveBrowserCredential authentication failed: User canceled authentication. - From approximately that moment the parent console accepted NO keyboard input. The window still
painted, but did not respond to typing, to close, or to minimize. - About 8 minutes later
pwsh.exeterminated with the unhandled exception below.
Suggested synthetic repro: from a pwsh session with PSReadLine loaded, have a second process
attached to the same console take console input and then FreeConsole / exit abruptly while
PSReadLine's reader thread is blocked in ReadKey.
Note this is the same root cause described in #3744 ("if a second process attached to the console is
also waiting for input, and then is terminated, .NET gets back 0 records, and decides to throw") --
except here the invalidation appears to PERSIST, which is exactly why 10 immediate retries cannot
recover from it.
Expected behavior
Two separate expectations, in priority order.
-
An exhausted retry in
_TryIgnoreIOEmust not terminate the host process.ReadKeyThreadProc
is a background thread with no top-level handler, so any escape is fatal and silent. Even when
the retries are legitimately exhausted, the correct outcome is to end the current ReadLine
session or surface a recoverable error -- not to destroy the session and every child process
running under that console. For console-hosted agents and CI harnesses, a keystroke-reader
thread takes down everything running beneath it. -
The retry policy should cover a state change, not just a nanosecond race.
_TryIgnoreIOE
retries 10 times with NO delay, so all ten attempts complete in microseconds. Even a 10 ms
delay between attempts would cover ~100 ms of console instability, at no practical cost on a
thread that is otherwise blocked waiting for a human.
Actual behavior
The process was terminated by an unhandled exception on PSReadLine's key-reader thread, with the
_TryIgnoreIOE mitigation from #3744 present in the stack -- i.e. the guard was in place and still
let the host die.
.NET Runtime, Event ID 1026:
Application: pwsh.exe
CoreCLR Version: 10.0.1126.37416
.NET Version: 10.0.11
Description: The process was terminated due to an unhandled exception.
Exception Info: System.InvalidOperationException: Cannot read keys when either application does not
have a console or when console input has been redirected. Try Console.Read.
at System.ConsolePal.ReadKey(Boolean intercept)
at Microsoft.PowerShell.Internal.VirtualTerminal._TryIgnoreIOE[T](Func`1 f)
at Microsoft.PowerShell.PSConsoleReadLine.ReadOneOrMoreKeys()
at Microsoft.PowerShell.PSConsoleReadLine.ReadKeyThreadProc()
at System.Threading.Thread.StartHelper.Callback(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
Paired Application Error, Event ID 1000:
Faulting application name: pwsh.exe, version: 7.6.5.500, time stamp: 0x6a630000
Faulting module name: KERNELBASE.dll, version: 10.0.26100.8875, time stamp: 0xca32cd54
Exception code: 0xe0434352
Fault offset: 0x00000000000c1ada
Report Id: 2b8c89f0-5813-429f-b72f-5699da29aa42
A full user-mode crash dump (9.3 MB) was captured and is available on request.
User-visible symptoms, which give no diagnostic at all: the window keeps its last painted frame,
stops accepting keyboard input entirely, ignores close and minimize, and several minutes later the
process simply vanishes. No error is printed. Critically, NO Application Hang (Event ID 1002) is
logged -- nothing hung -- so anyone triaging from Event Viewer is looking for the wrong fault class.
Every other process on the machine is unaffected
- 主要语言
- C#
- 星标
- 4.4k
- 派生
- 341
- PR 合并指标
- 30 天内没有已合并 PR
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
PowerShell/PSReadLine 的其他 Issue
-
Needs-Triage :mag:
难度 2/5 1-3 小时 新手友好度 78/100
PowerShell/PSReadLine#5205 ·
-
Needs-Triage :mag:
难度 2/5 1-3 小时 新手友好度 68/100
PowerShell/PSReadLine#5195 ·
-
Needs-Triage :mag:
难度 2/5 1-3 小时 新手友好度 72/100
PowerShell/PSReadLine#5121 ·
-
Needs-Triage :mag:
难度 2/5 1-3 小时 新手友好度 64/100
PowerShell/PSReadLine#5045 ·
-
Area-CommandHelp Issue-Enhancement
难度 1/5 1 小时以内 新手友好度 68/100
PowerShell/PSReadLine#3470 · 3 个 reaction ·
查看 PowerShell/PSReadLine 的全部 Issue
相似的 Issue
-
bug
难度 1/5 1 小时以内 新手友好度 75/100
sillsdev/languageforge-lexbox#2665 ·
-
bug documentation frontend
难度 2/5 1-3 小时 新手友好度 72/100
azurenoops/spin_agent#975 ·
-
难度 2/5 1-3 小时 新手友好度 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
难度 2/5 1-3 小时 新手友好度 92/100
dotnet/AspNetCore.Docs#37699 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
SubtitleEdit/subtitleedit#15108 · 1 条评论 ·