PowerShell / PowerShell/PowerShell
`ForEach-Object -Parallel`: consider changing the semantics of the `-TimeoutSeconds` parameter to apply to each thread or provide a new switch / parameter that does
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 55.5k
- Forks
- 8.5k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 88
Description
Summary of the new feature / enhancement
The current semantics of the -TimeoutSeconds parameter are arguably unhelpful:
-
The
-TimeoutSecondsinterval is applied to the runtime duration of the overall, typically throttled and therefore "batched" invocation[1] rather than to the runtime of each thread.- Therefore, a timeout can occur even if each individual thread completed in less than the specified timeout. A simple example:
# This times out when the 3rd thread runs, because - due to ThrottleLimit 2 -
# it only starts after 1.5+ seconds, after the first 2 threads
# have finished.
1..4 |
ForEach-Object -TimeoutSeconds 2 -ThrottleLimit 2 -Parallel {
Start-Sleep -MilliSeconds 1500
$_ # Pass the input object through.
}
-
When a timeout occurs, the command terminates overall.
- This means that threads for any remaining pipeline input then never even get to launch.
- In the above example, only
1and2print; input4never gets processed, because processing of3caused the timeout and therefore termination of the entire command.
- In the above example, only
- This means that threads for any remaining pipeline input then never even get to launch.
Applying the specified timeout:
- on a per-thread basis...
- ... and continuing processing of the inputs even if a single thread times out
is arguably much more sensible.
Therefore, I suggest implementing the latter:
-
Either: By simply changing the meaning of the current
-TimeoutSecondsparameter, assuming it amounts to a bucket 3 change. -
Or: By introducing a separate parameter / additional switch parameter as an opt-in to the proposed semantics.
[1] More accurately, only a fixed number of threads are allowed to run at a time, based on the -ThrottleLimit arguments, which defaults to 5. If more threads are needed, they have to wait until "slots" open up, which happens when currently executing threads finish.
Proposed technical implementation details (optional)
The following shows the desired semantics expressed in PowerShell code, applied to a slightly modified version of the example above, which provokes a timeout for the 3rd input object:
# Use -AsJob to receive a job that allows monitoring the threads individually.
# Note that -AsJob cannot be combined with -TimeoutSeconds
$job =
1..4 |
ForEach-Object -AsJob -ThrottleLimit 2 -Parallel {
if ($_ -eq 3) {
# Provoke a timeout error for this specific input.
Start-Sleep -MilliSeconds 2500; $_
} else {
Start-Sleep -MilliSeconds 1500; $_
}
}
# Receive job output in a polling loop, and terminate child jobs
# that have run too long.
$timeout = 2
do {
Start-Sleep -Milliseconds 500 # Sleep a little.
# Get pending results.
$job | Receive-Job
# If any child jobs have been running for more than N seconds,
# stop (terminate) them.
# This will open up slots for more threads to spin up.
foreach ($childJob in $job.ChildJobs.Where({ $_.State -eq 'Running' })) {
if (([datetime]::now - $childJob.PSBeginTime).TotalSeconds -ge $timeout) {
Write-Verbose -Verbose "Stopping job with ID $($childJob.Id) due to running longer than $timeout seconds..."
$childJob | Stop-Job
}
}
} while ($job.ChildJobs.Where({ $_.State -in 'NotStarted', 'Running' }))
Output:
1
2
4
VERBOSE: Stopping job with ID 4 due to running longer than 2 seconds...
Note:
-
Input
4was still processed, despite the thread for input3having timed out. -
The
IDvalue of the child job isn't really meaningful except to distinguish it from other child jobs; if you want to know what input object caused the timeout, you'll have to echo it as part of the script block (at the start, before a timeout can occur) - the job object doesn't contain this information.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the ForEach-Object -Parallel examples with -TimeoutSeconds, -ThrottleLimit, and -AsJob. Compare the current overall timeout behavior with the proposed per-thread behavior, including continued processing after one timeout. Done means either the existing parameter has the new semantics or a documented opt-in parameter provides them, with coverage for throttled inputs and timed-out threads.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- powershell
- Domain
- cli
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100