Skip to content

[NODERAWSOCKETS] Bind-first connect for synchronous getsockname - #27566

Merged
sbc100 merged 1 commit into
emscripten-core:mainfrom
guybedford:noderawsockets-connect-getsockname
Aug 27, 2026
Merged

[NODERAWSOCKETS] Bind-first connect for synchronous getsockname#27566
sbc100 merged 1 commit into
emscripten-core:mainfrom
guybedford:noderawsockets-connect-getsockname

Conversation

@guybedford

Copy link
Copy Markdown
Collaborator

This fixes an ordering issue in the NODERAWSOCKETS TCP client connect path.

An unbound client connect() created a plain async net.Socket, with saddr/sport only recorded on the async 'connect' event, so getsockname() immediately after a non-blocking connect() returned 0.0.0.0:0. Kernel semantics assign the ephemeral source port synchronously at connect(), so callers that read the local address right after a non-blocking connect raced the event loop (reliably failing under load).

connect() on an unbound socket now binds an ephemeral port first, through the same eager synchronous bindHandle path an explicit bind() takes, then connects through the bound handle:

  • getsockname() reports the assigned port synchronously after connect(), matching kernel behavior
  • the local address updates to the real source address once the connection completes, and the port stays stable
  • both bind primitives (public net.BoundSocket, or the tcp_wrap fallback on older node) are synchronous, so this works across node versions

Test coverage: test_noderawsockets_connect_getsockname asserts the ephemeral port is non-zero immediately after a non-blocking connect() returns, before any event loop turn, and that it is unchanged once connected. The test is plain POSIX and passes natively against the host stack.

Made with AI assistance under my review

An unbound TCP client connect created a plain async net.Socket, with
saddr/sport only recorded on the async 'connect' event, so getsockname()
immediately after a non-blocking connect() returned 0.0.0.0:0. Kernel
semantics assign the ephemeral source port synchronously at connect(), so
callers (e.g. mio) that read the local address right after a non-blocking
connect raced the event loop.

connect() now binds an ephemeral port first through the same eager
synchronous bindHandle path an explicit bind() takes, then connects
through the bound handle, making getsockname() correct up front.
@guybedford
guybedford force-pushed the noderawsockets-connect-getsockname branch from 468d477 to f9fedd7 Compare August 19, 2026 15:53
@guybedford

Copy link
Copy Markdown
Collaborator Author

@sbc100 this one still has approval, is it ok for me to land?

Comment thread test/test_sockets_node.py
server.server_close()
thread.join()

def test_noderawsockets_connect_getsockname(self):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it make sense to rename all the tests in this file from test_noderawsockets_xxx to just test_xxx not that they are in their own file? (Or are not all of them raw socket tests)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah good point, I can include this in a subsequent cleanup.

@sbc100
sbc100 merged commit 6c40a27 into emscripten-core:main Aug 27, 2026
42 checks passed
@guybedford
guybedford deleted the noderawsockets-connect-getsockname branch August 27, 2026 04:33
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.

2 participants