Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/10464.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Changed connection creation to explicitly close sockets if an exception is raised in the event loop's ``create_connection`` method.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Andrej Antonov
Andrew Leech
Andrew Lytvyn
Andrew Svetlov
Andrew Top
Andrew Zhou
Andrii Soldatenko
Anes Abismail
Expand Down
13 changes: 12 additions & 1 deletion aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,7 @@ async def _wrap_create_connection(
client_error: Type[Exception] = ClientConnectorError,
**kwargs: Any,
) -> Tuple[asyncio.Transport, ResponseHandler]:
sock = None
try:
async with ceil_timeout(
timeout.sock_connect, ceil_threshold=timeout.ceil_threshold
Expand All @@ -1112,7 +1113,11 @@ async def _wrap_create_connection(
interleave=self._interleave,
loop=self._loop,
)
return await self._loop.create_connection(*args, **kwargs, sock=sock)
connection = await self._loop.create_connection(
*args, **kwargs, sock=sock
)
sock = None
return connection
except cert_errors as exc:
raise ClientConnectorCertificateError(req.connection_key, exc) from exc
except ssl_errors as exc:
Expand All @@ -1121,6 +1126,12 @@ async def _wrap_create_connection(
if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
raise
raise client_error(req.connection_key, exc) from exc
finally:
if sock is not None:
# Will be hit if an exception is thrown before the event loop takes the socket.
# In that case, proactively close the socket to guard against event loop leaks.
# For example, see https://github.com/MagicStack/uvloop/issues/653.
sock.close()

def _warn_about_tls_in_tls(
self,
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def start_connection() -> Iterator[mock.Mock]:
"aiohttp.connector.aiohappyeyeballs.start_connection",
autospec=True,
spec_set=True,
return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),
) as start_connection_mock:
yield start_connection_mock

Expand Down
21 changes: 21 additions & 0 deletions tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,27 @@ async def test_tcp_connector_certificate_error(
await conn.close()


async def test_tcp_connector_closes_socket_on_error(
loop: asyncio.AbstractEventLoop, start_connection: mock.AsyncMock
) -> None:
req = ClientRequest("GET", URL("https://127.0.0.1:443"), loop=loop)

conn = aiohttp.TCPConnector()
with mock.patch.object(
conn._loop,
"create_connection",
autospec=True,
spec_set=True,
side_effect=ValueError,
):
with pytest.raises(ValueError):
await conn.connect(req, [], ClientTimeout())

assert start_connection.return_value.close.called

await conn.close()


async def test_tcp_connector_server_hostname_default(
loop: asyncio.AbstractEventLoop, start_connection: mock.AsyncMock
) -> None:
Expand Down
1 change: 1 addition & 0 deletions tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ async def make_conn() -> aiohttp.TCPConnector:
"aiohttp.connector.aiohappyeyeballs.start_connection",
autospec=True,
spec_set=True,
return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),
)
def test_proxy_connection_error(self, start_connection: mock.Mock) -> None:
async def make_conn() -> aiohttp.TCPConnector:
Expand Down
Loading