Skip to content

fix(ext/node): support ChildProcess.send with net.Server handles#34948

Merged
littledivy merged 1 commit into
mainfrom
orch/divybot-502
Jun 6, 2026
Merged

fix(ext/node): support ChildProcess.send with net.Server handles#34948
littledivy merged 1 commit into
mainfrom
orch/divybot-502

Conversation

@divybot

@divybot divybot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

subprocess.send('server', server) from node:child_process, where server is an unlistened net.createServer(), threw:

error: Uncaught (in promise) Error: Not implemented: ChildProcess.send with non-TCP net.Server handle

An unlistened net.Server has a null underlying handle, and getIpcHandleInfo only accepted TCP handles. This makes ChildProcess.send with net.Server / net.Socket handles work in the cases Node supports.

Changes

  • Null handle → plain message. An unlistened net.Server (or detached net.Socket) has a null underlying handle. getIpcHandleInfo now returns null in that case and the message is delivered without a handle, matching Node's if (!handle) message = message.msg. This is the exact case in the issue's repro.
  • Non-TCP (Pipe / unix-socket) handles. net.Server and net.Socket handles backed by a Pipe are now transferable. The IPC message records nativeKind ("tcp" | "pipe") so the receiver reconstructs the correct wrap type.
  • net.Server.prototype.listen Pipe branch. A Pipe wrap exposes an fd getter, so without an explicit handle branch listen(pipe) fell into the options.fd path and re-opened the already-owned fd, failing with EEXIST.
  • uv_pipe_open_listener (mirrors the existing uv_tcp_open_listener). A pipe opened from an inherited listening fd must not eagerly create an AsyncFd; otherwise uv_pipe_listen's UnixListener registration hits epoll_ctl(EPOLL_CTL_ADD) EEXIST on the same fd. PipeWrap::open dispatches to it for server pipes.

Tests

Adds tests/specs/node/child_process_ipc_handle/:

  • unlistened_server_main.mjs — the issue's repro (send an unlistened server; child receives the message with no handle).
  • pipe_server_main.mjs — transfer a listening unix-socket server; child reconstructs a working net.Server.

Both run under json and advanced serialization. Full suite (10 tests) passes:
cargo test --test specs -- node::child_process_ipc_handle.

Note: a full connect round-trip over a transferred unix-socket server isn't asserted because closeAfterSend unlinks the socket file on the parent's server.close(), which is inherent to unix sockets (TCP keeps working because the bound port stays valid while the child's dup'd fd listens).

Closes #34921

Closes denoland/divybot#502

`subprocess.send('server', server)` where `server` is an unlistened
`net.createServer()` threw `Not implemented: ChildProcess.send with
non-TCP net.Server handle`. An unlistened server has a null `_handle`,
and `getIpcHandleInfo` only accepted TCP handles.

- An unlistened net.Server / detached net.Socket has a null underlying
  handle. `getIpcHandleInfo` now returns null in that case and the
  message is sent without a handle, matching Node's
  `if (!handle) message = message.msg`.
- Accept non-TCP (Pipe / unix-socket) net.Server and net.Socket handles.
  The IPC message records `nativeKind` ("tcp" | "pipe") so the receiver
  reconstructs the correct wrap type.
- `net.Server.prototype.listen` gains a `Pipe` handle branch alongside
  the existing `TCP` one. A Pipe wrap exposes an `fd` getter, so without
  it `listen(pipe)` re-opened the already-owned fd and failed EEXIST.
- Add `uv_pipe_open_listener` (mirroring `uv_tcp_open_listener`) so a
  pipe opened from an inherited listening fd does not eagerly register an
  AsyncFd; `uv_pipe_listen`'s reactor registration would otherwise hit
  `epoll_ctl(ADD)` EEXIST on the same fd.

Adds spec tests covering the unlistened-server repro and a transferred
unix-socket server.

Co-Authored-By: Divy Srivastava <[email protected]>
@littledivy
littledivy merged commit 25cfe2b into main Jun 6, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-502 branch June 6, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChildProcess.send from node:child_process is not implemented

2 participants