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