Repository navigation
node:child_process: honor subprocess.send callback and closed-channel semantics #3316
Copy link
Copy link
Description
Summary
fork() now exposes a parent-side subprocess.send(...), but Perry does not match Node's observable optional-argument and closed-channel behavior. In Node, send has the message[, sendHandle[, options]][, callback] shape, invokes the callback when delivery succeeds or fails, and reports closed IPC channels. Perry currently ignores the optional argument slot and returns success regardless of whether the IPC write succeeded.
Node Behavior
Local Node v25.9.0 probe:
const { fork } = require("node:child_process");
const child = fork(childFile, [], { stdio: ["ignore", "ignore", "ignore", "ipc"] });
console.log(child.send.length); // 4
console.log(child.send({ ok: true }, (err) => console.log(err && err.code))); // true, callback gets null
child.disconnect();
console.log(child.send({ after: true }, (err) => console.log(err && err.code))); // false, callback gets ERR_IPC_CHANNEL_CLOSEDThe same API also accepts the documented sendHandle and options slots for IPC handle passing / serialization options.
Perry Behavior
Current source evidence:
crates/perry-runtime/src/child_process/fork.rs:95installssendon forkedChildProcessobjects ascp_cast2(cp_method_send).crates/perry-runtime/src/child_process/mod.rs:873-882implementscp_method_send(closure, message, _arg), ignores_arg, never detects a callback/options/sendHandle shape, callsreactor::cp_ipc_send(handle, message), discards that boolean result, and always returnstrue.crates/perry-runtime/src/child_process/mod.rs:985registers the method arity as2, while Node reportschild.send.length === 4.crates/perry-runtime/src/child_process/reactor.rs:328-378can report IPC write failure asfalse, but the publicsendmethod does not surface it or deliver a callback error.- Existing closed node:child_process — fork() + IPC (send/disconnect/'message'/channel) unimplemented #1933 covered making parent-side fork IPC exist; it did not track this callback / closed-channel / optional-argument behavior as a remaining leaf.
Suggested PR Cut
Batch this with related issues in:
node:child_process: subprocess lifecycle and setup parity cut
Good batch candidates:
- node:child_process: 57 surface methods still stubbed (ChildProcess.kill/disconnect/send + stdio + execFile) #2130
- node:child_process: implement subprocess[Symbol.dispose] lifecycle method #2556
- node:process: implement child-side IPC channel surface #2529
Do not batch with:
- unrelated modules
- broad subsystem rewrites
- spawn/exec option semantics that do not touch IPC delivery
- fixes needing a compiled-Perry child-process IPC harness unless the PR deliberately includes that harness work
Acceptance
- parity/regression test proves callback invocation on successful
subprocess.send(...) - parity/regression test proves a disconnected IPC channel returns
falseand calls the callback with a Node-shapedERR_IPC_CHANNEL_CLOSED subprocess.send.lengthmatches Node's observable arity- implementation handles or explicitly rejects unsupported
sendHandle/optionscases with Node-shaped behavior instead of silently ignoring them - related known-failure/docs/manifest entries updated if touched
Metadata
Metadata
Assignees
Labels
No labels
Activity
andrewtdiz commentedon Jun 2, 2026
Taking this with #2556 and #2717 as a focused node:child_process lifecycle/IPC surface PR cut. Planned scope: ChildProcess Symbol.dispose dispatch, _forkChild enumerable export shape, and subprocess.send callback/closed-channel observable semantics. Non-goals: broad spawn/exec option semantics (#2555), setup arg validation (#3079), and child-side process IPC surface (#2529) unless a minimal helper is required for the selected tests.
andrewtdiz commentedon Jun 2, 2026
Implemented in draft PR #4110.