Skip to content

Conversation

@stuajnht
Copy link

This is to resolve #5185 by adding a Timeout parameter to Start-Process.

If the timeout is reached and the process hasn't finished, the process is stopped.

To perform the stopping of processes, the Stop-Process cmdlet is invoked. This is done to prevent repeating any checks needed when killing a process.

Before the process is stopped, a "search" is attempted to find all descendant processes to hopefully avoid any orphaned processes remaining active. This uses WMI on Windows and the ps command on UNIX-like systems, as this provided the most reliable way for cross-platform support.

As mentioned in the contributing guidelines, this change may warrant a note in the changelog (I am unsure where to put it).

[feature]

If the timeout is reached and the process hasn't finished, the process is stopped.
@msftclas
Copy link

msftclas commented Nov 16, 2017

CLA assistant check
All CLA requirements met.

Copy link
Member

@SteveL-MSFT SteveL-MSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

/// </summary>
[Parameter]
[Alias("TimeoutSec")]
[ValidateNotNullOrEmpty]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason for this upper limit?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we use [ValidateRange(0, Int32.MaxValue)] but now we have [ValidateRange(ValidateRangeKind.Positive)]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SteveL-MSFT No reason, just that the Wait-Process cmdlet has this limit so I used the same range.
@iSazonov I have changed it to [ValidateRange(ValidateRangeKind.Positive)].

string message = StringUtil.Format(ProcessResources.StartProcessTimeoutExceeded, process.ProcessName);
ErrorRecord er = new ErrorRecord(new TimeoutException(message), "StartProcessTimeoutExceeded", ErrorCategory.OperationTimeout, process);

StopProcessCommand stop = new StopProcessCommand();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like Stop-Process should have a switch called -IncludeChildProcesses and you would use that here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally. As there currently isn't one, I implemented the GetProcessTreeIds function to do this role.

If this code is accepted, I had planned to open an issue / PR about somehow integrating this function into the Stop-Process cmdlet in the future (so as not to change too much code in this PR), instead of it being located in Start-Process.

