Repository navigation
Kill the child when ExecuteAsync is cancelled, as Execute does [patch] - #100
Merged
Merged
Conversation
Cancelling ExecuteAsync returned "Operation was cancelled." and disposed the Process, but disposing does not end the child, so the command ran on to completion and performed its side effects. The cancellation handler now kills the process before returning, like the synchronous path. TryKill also kills the whole process tree where the platform supports it (netcoreapp3.0+), so commands the shell started go with it on both paths. Fixes #53 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7wytU6jsZ1cH3f6grCTz5
Every await that can observe the token now sits inside the inner try, so no OperationCanceledException can reach the outer handler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7wytU6jsZ1cH3f6grCTz5
With a two-second side effect a loaded Windows runner observed the 250 ms cancellation only as the command finished, so the marker existed and the test blamed the kill. The command now waits ten seconds, each run asserts it returned well before that (a run that did not proves nothing either way), and the final wait is measured from the later run's start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7wytU6jsZ1cH3f6grCTz5
Same wait for a side effect that must not happen, but it ends early when the test run is cancelled, and it clears Sonar S2925. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7wytU6jsZ1cH3f6grCTz5
|
This was referenced Oct 8, 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.



Fixes #53
Problem
When
NativeCommandExecutor.ExecuteAsyncwas cancelled, it returnedOperation was cancelled.and disposed theProcess. Disposing doesn't end the child, so the command kept running and still performed its side effects. The synchronousExecutealready kills the child on cancellation.Change
ExecuteAsyncnow catchesOperationCanceledExceptioninside the scope where the process exists, and callsTryKill(process)before returningCancelled(). A token that is already cancelled before start still short-circuits as before.OperationCanceledExceptionhandler is gone. Every await that can observe the token is now inside the inner try, so that handler could no longer be reached.TryKillusesKill(entireProcessTree: true)on netcoreapp3.0+, so on both paths any commands the shell started are killed with it. It falls back toKill()on netstandard2.1.Tests
CommandExecutor_Cancellation_Stops_The_Commandrunssleep 10; touch <marker>through each path (ping/type nulon Windows). It cancels after 250 ms and asserts that each run returned well before the 10 s side effect was due. It then waits until the side effect would have happened and asserts that neither marker exists.TryKillcall in the async handler, the test fails onAssert.IsFalse(File.Exists(asyncMarker)). With the fix, it passes.Essentials.CommandExecutors.Nativeis clean on all targets.This merges cleanly with
mainafter #99 (Unix argument quoting).🤖 Generated with Claude Code
https://claude.ai/code/session_01D7wytU6jsZ1cH3f6grCTz5