fix(pi-natives): bound shell reader shutdown wait

Fixes #230
This commit is contained in:
can1357
2026-03-01 11:40:19 +01:00
parent a97d03b7fb
commit 0a3c192d7d
2 changed files with 29 additions and 1 deletions
+10 -1
View File
@@ -599,6 +599,7 @@ async fn run_shell_command(
// for a short period, then cancel.
const POST_EXIT_IDLE: Duration = Duration::from_millis(250);
const POST_EXIT_MAX: Duration = Duration::from_secs(2);
const READER_SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(250);
let mut reader_finished = false;
let mut idle_timer = Box::pin(time::sleep(POST_EXIT_IDLE));
@@ -624,7 +625,15 @@ async fn run_shell_command(
if !reader_finished {
reader_cancel.cancel();
let _ = reader_handle.await;
match time::timeout(READER_SHUTDOWN_TIMEOUT, &mut reader_handle).await {
Ok(res) => {
let _ = res;
}
Err(_) => {
reader_handle.abort();
let _ = reader_handle.await;
}
}
}
cancel_bridge.abort();
let _ = cancel_bridge.await;
@@ -244,6 +244,25 @@ describe("executeBash", () => {
}
});
it("completes even when background job keeps stdout pipe open", async () => {
if (process.platform === "win32") return;
const runPromise = executeBash("{ sleep 3; echo late; } & echo immediate", {
cwd: tempDir,
timeout: 5000,
});
const timed = await Promise.race([
runPromise.then(result => ({ type: "result" as const, result })),
Bun.sleep(1500).then(() => ({ type: "timeout" as const })),
]);
expect(timed.type).toBe("result");
if (timed.type === "result") {
expect(timed.result.cancelled).toBe(false);
expect(timed.result.exitCode).toBe(0);
expect(timed.result.output).toContain("immediate");
}
});
it("kills spawned process on timeout (not just orphans it)", async () => {
if (process.platform === "win32") return;