[Parameter]
[Alias("TimeoutSec")]
[ValidateNotNullOrEmpty]
[ValidateRange(1, 32767)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that Timeout is a bit misleading as it's not really a timeout on starting the process, but on how long it's expected to run. Perhaps calling this -WaitForExitTimeout would be more accurate? cc @HemantMahawar @joeyaiello

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-ExitTimeout
Also we could type of -Wait from switch to int to accept timeout.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed; -ExitTimeout makes more sense. Would changing -Wait to an int cause any problems with existing scripts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably leave -Wait as-is for compat reasons. -ExitTimeout works for me.

jobObject.WaitOne(_waithandle, _timeout);
if (!process.HasExited)
{
StopProcessOnTimeout(process);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we actually want to stop the process on timeout by default. I think it might be sufficient to return a non-terminating error so that -PassThru returns the process so the user can terminate it if so desired? cc @HemantMahawar @joeyaiello

Copy link
Contributor

@bergmeister bergmeister Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a user's perspective, I'd expect PowerShell to stop the process for me because this is how at least I would use this parameter. Even if it is just a naming problem, a -Timeout like implementation would be more useful to me than a -WaitForExitTimeout implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we're actually making this change if the following works fine Start-Process -PassThru | Wait-Process -Timeout? It seems we duplicate a code.
I'd expect that we enhance Wait-Process cmdlet with -PassThru to get Start-Process -PassThru | Wait-Process -Timeout 10 -PassThru | Stop-Process.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SteveL-MSFT @bergmeister I thought about both of these ways when implementing this, but settled on stopping the process on timeout, similar to https://ss64.com/bash/timeout.html. The reasoning is if I started a process and gave it a time limit, I would expect it to stop if the limit is reached, rather than just let me know it had gone over the limit. Is it worth adding a switch -StopOnExitTimeout (or similar)? Or, should the below suggestion be used?

@iSazonov I see your point, and didn't think of it being run this way. I suppose adding the -ExitTimeout parameter is about simplifying the amount of cmdlets needed. Would you prefer me to implement -PassThru for Wait-Process instead?


processRelationships = ps.StandardOutput.ReadToEnd();

ps.WaitForExit();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a potential this could not exit?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Shall add a few seconds wait before returning, just in case.

processRelationships = ps.StandardOutput.ReadToEnd();

ps.WaitForExit();
ps.Close();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check the exit code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had assumed this would either succeed or throw an error. Shall add a check just in case.

}

# Find where test/powershell is so we can find the testexe command relative to it
$powershellTestDir = $PSScriptRoot
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't have to search for testexe, I think the current pattern is to expect using Start-PSPester which builds testexe and puts it into the path

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, didn't realise it worked that way. Shall change to just use textexe.

Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we're actually making this change if the following works fine Start-Process -PassThru | Wait-Process -Timeout? It seems we duplicate a code.
I'd expect that we enhance Wait-Process cmdlet with -PassThru to get Start-Process -PassThru | Wait-Process -Timeout 10 -PassThru | Stop-Process.

/// </summary>
[Parameter]
[Alias("TimeoutSec")]
[ValidateNotNullOrEmpty]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we use [ValidateRange(0, Int32.MaxValue)] but now we have [ValidateRange(ValidateRangeKind.Positive)]

[Parameter]
[Alias("TimeoutSec")]
[ValidateNotNullOrEmpty]
[ValidateRange(1, 32767)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-ExitTimeout
Also we could type of -Wait from switch to int to accept timeout.

_timeout = value * 1000;
_timeoutSpecified = true;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use autogenerated property:

public int Timeout { Get; Set; } = Threading.Timeout.Infinite;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I was not aware of doing it this way.

[Parameter]
public SwitchParameter Wait { get; set; }

/// <summary>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow common convention and add final dot in public comments.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. The other public comments in Start-Process didn't have a dot, so I followed the same format as those.

}

if (Wait.IsPresent)
if (Wait.IsPresent || _timeoutSpecified)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we'll use autogenerated property we can check Timeout != Threading.Timeout.Infinite

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have changed as requested

{
if (!process.HasExited)
{
#if UNIX
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove two code paths and use public bool WaitForExit(int milliseconds); for all plaforms.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, I don't quite understand what you are asking here. Are you saying to remove the ProcessCollection / jobObject and just have a WaitForExit for all platforms? e.g.

if (!process.WaitForExit(ExitTimeout))
{
    StopProcessOnTimeout(process);
}

From what I can see, the Windows specific code keeps track of any child processes that are created, which doesn't happen for UNIX. I am unsure of the reasoning behind this, as the earliest commit on GitHub for these lines does not contain any helpful information.

jobObject.WaitOne(_waithandle, _timeout);
if (!process.HasExited)
{
StopProcessOnTimeout(process);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we're actually making this change if the following works fine Start-Process -PassThru | Wait-Process -Timeout? It seems we duplicate a code.
I'd expect that we enhance Wait-Process cmdlet with -PassThru to get Start-Process -PassThru | Wait-Process -Timeout 10 -PassThru | Stop-Process.

[feature]

Based on PR feedback, also:
* Skipped searching for `testexe`, as `Start-PSPester` puts it into the path
* Set the upper limit for `ExitTimeout` to be a positive range, rather than 32767
* Used autogenerated property for `ExitTimeout`
* Set a timeout and checked the exit code for the `ps axo` process
* Used common coding convention for comments
@stuajnht
Copy link
Author

Hi all,

Thanks for your review feedback. I have made a number of changes based on it:

  • Changed Timeout parameter to ExitTimeout
  • Skipped searching for testexe, as Start-PSPester puts it into the path
  • Set the upper limit for ExitTimeout to be a positive range, rather than 32767
  • Used autogenerated property for ExitTimeout
  • Set a timeout and checked the exit code for the ps axo process
  • Used common coding convention for comments

Any questions that I have are in the relevant feedback comments.

@stuajnht stuajnht changed the title Start-Process: add parameter 'Timeout' Start-Process: add parameter 'ExitTimeout' Nov 17, 2017
@SteveL-MSFT SteveL-MSFT added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Nov 17, 2017
@SteveL-MSFT
Copy link
Member

The concept is definitely a good one and something needed. I'd like @PowerShell/powershell-committee to review the design to make sure it fits with PowerShell norms.

@iSazonov
Copy link
Collaborator

As I said above I'd prefer to see Start-Process -PassThru | Wait-Process -Timeout 10 -PassThru | Stop-Process.
Another thought is that -Wait and -ExitTimeout is duplicates. We'd accept a slight breaking change -Wait | -Wait <timeout> where -Wait is infinite timeout. It is breaking only -Wait:$true that is rare case.
Another thought is - can there be other timeouts? If no we'd use -Timeout name.

@mattpwhite
Copy link

Is this a race? It looks like PIDs are being collected as integers (as opposed to handles that might prevent re-use), then later those PIDs are killed, but there's nothing to say that the PID hasn't been reused by the OS for a different process at that point, or is there?

@mklement0
Copy link
Contributor

mklement0 commented Nov 23, 2017

@stuajnht: Consistency of parameter names across cmdlets is important, so I agree with @iSazonov that we should revert to -Timeout.

@iSazonov: Being able to specify a timeout value directly is a welcome convenience that we should add to other cmdlets as well - see #5434 and #5433

PowerShell lacks the concept of an optional argument value altogether, and I suggest not opening that can of worms with something -Wait | -Wait <timeout>. (If anything, to avoid ambiguity, it would have to have the form -Wait | -Wait:<timeout>, analogous to how POSIX shells do it.)

To me, there's nothing wrong with a separate -Timeout parameter.

@stuajnht
Copy link
Author

@mklement0 @iSazonov I am happy to change the parameter back to -Timeout if no-one else has any objections about the parameter name needing to be clarified about what it does.

@mattpwhite After trying with a variety of programs, I believe that you are right. When I was coding this, killing the parent process would keep the child processes running. As was mentioned in one of the comments, ideally the Stop-Process cmdlet would have the ability to terminate child processes. My idea was to include something in this PR to prevent child processes being leftover when the timeout occurs, but I realise now it should be in its own PR instead.

@SteveL-MSFT Has anything been considered if the process should be stopped on the timeout being reached, or if it should just warn the user?

Thanks all, and I'll update the PR once I have more information.

@mklement0
Copy link
Contributor

@stuajnht:

if the process should be stopped on the timeout being reached

To me, that calls for another switch: -StopOnTimeout; by default, no action (other than the timeout error) should be taken.

@SteveL-MSFT
Copy link
Member

@stuajnht I'll try to get the committee to review this today

@SteveL-MSFT
Copy link
Member

SteveL-MSFT commented Nov 29, 2017

@PowerShell/powershell-committee reviewed this and would like the parameter to be called -MaximumDuration and validate that when used with -PassThru, the dead process object should be emitted. -MaximumDuration implies -Wait and no requirement to make them mutually exclusive. We agree it's a good idea to add -PassThru to Wait-Process to support advanced usage scenarios as a separate issue/PR.

@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Nov 29, 2017
@mklement0
Copy link
Contributor

May I ask what the rationale is for not sticking with the well-established -Timeout name for the parameter (used in several other cmdlets)?

Aside from this inconsistency, "duration" smacks of [timespan].

@mklement0
Copy link
Contributor

Implying -Wait when a timeout is specified definitely makes sense, but, again ... uh ... calling a timeout a timeout makes sense too.

@SteveL-MSFT
Copy link
Member

@mklement0 the main push back on calling it Timeout is that it's not a timeout on the starting of a process, but the max lifetime allowed for the started process. Duration sounded better than Lifetime.

@mklement0
Copy link
Contributor

@SteveL-MSFT:

I appreciate your letting me know.

I don't think there's any point in distinguishing between launch duration and execution duration - they should be treated as a single timeout, starting at the launch point in time.

If there's a technical reason for not being able to enforce the timeout during the launch phase a simple note in the documentation will do.

Even if you did want to make the distinction you're suggesting, -MaximumDuration is no less ambiguous than -Timeout: duration of what?

@iSazonov
Copy link
Collaborator

I'd prefer to see unified parameter names. These parameters (Timeout) are easier to discover and remember than the unique parameters (MaximumDuration) in each cmdlet.

@SteveL-MSFT SteveL-MSFT added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Nov 30, 2017
@SteveL-MSFT SteveL-MSFT removed the Committee-Reviewed PS-Committee has reviewed this and made a decision label Nov 30, 2017
@SteveL-MSFT
Copy link
Member

@mklement0 @iSazonov appreciate all the feedback and you both make valid points, will bring it back up

@SteveL-MSFT
Copy link
Member

@PowerShell/powershell-committee revisited this and would be ok with -ExecutionTimeout. In general, documentation should not be required to understand the intent or purpose of a parameter. The name should be self documenting as much as possible without being overly verbose. Just -Timeout is ambiguous of the purpose, but -ExecutionTimeout is concise and reasonable.

@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Dec 6, 2017
@TravisEz13 TravisEz13 changed the title Start-Process: add parameter 'ExitTimeout' Start-Process: add parameter -ExitTimeout Feb 8, 2018
[Parameter]
[ValidateNotNullOrEmpty]
[ValidateRange(ValidateRangeKind.Positive)]
public int ExitTimeout { get; set; } = Timeout.Infinite;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to be updated per committee review

@stale
Copy link

stale bot commented Mar 16, 2018

This PR has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed if no further activity occurs within 10 days. Thank you for your contributions.

@stale stale bot added the Stale label Mar 16, 2018
@iSazonov
Copy link
Collaborator

@stuajnht Could you please continue?

@stale stale bot removed the Stale label Mar 19, 2018
@TravisEz13
Copy link
Member

@iSazonov Please consider the bots comment the request to continue. By commenting, you are defeating the bot.

@iSazonov
Copy link
Collaborator

@TravisEz13 Thanks for clarify. I thought the bot only counted commits.
I think the PR is useful and we could grab and continue.

@TravisEz13
Copy link
Member

@iSazonov But a closed PR can be continued as well as long as the owner doesn't delete the branch. The bot will label all of them as review-abandoned so we can query for them. If you think the wording of the message of the bot needs to be updated, create a PR or just file an issue with a proposal and mention me.

@iSazonov
Copy link
Collaborator

@TravisEz13 I open Issue #6446 with suggestion.

@stale
Copy link

stale bot commented Apr 20, 2018

This PR has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed if no further activity occurs within 10 days.
Thank you for your contributions.
Community members are welcome to grab these works.

@stale stale bot added the Stale label Apr 20, 2018
@stale
Copy link

stale bot commented Apr 30, 2018

This PR has be automatically close because it is stale. If you wish to continue working on the PR, please first update the PR, then reopen it.
Thanks again for your contribution.
Community members are welcome to grab these works.

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

Labels

Committee-Reviewed PS-Committee has reviewed this and made a decision Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Start-Process should support timeout

8 participants