diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index 56bdabbff14..8b4c57777d4 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Management.Automation.Host; using System.Management.Automation.Language; using System.Management.Automation.Remoting.Internal; @@ -1056,7 +1057,7 @@ public override bool HasMoreData { get { - foreach (var childJob in ChildJobs) + foreach (var childJob in ChildJobs.ToArray()) { if (childJob.HasMoreData) { @@ -1137,9 +1138,9 @@ internal void Start() System.Threading.ThreadPool.QueueUserWorkItem( (_) => { - foreach (var childJob in ChildJobs) + foreach (PSTaskChildJob childJob in ChildJobs.ToArray()) { - _taskPool.Add((PSTaskChildJob)childJob); + _taskPool.Add(childJob); } _taskPool.Close(); @@ -1162,7 +1163,7 @@ private void HandleTaskPoolComplete(object sender, EventArgs args) // Final state will be 'Complete', only if all child jobs completed successfully. JobState finalState = JobState.Completed; - foreach (var childJob in ChildJobs) + foreach (var childJob in ChildJobs.ToArray()) { if (childJob.JobStateInfo.State != JobState.Completed) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 index de384c6ad78..174851cdc57 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 @@ -486,6 +486,44 @@ Describe 'ForEach-Object -Parallel -AsJob Basic Tests' -Tags 'CI' { $job | Wait-Job | Remove-Job } + It 'Does not crash when the ChildJobs collection is mutated while a parallel job starts' { + $testScript = Join-Path $TestDrive 'Mutate-ChildJobs.ps1' + $stdoutPath = Join-Path $TestDrive 'stdout.txt' + $stderrPath = Join-Path $TestDrive 'stderr.txt' + Set-Content -LiteralPath $testScript -Value @' +$job = 0..20 | ForEach-Object -AsJob -Parallel { + Start-Sleep -Milliseconds 300 + $_ +} -ThrottleLimit 2 + +Start-Sleep -Milliseconds 100 +$deadline = [DateTime]::UtcNow.AddSeconds(30) +while ($job.ChildJobs.Count -le 15 -and [DateTime]::UtcNow -lt $deadline) { + Start-Sleep -Milliseconds 100 +} + +if ($job.ChildJobs.Count -le 15) { + throw "Timed out waiting for child jobs to be created." +} + +$job.ChildJobs.RemoveAt(15) + +$job | Wait-Job -Timeout 30 | Out-Null +$job | Remove-Job -Force +'@ + + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" + $process = Start-Process -FilePath $powershell -ArgumentList @( + '-NoLogo' + '-NoProfile' + '-NonInteractive' + '-File' + $testScript + ) -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -Wait -PassThru + + $process.ExitCode | Should -Be 0 + } + It 'Verifies dollar underbar variable' { $expected = 1..10