Skip to content

asyncio proactor server leaks the accepted socket and stops accepting when transport creation fails #156365

Open

Description

@tonghuaroot

Bug report

Bug description:

On the proactor event loop (the default on Windows), BaseProactorEventLoop._start_serving()'s loop() callback runs protocol_factory() and transport creation in the same callback that re-arms accept(), guarded only by except OSError / except CancelledError. When protocol_factory() or _make_ssl_transport() / _make_socket_transport() raises for an accepted connection:

  • a non-OSError (e.g. a failing protocol_factory) propagates out of loop(): the accepted socket is never closed (it leaks until garbage collection) and accept is not re-armed, so the server stops accepting new connections;
  • an OSError is caught as 'Accept failed on a socket', which closes the listening socket, and still leaks the accepted socket.

This is the proactor counterpart of gh-155934 (selector accept) and gh-153133 (create_connection): an accepted socket must be closed when the transport is never created, and a per-connection failure must not tear down the listening socket or the accept loop.

Verified with mock-based tests in test_proactor_events (the same approach used for the selector fix in gh-155934), which run on all platforms.

CPython versions tested on:

CPython main

Operating systems tested on:

Windows (ProactorEventLoop)

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    stdlibStandard Library Python modules in the Lib/ directory
    on Aug 25, 2026
  2. Chris0Jeky commented on Nov 5, 2026

    @Chris0Jeky

    A related case on the same path: accept() itself failing with a transient OSError also closes the listening socket for good.

    On the proactor event loop, BaseProactorEventLoop._start_serving() handles any OSError from self._proactor.accept(sock) (or from f.result()) by calling the exception handler ("Accept failed on a socket") and then sock.close() on the listening socket. When the error is transient, such as WinError 10055 (WSAENOBUFS, "the system lacked sufficient buffer space or because a queue was full"), which a Windows host can return briefly under memory pressure, the server never accepts again: the process stays alive and keeps running its other work, but the port refuses every new connection. We hit this on a long-running aiohttp server on a machine under heavy memory pressure.

    Seen on CPython 3.12.10 and 3.14.3 (Windows, default proactor loop); the code path is the same in both.

    Minimal repro (Windows; injects one WinError 10055 from accept, then tries a client connection):

    import asyncio, socket
    
    async def main():
        loop = asyncio.get_running_loop()
        proactor = loop._proactor
        orig, fired = proactor.accept, []
    
        def flaky(sock):
            if not fired:
                fired.append(1)
                raise OSError(10055, "no buffer space", None, 10055)  # WSAENOBUFS, once
            return orig(sock)
    
        proactor.accept = flaky
        loop.set_exception_handler(lambda l, ctx: print("handler:", ctx["message"]))
    
        async def handle(reader, writer):
            writer.write(b"ok"); await writer.drain(); writer.close()
    
        lsock = socket.socket(); lsock.bind(("127.0.0.1", 0)); port = lsock.getsockname()[1]
        server = await asyncio.start_server(handle, sock=lsock)
        await asyncio.sleep(0.5)
        try:
            r, w = await asyncio.wait_for(asyncio.open_connection("127.0.0.1", port), 3)
            print("connected:", await r.read(2)); w.close()
        except OSError as e:
            print("connection refused after one transient accept error:", e)
        server.close()
    
    asyncio.run(main())

    Output on 3.12.10:

    handler: Accept failed on a socket
    connection refused after one transient accept error: [WinError 1225] The remote computer refused the network connection
    

    Retrying after a short backoff instead of closing the socket (keeping the sock.fileno() == -1 case as is) keeps the server alive in this repro. Related: gh-93821 (ERROR_NETNAME_DELETED closing the serving socket).

  3. added a commit that references this issue on Nov 6, 2026
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

    OS-windowsstdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions