Skip to content

asyncio.create_connection() leaks the socket if the transport cannot be created #153133

Description

@deadlovelll

Bug report

Bug description:

Bug description

BaseEventLoop._create_connection_transport() closes the socket only if await waiter fails:

try:
    await waiter
except:
    transport.close()
    raise

If sock.setblocking(), protocol_factory() or _make_*_transport() raises before the transport exists, the socket is never closed

Reproducer

import asyncio
import socket


async def main():
    loop = asyncio.get_running_loop()
    sock = socket.socket()
    try:
        await loop.create_connection(lambda: 1 / 0, sock=sock)
    except ZeroDivisionError:
        pass
    print("leak: socket not closed" if sock.fileno() != -1 else "ok: closed")
    sock.close()


asyncio.run(main())

Expected: ok: closed but actually: leak: socket not closed

Proposed fix

Close the socket on error when the transport hasn't been created yet:

async def _create_connection_transport(
        self, sock, protocol_factory, ssl,
        server_hostname, server_side=False,
        ssl_handshake_timeout=None,
        ssl_shutdown_timeout=None, context=None):

    transport = None
    try:
        sock.setblocking(False)
        context = context if context is not None else contextvars.copy_context()

        protocol = protocol_factory()
        waiter = self.create_future()
        if ssl:
            sslcontext = None if isinstance(ssl, bool) else ssl
            transport = self._make_ssl_transport(
                sock, protocol, sslcontext, waiter,
                server_side=server_side, server_hostname=server_hostname,
                ssl_handshake_timeout=ssl_handshake_timeout,
                ssl_shutdown_timeout=ssl_shutdown_timeout,
                context=context)
        else:
            transport = self._make_socket_transport(sock, protocol, waiter, context=context)

        await waiter
    except:
        if transport is None:
            sock.close()
        else:
            transport.close()
        raise

    return transport, protocol

I have a fix ready

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions