Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read-Host should have a -TimeoutSecond parameter #19664

Closed
SteveL-MSFT opened this issue May 16, 2023 · 17 comments
Closed

Read-Host should have a -TimeoutSecond parameter #19664

SteveL-MSFT opened this issue May 16, 2023 · 17 comments
Labels
Issue-Enhancement the issue is more of a feature request than a bug Needs-Triage The issue is new and needs to be triaged by a work group. Resolution-No Activity Issue has had no activity for 6 months or more

Comments

@SteveL-MSFT
Copy link
Member

Summary of the new feature / enhancement

If the user input isn't strictly required and some default can be used, it may make sense to have Read-Host have a timeout so that the script isn't blocked indefinitely.

Proposed technical implementation details (optional)

Would need to show the timeout, default, and countdown.

@SteveL-MSFT SteveL-MSFT added Issue-Enhancement the issue is more of a feature request than a bug Needs-Triage The issue is new and needs to be triaged by a work group. labels May 16, 2023
@mklement0

This comment was marked as outdated.

@kasini3000
Copy link

hi all guys:
Great, this feature is commendable.
As I currently do not have this feature, I implemented it myself using a script.
Well, perhaps my implementation is not perfect yet. Welcome to reference and improve.
https://gitee.com/chuanjiao10/kasini3000/blob/master/read-host+timeout_v2.1.ps1

@jhoneill
Copy link

jhoneill commented May 17, 2023

Hmmm. I only see read host being used (a) where the program is written like 1970s BASIC

