fix(ext/node): support ChildProcess.send with net.Server handles#34948
Merged
Conversation
`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
approved these changes
Jun 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
subprocess.send('server', server)fromnode:child_process, whereserveris an unlistenednet.createServer(), threw:An unlistened
net.Serverhas anullunderlying handle, andgetIpcHandleInfoonly accepted TCP handles. This makesChildProcess.sendwithnet.Server/net.Sockethandles work in the cases Node supports.Changes
net.Server(or detachednet.Socket) has a null underlying handle.getIpcHandleInfonow returnsnullin that case and the message is delivered without a handle, matching Node'sif (!handle) message = message.msg. This is the exact case in the issue's repro.net.Serverandnet.Sockethandles backed by aPipeare now transferable. The IPC message recordsnativeKind("tcp"|"pipe") so the receiver reconstructs the correct wrap type.net.Server.prototype.listenPipe branch. APipewrap exposes anfdgetter, so without an explicit handle branchlisten(pipe)fell into theoptions.fdpath and re-opened the already-owned fd, failing withEEXIST.uv_pipe_open_listener(mirrors the existinguv_tcp_open_listener). A pipe opened from an inherited listening fd must not eagerly create anAsyncFd; otherwiseuv_pipe_listen'sUnixListenerregistration hitsepoll_ctl(EPOLL_CTL_ADD)EEXISTon the same fd.PipeWrap::opendispatches 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 workingnet.Server.Both run under
jsonandadvancedserialization. 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
closeAfterSendunlinks the socket file on the parent'sserver.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