Skip to content

Commit 182198f

Browse files
authored
Explicitly close the socket if there is a failure in start_connection() (#10464)
1 parent 0c4b1c7 commit 182198f

File tree

6 files changed

+39
-1
lines changed

6 files changed

+39
-1
lines changed

CHANGES/10464.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Changed connection creation to explicitly close sockets if an exception is raised in the event loop's ``create_connection`` method -- by :user:`top-oai`.

CONTRIBUTORS.txt

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Andrej Antonov
4343
Andrew Leech
4444
Andrew Lytvyn
4545
Andrew Svetlov
46+
Andrew Top
4647
Andrew Zhou
4748
Andrii Soldatenko
4849
Anes Abismail

aiohttp/connector.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,7 @@ async def _wrap_create_connection(
11011101
client_error: Type[Exception] = ClientConnectorError,
11021102
**kwargs: Any,
11031103
) -> Tuple[asyncio.Transport, ResponseHandler]:
1104+
sock: Union[socket.socket, None] = None
11041105
try:
11051106
async with ceil_timeout(
11061107
timeout.sock_connect, ceil_threshold=timeout.ceil_threshold
@@ -1112,7 +1113,11 @@ async def _wrap_create_connection(
11121113
interleave=self._interleave,
11131114
loop=self._loop,
11141115
)
1115-
return await self._loop.create_connection(*args, **kwargs, sock=sock)
1116+
connection = await self._loop.create_connection(
1117+
*args, **kwargs, sock=sock
1118+
)
1119+
sock = None
1120+
return connection
11161121
except cert_errors as exc:
11171122
raise ClientConnectorCertificateError(req.connection_key, exc) from exc
11181123
except ssl_errors as exc:
@@ -1121,6 +1126,12 @@ async def _wrap_create_connection(
11211126
if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
11221127
raise
11231128
raise client_error(req.connection_key, exc) from exc
1129+
finally:
1130+
if sock is not None:
1131+
# Will be hit if an exception is thrown before the event loop takes the socket.
1132+
# In that case, proactively close the socket to guard against event loop leaks.
1133+
# For example, see https://github.com/MagicStack/uvloop/issues/653.
1134+
sock.close()
11241135

11251136
def _warn_about_tls_in_tls(
11261137
self,

tests/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ def start_connection() -> Iterator[mock.Mock]:
253253
"aiohttp.connector.aiohappyeyeballs.start_connection",
254254
autospec=True,
255255
spec_set=True,
256+
return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),
256257
) as start_connection_mock:
257258
yield start_connection_mock
258259

tests/test_connector.py

+23
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,29 @@ async def test_tcp_connector_certificate_error(
646646
await conn.close()
647647

648648

649+
async def test_tcp_connector_closes_socket_on_error(
650+
loop: asyncio.AbstractEventLoop, start_connection: mock.AsyncMock
651+
) -> None:
652+
req = ClientRequest("GET", URL("https://127.0.0.1:443"), loop=loop)
653+
654+
conn = aiohttp.TCPConnector()
655+
with (
656+
mock.patch.object(
657+
conn._loop,
658+
"create_connection",
659+
autospec=True,
660+
spec_set=True,
661+
side_effect=ValueError,
662+
),
663+
pytest.raises(ValueError),
664+
):
665+
await conn.connect(req, [], ClientTimeout())
666+
667+
assert start_connection.return_value.close.called
668+
669+
await conn.close()
670+
671+
649672
async def test_tcp_connector_server_hostname_default(
650673
loop: asyncio.AbstractEventLoop, start_connection: mock.AsyncMock
651674
) -> None:

tests/test_proxy.py

+1
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ async def make_conn() -> aiohttp.TCPConnector:
216216
"aiohttp.connector.aiohappyeyeballs.start_connection",
217217
autospec=True,
218218
spec_set=True,
219+
return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),
219220
)
220221
def test_proxy_connection_error(self, start_connection: mock.Mock) -> None: # type: ignore[misc]
221222
async def make_conn() -> aiohttp.TCPConnector:

0 commit comments

Comments
 (0)