mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Fire spawn listeners for a child that exits before its first poll pass
A child spawned from a listener that runs during a poll pass, such as a 'close' listener, can exit before the next pass fires its 'spawn' listeners. The same pass's waitpid loop then settled it with 'spawn' never emitted. Settling an exited child now fires its pending 'spawn' listeners first, before its stream data and 'exit', as Node emits them.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7640-child-spawn-before-fast-exit.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -2780,7 +2780,8 @@ static void scr_child_settle(ScrChild *c) {
|
||||
scr_children_reffed_n--;
|
||||
}
|
||||
if (c->state == SCR_CHILD_EXITED) {
|
||||
scr_child_stream_drain_now(c->out_stream);
|
||||
if (!c->spawn_done) scr_child_fire_spawn(c);
|
||||
if (!scr_exc_pending()) scr_child_stream_drain_now(c->out_stream);
|
||||
if (!scr_exc_pending()) scr_child_stream_drain_now(c->err_stream);
|
||||
if (!scr_exc_pending()) scr_ipc_settle(c->ipc);
|
||||
}
|
||||
@@ -5607,9 +5608,12 @@ static void scr_child_settle(ScrChild *c) {
|
||||
/* Streams first, settled after: an exit listener registered from a
|
||||
* draining 'data'/'end' callback still fires below, like Node's. The
|
||||
* SPAWN-FAILURE path skips the drain — Node fires 'error' first and
|
||||
* the streams' immediate EOF delivers 'end' on the next turn. */
|
||||
* the streams' immediate EOF delivers 'end' on the next turn. A child
|
||||
* spawned during this pass can exit before the next pass fires 'spawn';
|
||||
* Node emits 'spawn' before any data or 'exit', so it fires here first. */
|
||||
if (c->state == SCR_CHILD_EXITED) {
|
||||
scr_child_stream_drain_now(c->out_stream);
|
||||
if (!c->spawn_done) scr_child_fire_spawn(c);
|
||||
if (!scr_exc_pending()) scr_child_stream_drain_now(c->out_stream);
|
||||
if (!scr_exc_pending()) scr_child_stream_drain_now(c->err_stream);
|
||||
if (!scr_exc_pending()) scr_ipc_settle(c->ipc);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// A child spawned from a 'close' listener that exits before the listener
|
||||
// returns still emits 'spawn' before 'exit'.
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
function busyWait(ms: number): void {
|
||||
const end = Date.now() + ms;
|
||||
while (Date.now() < end) {}
|
||||
}
|
||||
|
||||
const events: string[] = [];
|
||||
spawn("sh", ["-c", "exit 0"], { stdio: "ignore" }).on("close", () => {
|
||||
const child = spawn("sh", ["-c", "exit 4"], { stdio: "ignore" });
|
||||
child.on("spawn", () => events.push("spawn"));
|
||||
child.on("exit", (code) => events.push(`exit ${code}`));
|
||||
child.on("close", (code) => {
|
||||
events.push(`close ${code}`);
|
||||
console.log(events.join(", "));
|
||||
});
|
||||
busyWait(300);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2600, "san": 4000 }
|
||||
Reference in New Issue
Block a user