Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close the socket if there's a failure in start_connection() #10464

Merged
merged 9 commits into from
Feb 20, 2025
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