$Parameter = Read-host "Please enter the parameter"
Instead of `Param ($parameter)

(b) Where people are using it as "Hit enter to continue", and here having a time out might help.

@andyleejordan
Copy link
Member

As @SeeminglyScience pointed out, we think this would require an async ReadKey implementation in .NET...so it may never happen. Or possibly you could do the same thing we did in PSES and to "cancel" ReadKey send fake input and discard it 🤷

@SeeminglyScience
Copy link
Collaborator

SeeminglyScience commented May 17, 2023

Or possibly you could do the same thing we did in PSES and to "cancel" ReadKey send fake input and discard it 🤷

That's also a can of worms though. We only got away with that in PSES because of the ReadKey override hijinks we do with PSReadLine. We'd need to standardize that hack, otherwise the first bit of input after a timeout would be dropped.

@rhubarb-geek-nz
Copy link

rhubarb-geek-nz commented May 18, 2023

I would like to raise the stakes here and say that why can we not have a termination mechanism on any command, rather than piecemeal by each Cmdlet?

A PowerShell command is invoked with a new PowerShell and that has a StopAsync method. So in theory any command or pipeline can be stopped.

The standard mechanism in dotnet core is to use a CancellationToken for a client/caller to indicate when a command should be cancelled.

So instead of each command having a timeout parameter, you could add CancellationToken support to Invoke-Command so that would do the equivalent of the following

cancellationToken.Register(() =>
            {
                powerShell.StopAsync((t) =>
                {
                });
            });

Ideally each PowerShell Runspace should have a global CancellationToken so that code that is not in a Cmdlet can know when to terminate, also when invoking a new command the new CancellationToken should both be cancelled by the current cancellation token and allow it to set new criteria for the new invocation scope.

This means that any code could set up a CancellationToken for their current local requirements and then use Invoke-Command to use that for the child script block.

In this scenario the Invoke-Command would accept a script block, arguments and cancellation token. In the case of the the cancellation token being invoked it would throw an exception.

$cancellationTokenSource = New-Object -Type System.Threading.CancellationTokenSource

$cancellationTokenSource.CancelAfter(5000)

Invoke-Command -NoNewScope -ScriptBlock {
        Read-Host -Prompt "prompt "
}  -CancellationToken $cancellationTokenSource.Token

If nested/child PowerShell invocations already propagate the Stop/StopAsync then the global cancellation token might not be required.

It could be even better if every command accepted a cancellation token directly with CommonParameters where that automatically stopped the command when the cancellation was triggered and threw an exception.

@kasini3000
Copy link

There should also be a parameter here:
-default_value_on_timeout

@rhubarb-geek-nz
Copy link

Unfortunately the Read-Host Cmdlet does not respond to the PowerShell being stopped.

Although Microsoft.PowerShell.PSConsoleReadLine does implement a ReadLine that takes a CancellationToken it doesn't work until a key has been entered.

#!/usr/bin/env pwsh

Import-Module PSReadLine

$runspace=[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace

$cancellationTokenSource = New-Object -Type System.Threading.CancellationTokenSource
$cancellationTokenSource.CancelAfter(5000)
$cancellationToken = $cancellationTokenSource.Token
try
{
	$line = [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($runspace,$executionContext,$cancellationToken,$null)

	$line
}
finally
{
	$cancellationTokenSource.Dispose()
}

So even though the five seconds has elapsed, the ReadLine does not return until a key has been pressed.

@mklement0
Copy link
Contributor

@rhubarb-geek-nz
Copy link

@SeeminglyScience
Copy link
Collaborator

It doesn't cancel properly due to the issues I outlined above. In PSES we make it work by injecting our own ReadKey handler, but this also has many many issues. In vscode-powershell specifically we also have the benefit of being able to send input via a VSCode API.

A generic cancellable ReadKey has to be provided by the BCL for a reliable implementation.

@rhubarb-geek-nz
Copy link

On Linux/POSIX input can be done with non-blocking IO and select/read after using cfmakeraw on file description zero

On Windows, ( I have not tried this ) you can use WaitForSingleObject on the file descriptor from GetStdHandle(STD_INPUT_HANDLE). Then use GetNumberOfConsoleInputEvents to determine if anything to process.

This is documented at ReadConsoleInput.

In this the only thing blocking is the WaitForSingleObject, but I don't see an issue with a single background thread per process dedicated to reading that once you have loaded PSReadLine module.

@SeeminglyScience
Copy link
Collaborator

On Linux/POSIX input can be done with non-blocking IO and select/read after using cfmakeraw on file description zero

Keep in mind that the amount of code that it takes the BCL to translate VT input into uniform structures we can use is quite large and updated somewhat frequently. In PSES we did a variant of what you're referring to in order to poll for input pending. At the time I did not see a way to do a non-blocking read while still retaining the console attributes we needed and not consume the input anyway when cancelled (ofc I didn't look very hard either as it would have meant reimplementing everything the BCL does, definitely last resort).

That's not to say that we can never duplicate code that is in the BCL, but the impact needs to match the maintenance cost.

On Windows, ( I have not tried this ) you can use WaitForSingleObject on the file descriptor from GetStdHandle(STD_INPUT_HANDLE). Then use GetNumberOfConsoleInputEvents to determine if anything to process.

WaitForSingleObject will return for much more than what Console.ReadKey will. It'll fire for mouse events, focus events, window buffer size changes, etc. We would need to then PeekConsoleInput and verify that it contains something we care about, and then either drain it (and use it) or instead flush it (which would deny something else the ability to use that input). That introduces a ton of potential races between us and Console.ReadKey (and anything else reading console input).


Both of those can still be used to poll for input, but that's not free either. If you're polling then it's very difficult not to create some lag somewhere or end up using too much CPU. In PSES we had staged timeouts depending on when the last key press was but even that ended up a little janky in some places (after a long pause there was noticeable delay in the first key stroke, right click copy paste was very slow).

That was something we dealt with in PSES because the impact was very high. If you can't actually cancel reading input, then you can't step through code with the debugger, F8 to run selection doesn't work, a ton of stuff stops working.

I do not personally think that Read-Host -Timeout meets that bar for impact. Though it is another good reason for the BCL to provide an API that facilitates it.

@rhubarb-geek-nz
Copy link

Thank you for that overview. You could treat a console app much like a traditional GUI app. Just like a windows app has GetMessage/TranslateMessage/DispatchMessage at its heart, you could do similar with a console app where you have one component that is responsible for waiting, reading and writing to the actual console and dispatches the different console input depending on its type ( windows size changes, keyboard input, signals, mouse movements and clicks). I notice that if you just run a script by itself then PSReadLine is not loaded until it is needed, and an interactive PowerShell loads it straight away. So you could say that once PSReadLine is loaded it owns the console and other components need to read the content and write their output through it. So nobody needs to read the console input directly or peek ahead because the PSReadLine message loop would process all that and dispatch the input to the various handlers, eg updating size of the window or location of the cursor. Just like a GUI app, different threads would need to effectively get the focus for input. If a line was being input and edited for a given Cmdlet invocation and that was stopped/cancelled then the entire line could be thrown away because the purpose for the entry has now gone. Each new request of a line starts from scratch, it does not need to get previously typed ahead data that was entered before the current readline request is made. Just a thought.

Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

1 similar comment
Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

@microsoft-github-policy-service microsoft-github-policy-service bot added the Resolution-No Activity Issue has had no activity for 6 months or more label Nov 17, 2023
Copy link
Contributor

This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Enhancement the issue is more of a feature request than a bug Needs-Triage The issue is new and needs to be triaged by a work group. Resolution-No Activity Issue has had no activity for 6 months or more
Projects
None yet
Development

No branches or pull requests

7 participants