Skip to content

asyncio does not set TCP_NODELAY on macOS #148783

Open

Description

@SEIAROTg

Bug report

Bug description:

On macOS, asyncio server silently skips setting TCP_NODELAY on accepted connections.

Reproduce

import asyncio
import socket

async def main():
    async def on_connect(reader, writer):
        sock = writer.transport.get_extra_info('socket')
        nodelay = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
        print(f'server side: sock.proto={sock.proto} TCP_NODELAY={nodelay}')
        writer.close()
        await writer.wait_closed()
        srv.close()

    srv = await asyncio.start_server(on_connect, '127.0.0.1', 0)
    port = srv.sockets[0].getsockname()[1]

    reader, writer = await asyncio.open_connection('127.0.0.1', port)
    sock = writer.transport.get_extra_info('socket')
    nodelay = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
    print(f'client side: sock.proto={sock.proto} TCP_NODELAY={nodelay}')

    await asyncio.sleep(0.1)
    writer.close()
    await writer.wait_closed()
    await srv.wait_closed()

asyncio.run(main())
Platform Side sock.proto TCP_NODELAY
Linux client 6 ✅ 1 ✅
Linux server 6 ✅ 1 ✅
macOS client 6 ✅ 4 ✅
macOS server 0 ❌ 0 ❌

Cause

In socket.__init__, CPython tries to detect proto with getsockopt + SO_PROTOCOL and falls back to 0 if SO_PROTOCOL is not supported, which is the case on macOS.

cpython/Modules/socketmodule.c

Lines 5749 to 5764 in 8276778

#ifdef SO_PROTOCOL
if (proto == -1) {
int tmp;
socklen_t slen = sizeof(tmp);
if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL,
(void *)&tmp, &slen) == 0)
{
proto = tmp;
} else {
set_error();
return -1;
}
}
#else
proto = 0;
#endif

While socket.accept does pass in the proto from listening socket, it is silently overridden to zero by the above logic.

sock = socket(self.family, self.type, self.proto, fileno=fd)

asyncio sets TCP_NODELAY only when sock.proto == socket.IPPROTO_TCP, but it is 0 on macOS for the above reason.

if hasattr(socket, 'TCP_NODELAY'):
def _set_nodelay(sock):
if (sock.family in {socket.AF_INET, socket.AF_INET6} and
sock.type == socket.SOCK_STREAM and
sock.proto == socket.IPPROTO_TCP):
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
else:
def _set_nodelay(sock):
pass

Client socket works fine because no fd is passed to socket.__init__ so the proto detection doesn't happen.

CPython versions tested on:

3.13

Operating systems tested on:

macOS, Linux

Linked PRs

Activity

  1. added 2 commits that reference this issue on Apr 19, 2026
  2. tzipora-rose commented on Oct 20, 2026

    @tzipora-rose

    Windows is affected too, through two paths, and gh-148784 covers one of them.

    The reproduction above on Windows 10, CPython 3.12.10:

    Event loop Side sock.proto TCP_NODELAY
    ProactorEventLoop (the default) client 6 1
    ProactorEventLoop (the default) server 0 0
    WindowsSelectorEventLoop client 6 1
    WindowsSelectorEventLoop server 0 0

    The selector loop has the cause described here: SO_PROTOCOL is not defined on Windows, so socket.__init__ replaces the proto that socket.accept() passes with 0 (on 3.12.10, socket.socket(AF_INET, SOCK_STREAM, 6, fileno=fd).proto is 0). gh-148784 keeps a proto passed explicitly, which covers this path.

    The proactor loop has a second cause, which gh-148784 does not reach: IocpProactor._get_accept_socket creates the socket that AcceptEx accepts into with socket.socket(family), so its proto is 0 and _set_nodelay skips it. No file descriptor is passed there, so the detection is not involved. With _get_accept_socket creating that socket with socket.SOCK_STREAM, socket.IPPROTO_TCP, the reproduction's server side gives sock.proto=6 TCP_NODELAY=1. The proactor transport has called _set_nodelay since bpo-35380 (gh-79561), but for connections the proactor accepts the call does nothing.

    Both causes are in main as of 2026-10-20.

    Investigated and written in collaboration with Claudia in Claude Code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions