Repository navigation
fix: cancelling PowerShell work no longer freezes the app when its powershell.exe has died - #2638
Merged
Merged
Conversation
…wershell.exe has died PowerShellRunner's cancellation called ps.Stop() on the thread that called Cancel(), and Stop() waits for the stop to land: with the child process dead, Cancel() blocked 58 s. The stop now goes to the thread pool and cannot throw. InvalidRunspaceStateException and InvalidPowerShellStateException, which no service or tab caught, now arrive as RuntimeException. A kept runspace whose child has exited is replaced before the next run, which had waited 60 s and failed, and its teardown is left to the thread pool. Closes #2608.
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2608.
Every script SysManager runs goes through
PowerShellRunnerinto a Windows PowerShell 5.1 child. I reproduced whathappens when that child dies, with a console program that runs a script printing its own
$PID, ends exactly thatpowershell.exe, and times what follows:Cancel()7–17 msCancel()blocked 58 sPSRemotingTransportExceptionKilling the child mid-run produced
PSRemotingTransportExceptionevery time, which the services already handle. Thetwo types the issue names are what
BeginInvokethrows for a runspace that is no longer open: an in-process probegives
InvalidRunspaceStateExceptionfor one that never opened and for one that was closed. Both derive fromSystemException, so they pass the services'catch (RuntimeException)and the tabs'catch (InvalidOperationException);RestorePointService.ListAsyncand the Restore Points and Task Scheduler view-modelscatch nothing else around that call.
What changes, all in
PowerShellRunner:ps.Stop()on the thread that calledCancel(), the UI thread for a Cancel button, andStop()waits for the stop to land; on a dead child that wasuntil the transport gave up.
RequestStophands the stop to the thread pool, through a newscheduleStopseam,and logs what
Stop()throws there. The re-assert afterBeginInvokegoes through it too.RuntimeException.IsSessionBrokenmatches the two types, directly or one level in, andthe run turns them into
RuntimeException("The Windows PowerShell session stopped working while the script was running."), as it already does for a host that cannot start or open in time.BeginInvokemoved inside theexisting
try, so one handler covers it and the wait.Openedfor about a minuteafter its child dies, so the lease now also asks
RunspaceResources.ChildHasExited. Disposing that runspace takesthe same minute, which the first version of this change still spent in the lease before building the new one, so
Retiredisposes a dead child's resources on the thread pool and a live one's at once, in the lease, the idleeviction and
Dispose.Not changed: when the child dies in the middle of a script, the run itself still ends only when the transport gives
up, about a minute later. Cancel no longer freezes anything in that minute, and the tab then reports the failure. I
have opened #2637 for ending that run as soon as the child exits.
Tests:
PowerShellRunnerTests: a runspace that is never opened givesRuntimeExceptionwithInvalidRunspaceStateExceptioninside;
Cancel()hands one stop to a scheduler that holds it, and the endless script is still running whenCancel()returns, then stops withOperationCanceledExceptiononce the stop is run; a cached runspace whoseprocess handle refers to nothing is rebuilt; and a replacement does not wait for the old session's teardown, which
the test holds until it ends.
PipelineStoppedDetectionTests: which exceptionsIsSessionBrokentakes, including that aRuntimeException, astop, a transport failure and an
InvalidOperationExceptionare left as they are.IntegrationTests/PowerShellRunnerTests.Runner_RebuildsARunspaceWhoseChildHasExited: the same rebuild with a realprocess that has exited.
Stop()leaves the scheduler empty; without the translationInvalidRunspaceStateExceptionarrives as itself; a predicate that matches nothing, misses the inner exception ormatches every
SystemExceptionfails its shape test; a lease that ignores the child, or reads a dead handle asalive, builds once instead of twice; and a teardown done in the lease holds the next run past its 30-second bound.
That last test passed against the defect at first, twice, for two separate reasons: the hold and the test's wait had
the same 30-second limit, and then the lease ran inside the
RunAsynccall itself. It now holds for two minutes andstarts the run from another thread.
ArchitectureTests.NoTestScript_CanRunForeverrequires,so a stop that never lands fails the test instead of hanging the suite.
Docs: ARCHITECTURE.md (the
PowerShellRunnerentry), CHANGELOG 1.127.3. Version 1.127.3.