Enforce process ownership: guarantee process termination across all execution paths - #339
Conversation
|
This is a breaking change, but it only affects very specific edge cases where all the following conditions were true:
In most cases, in the event of std streams closing, the process would just exit anyway. Also, theoretically the streams may not close in a rare case where the CliWrap/CliWrap/Command.Execution.cs Lines 250 to 260 in 234ae44 Additionally, the pull event stream execution model currently employs the behavior of detaching from the process when the iterator is abandoned (i.e., reached The ultimate goal of this PR is to normalize the behavior under a simple and predictable expectation: when The open question remains about the @copilot please share your thoughts |
Agree with normalizing everything under "when On streams not closing / On
One caveat: that disposable also runs on normal completion, because So I'd gently push back on "there isn't a clear way to tell": there is, and it's symmetric with the |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## prime #339 +/- ##
==========================================
- Coverage 92.46% 92.45% -0.02%
==========================================
Files 26 26
Lines 1155 1166 +11
Branches 68 69 +1
==========================================
+ Hits 1068 1078 +10
+ Misses 66 65 -1
- Partials 21 23 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Hmm, but what makes that disposable get disposed? What if I just run In any case, if the fix is simple and symmetrical, we should apply it to the |
|
@copilot why do you require two pull requests? You already own this one |
You're right — no need for a second PR. I've made the
Applied the symmetric fix to On your question: The fix mirrors |
When a pipe delegate threw an exception, the underlying process was left running in the background. In the worst case (process blocked writing to a full pipe buffer with no reader), this became a permanent deadlock with no way out short of user cancellation.
The new convention: CliWrap takes full ownership of the process it spawns and guarantees it is terminated before any execution method returns or throws, regardless of exit path.
Changes
Command.Execution.csTask.WhenAny(waitTask, pipingTask)to detect early pipe failure, then immediatelyKill()the process before awaiting it — prevents the deadlock where the process is blocked on a full pipe buffer with no readertry/finallywithprocess.Kill()as a belt-and-suspenders guarantee covering any exit path not already handled by the inline killPullEventStreamCommandExtensions.csabandonCts(which let the process keep running in the background when the consumer broke out) withkillCtswired as the forceful cancellation token intoExecuteAsyncawait foreachnow cancelskillCts, killing the process beforeListenAsyncreturnsReadme.mdListenAsyncbreak behaviorExample: previously broken, now correct