Skip to content

net: add synchronous, role-neutral net.BoundHandle#63951

Open
guybedford wants to merge 4 commits into
nodejs:mainfrom
guybedford:tcp-bound-handle
Open

net: add synchronous, role-neutral net.BoundHandle#63951
guybedford wants to merge 4 commits into
nodejs:mainfrom
guybedford:tcp-bound-handle

Conversation

@guybedford

Copy link
Copy Markdown
Contributor

Add net.BoundHandle, a synchronous TCP bind primitive that mirrors POSIX bind(2): the socket is bound to a local address but stays role-agnostic until it is adopted as a server (server.listen()) or a client (new net.Socket({ handle }) followed by connect()).

Constructing a net.BoundHandle binds inline via the existing uv_tcp_bind() path, so the kernel-assigned address (including the ephemeral port when port is 0) is available immediately via boundHandle.address(), and bind errors throw synchronously.

Adoption transfers ownership of the underlying handle; afterwards address() and close() throw ERR_SOCKET_HANDLE_ADOPTED. An un-adopted handle is released with close() or via Symbol.dispose (using).

The handle is passed in-process rather than as a file descriptor, so it also works on Windows.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added errors Issues and PRs related to JavaScript errors originated in Node.js core. needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem. labels Jun 16, 2026
@guybedford guybedford force-pushed the tcp-bound-handle branch 2 times, most recently from 5db58e1 to 9da4d5f Compare June 17, 2026 00:07
Signed-off-by: Guy Bedford <guybedford@gmail.com>
@mcollina

Copy link
Copy Markdown
Member

I don't understand why this is needed

@Ethan-Arrowood Ethan-Arrowood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate this change. net.BoundHandle lets you reserve a TCP port and actually hold onto it until you're ready to use it. This simplifies utility modules like get-port that are susceptible to TOCTOU (Time-of-Check to Time-of-Use) race conditions. Of course, it doesn't solve the problem perfectly since this is in-process only, but I think it's a great improvement.

As a follow-up, it'd be great if BoundHandle could optionally expose its underlying file descriptor, so the reservation can be handed to another process (Unix-only, since this relies on POSIX fd passing). That would extend the same race-free guarantee across a process boundary.

@guybedford

guybedford commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for clarifying the use case here @Ethan-Arrowood. I've gone ahead and added a commit to expose the fd() as well here for the additional thread-passing use case.

@guybedford guybedford added request-ci Add this label to start a Jenkins CI on a PR. and removed request-ci Add this label to start a Jenkins CI on a PR. labels Jun 17, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@mcollina

Copy link
Copy Markdown
Member

@guybedford can you update the docs to clarify when somebody would need this?

@mcollina

Copy link
Copy Markdown
Member

is it possible to make this transferable across threads?

@guybedford

Copy link
Copy Markdown
Contributor Author

@mcollina I've gone ahead and simplified the docs to keep the explanation and example simple. Use cases for binding a net.Socket() are generally considered more obscure, but include multi-homed servers that must control egress IP or port, network tools (like ping/traceroute) and legacy privileged port protocols. This offers functional completeness with the posix model, while the major use case for most Node.js users I think will be the synchronous port reservation (either via explicit port or port: 0 auto assignment). IP reservation for multi-IP/virtual IP use cases may be less common there as well.

In terms of the design, I originally called this bindSync since it reflects the full functionality of bind(2), but since it returns a BoundHandle renamed it to be the BoundHandle constructor. If this is confusing, I can certainly rename it back to net.bindSync() or even just net.bind() though certainly. It effectively mirrors the new TCP() in TCP wrap, effectively closing the gap on use cases that would otherwise need to dip into that pseudo-private API.

For threads, I have already extended the API to support boundHandle.fd() for passing, so that the threaded use case can work here (by passing the fd option to listen({ fd }) within the receiving thread). The reason I didn't make BoundHandle itself serializable here is that on Windows we can't support this, so explicitly using fd , we make it clearer this is posix-specific.

Tidy the BoundHandle comments, document the BoundHandle handle option on
server.listen() and net.createConnection(), drop two unused doc link
definitions, and cover no-arg port reservation in the test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

errors Issues and PRs related to JavaScript errors originated in Node.js core. needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants