fix: aborted internal background jobs during shell cancellation

- Added Job::abort_internal_tasks to abort internal async tasks and drop their join handles.
- Updated shell cancellation paths to call this abort logic and handle mutable shell job lists before signaling remaining process groups.
- Added Rust and TypeScript tests that verify cancellation prevents background shell jobs from completing after abort.
This commit is contained in:
can1357
2026-06-02 19:29:23 +02:00
parent ecc5a6d5d5
commit cd9fc55572
3 changed files with 100 additions and 11 deletions
@@ -570,11 +570,15 @@ describe("executeBash", () => {
if (process.platform === "win32") return;
const marker = path.join(tempDir, "marker-bg-abort.txt");
const release = path.join(tempDir, "marker-bg-abort.release");
const started = path.join(tempDir, "marker-bg-abort.started");
const markerEscaped = marker.replace(/'/g, "'\\''");
const releaseEscaped = release.replace(/'/g, "'\\''");
const startedEscaped = started.replace(/'/g, "'\\''");
const controller = new AbortController();
const promise = executeBash(
`{ sleep ${KILL_MARKER_DELAY_SECONDS}; echo done > '${markerEscaped}'; } & sleep 10`,
`{ touch '${startedEscaped}'; while [ ! -f '${releaseEscaped}' ]; do sleep 0.05; done; echo done > '${markerEscaped}'; } & sleep 10`,
{
cwd: tempDir,
timeout: 10000,
@@ -582,7 +586,11 @@ describe("executeBash", () => {
},
);
await Bun.sleep(100);
const startDeadline = Date.now() + 4000;
while (!fs.existsSync(started) && Date.now() < startDeadline) {
await Bun.sleep(2);
}
expect(fs.existsSync(started)).toBe(true);
controller.abort();
const result = await promise;
@@ -590,6 +598,8 @@ describe("executeBash", () => {
expect(result.output).toContain("Command cancelled");
await Bun.sleep(KILL_MARKER_ASSERTION_WAIT_MS);
fs.writeFileSync(release, "");
await Bun.sleep(150);
expect(fs.existsSync(marker)).toBe(false);
});