mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
fix(runtime): avoid idle waits for queued fork IPC (#392)
This commit is contained in:
@@ -53,6 +53,7 @@
|
||||
* their empty implementation makes the absence explicit at link time
|
||||
* without weakening async/timer support. */
|
||||
bool scr_children_pending(void) { return false; }
|
||||
bool scr_children_ready(void) { return false; }
|
||||
bool scr_children_reffed_pending(void) { return false; }
|
||||
bool scr_children_failed_pending(void) { return false; }
|
||||
void scr_children_poll(void) {}
|
||||
@@ -2614,6 +2615,11 @@ bool scr_loop_run(ScrPromise *top_level) {
|
||||
/* Pending immediates are always-ready work: no sleep — run due timers
|
||||
* (Node's timers phase precedes check), then the check phase below. */
|
||||
if (scr_pending_immediates > 0) due = now;
|
||||
/* Child polling can drain a pipe into the IPC queue after its dispatch
|
||||
* station has run. Completed sends and disconnects can also become ready
|
||||
* during that turn. No fd will wake us for this userspace work: return to
|
||||
* dispatch without sleeping, still allowing due timers and immediates. */
|
||||
if (scr_children_ready()) due = now;
|
||||
bool evw = scr_events_watching_fn != NULL && scr_events_watching_fn();
|
||||
if (io) {
|
||||
if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
||||
|
||||
@@ -5660,6 +5660,20 @@ static bool scr_ipc_pending(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool scr_children_ready(void) {
|
||||
for (ScrIpc *ipc = scr_ipcs; ipc != NULL; ipc = ipc->next) {
|
||||
if (ipc->disconnect_pending || (ipc->n_pending > 0 && ipc->n_message > 0)) {
|
||||
return true;
|
||||
}
|
||||
bool writer_pending = scr_child_writer_pending(ipc->writer);
|
||||
if (ipc->n_send > 0 && (ipc->send_error != NULL || !writer_pending)) {
|
||||
return true;
|
||||
}
|
||||
if (ipc->local_closing && !writer_pending) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double scr_process_fork_target(double target_count) {
|
||||
if (scr_process_fork_id != -2) return scr_process_fork_id;
|
||||
double target = -1;
|
||||
|
||||
@@ -2806,6 +2806,9 @@ const char *scr_signal_name(int sig);
|
||||
* pending child: non-kqueue platforms, spawn failures awaiting their
|
||||
* first-pass settle, or a child whose exit filter could not be armed. */
|
||||
bool scr_children_pending(void);
|
||||
/* Work already queued in userspace: dispatch can progress without another
|
||||
* pipe/exit notification. Connected channels and blocked writes are not ready. */
|
||||
bool scr_children_ready(void);
|
||||
bool scr_children_failed_pending(void);
|
||||
void scr_children_poll(void);
|
||||
bool scr_children_wait(double max_wait_ms);
|
||||
|
||||
@@ -137,6 +137,7 @@ const hostLaneContractPattern = [
|
||||
"udp-loopback-pair",
|
||||
"1564-fs-watch.ts",
|
||||
"1470-child-lifecycle.ts",
|
||||
"2963-child-fork-dispatch/main.ts",
|
||||
"read-all: chunked writes with delays, then EOF",
|
||||
].join("|");
|
||||
const hostInvariantContractFiles = [
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { fork } from "node:child_process";
|
||||
|
||||
const child = fork(new URL("./worker.ts", import.meta.url), [], {
|
||||
stdio: ["ignore", "ignore", "inherit", "ipc"],
|
||||
});
|
||||
|
||||
// Start the deadline after the child is ready so process startup is not part
|
||||
// of the bound. Queued IPC must not wait for the idle poll timeout (1 second
|
||||
// per handoff), or for this timer to wake the loop. No periodic wakeups.
|
||||
let replies = 0;
|
||||
let sent = 0;
|
||||
let disconnected = false;
|
||||
child.once("message", () => {
|
||||
const deadline = setTimeout(() => {
|
||||
console.log("IPC stalled");
|
||||
}, 500);
|
||||
child.on("message", (message: { value: number }) => {
|
||||
replies++;
|
||||
if (message.value < 3) {
|
||||
child.send({ value: message.value + 1 }, (error) => {
|
||||
if (error) throw error;
|
||||
sent++;
|
||||
});
|
||||
}
|
||||
});
|
||||
child.once("disconnect", () => {
|
||||
disconnected = true;
|
||||
clearTimeout(deadline);
|
||||
});
|
||||
child.send({ value: 1 }, (error) => {
|
||||
if (error) throw error;
|
||||
sent++;
|
||||
});
|
||||
});
|
||||
child.once("exit", (code) => {
|
||||
console.log("replies", replies, "sent", sent, "disconnected", disconnected, "exit", code);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
process.on("message", (message: { value: number }) => {
|
||||
process.send?.({ value: message.value }, (error) => {
|
||||
if (error) throw error;
|
||||
if (message.value === 3) process.disconnect();
|
||||
});
|
||||
});
|
||||
process.send?.({ value: 0 });
|
||||
Reference in New Issue
Block a user