Skip to content

[Windows] Stop silently dropping TCP_NODELAY - #3707

Open
jakepetroules wants to merge 1 commit into
apple:mainfrom
jakepetroules:windows-tcp-nodelay
Open

[Windows] Stop silently dropping TCP_NODELAY#3707
jakepetroules wants to merge 1 commit into
apple:mainfrom
jakepetroules:windows-tcp-nodelay

Conversation

@jakepetroules

@jakepetroules jakepetroules commented Aug 15, 2026

Copy link
Copy Markdown
Member

Motivation:

BaseSocket.setOption does not set TCP_NODELAY on a socket that is not an IP socket, because that call fails on UNIX domain sockets. To find the family, it read try? self.localAddress().protocol. If that failed, it treated the socket as "not IP" and set nothing.

On Windows this dropped the option every time:

  • Winsock fails getsockname for a socket that is not bound yet, so localAddress() throws.
  • NIO applies channel options before it binds or connects.
  • ClientBootstrap and ServerBootstrap both enable TCP_NODELAY by default.

So Nagle's algorithm stayed on for every NIO TCP channel on Windows.

Measured on a Windows ARM64 machine before this change:

NIOSETOPT level=6 name=1 value=1 type=Int32 size=4    // setsockopt succeeds
NIOSETOPT   readback=0                                // but the option is off
NIOCHAN raw client=0 accepted=0 listener=0            // all three channels

After this change, the read back value is 1.

Modifications:

Ask the socket for its address family, instead of reading an address it may not have yet. Winsock returns the family in WSAPROTOCOL_INFOW, which comes from SO_PROTOCOL_INFOW. Other platforms still use the local address, so their behaviour does not change.

Result:

TCP_NODELAY is now really applied on Windows.

This fixes ChannelTests.testTCP_NODELAYisOnByDefaultForInetSockets and testTCP_NODELAYisOnByDefaultForInet6Sockets. Please note that those tests cannot show it in this PR, because ChannelTests is still skipped on Windows. Another PR enables that suite, and with both PRs the two tests pass. The measurement above is the evidence for this PR on its own.

Tested on a Windows ARM64 machine (Swift 6.3.2): builds on its own, and part of a larger branch where a full swift test passes (2360 tests, 0 failures). NIOPosix still builds on macOS.

`BaseSocket.setOption` refuses to set `TCP_NODELAY` on sockets whose protocol
family is not IP, because doing so fails on UNIX domain sockets. It determined
the family from `try? self.localAddress().protocol`, treating a failure to
answer as "not an IP socket" and returning without setting anything.

On Windows that discards the option every time. Winsock fails `getsockname` for
a socket that has not been bound yet, where POSIX reports the wildcard address,
and channel options are applied before the socket is bound or connected. Both
`ClientBootstrap` and `ServerBootstrap` enable `TCP_NODELAY` by default, so in
practice Nagle's algorithm stayed on for every NIO TCP channel on Windows.

Ask the socket for its address family instead of deriving it from an address it
may not have yet: Winsock reports it in the `WSAPROTOCOL_INFOW` returned by
`SO_PROTOCOL_INFOW`. Other platforms keep using the local address exactly as
before.

This fixes `ChannelTests.testTCP_NODELAYisOnByDefaultForInetSockets` and
`testTCP_NODELAYisOnByDefaultForInet6Sockets`, which observed the option reading
back as 0 on Windows.
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.

1 participant