Skip to content

Handle InvalidOperationException from Console.ReadKey #3744

Closed

Description

Description of the new feature/enhancement

Under certain (unexpected) circumstances the Console.ReadKey and KeyAvailable APIs can throw an InvalidOperationException. See: dotnet/runtime#88697

(TL;DR: 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.)

Ideally, that bug would be fixed in .NET. However, it is unclear when or if that will happen. I would like to propose that we update PSReadLine's use of these APIs to handle these situations instead of crashing.

For those who know what "razzle" is: the end goal here is to fix ctrl+c in PowerShell razzle.

(For those who don't know: "razzle" is an internal build environment, in which it is not too hard to hit the situation described in dotnet/runtime#88697.)

Proposed technical implementation details

The general idea is simple: wrap in a try/catch, and if it fails, just reissue the call.

One possible wrinkle: those APIs are expected to throw if the "application does not have a console or when console input has been redirected". I would not expect PSReadLine to come into play at all in such a case. Since a user script/module has no opportunity to catch the exception, such an exception should always crash the process. But it could be possible that someone is depending on this behavior (to crash in a console-input-redirected case)... in which case, we could try to detect this (not sure off the top of my head how) or adopt a heuristic, like "if the operation throws 5 times in a row, give up and let the exception fly". Let me know if you think going to such lengths is necessary.

Exhibit A: stack trace when the problem is hit:

An error has occurred that was not properly handled. Additional information is shown below. The PowerShell process will exit.
Unhandled exception. 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.PSConsoleReadLine.ReadOneOrMoreKeys()
   at Microsoft.PowerShell.PSConsoleReadLine.ReadKeyThreadProc()
   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)

Activity

  1. jazzdelightsme commented on Jul 15, 2023

    @jazzdelightsme
    ContributorAuthor

    Note: this problem is the biggest obstacle preventing the ConsoleBouncer project from being reliable: https://github.com/jazzdelightsme/ConsoleBouncer#:~:text=May%20crash%20your%20shell

  2. StevenBucher98 commented on Jul 24, 2023

    @StevenBucher98
    Collaborator

    Dan Thompson (@jazzdelightsme) would you be willing to create a PR and see if that fixes your problems? We are also wanting to make sure it doesn't break any other scenarios with PSRL.

  3. daxian-dbw commented on Jul 24, 2023

    @daxian-dbw
    Member

    those APIs are expected to throw if the "application does not have a console or when console input has been redirected". I would not expect PSReadLine to come into play at all in such a case.

    Right, PSReadLine doesn't support the case where either stdin or stdout is redirected:

    if (Console.IsInputRedirected || Console.IsOutputRedirected)
    {
    // System.Console doesn't handle redirected input. It matches the behavior on Windows
    // by throwing an "InvalidOperationException".
    // Therefore, if either stdin or stdout is redirected, PSReadLine doesn't really work,
    // so throw and let PowerShell call Console.ReadLine or do whatever else it decides to do.
    //
    // Some CI environments redirect stdin/stdout, but that doesn't affect our test runs
    // because the console is mocked, so we can skip the exception.
    if (!IsRunningCI(console))
    {
    throw new NotSupportedException();
    }
    }

    So, this sounds like an acceptable change. Can you please submit a PR so we can take a closer look at the proposed changes?

  4. daxian-dbw commented on Jul 27, 2023

    @daxian-dbw
    Member

    Closed via #3755

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions