Skip to content

Commit 9996621

Browse files
top-oaipatchback[bot]
authored andcommitted
Explicitly close the socket if there is a failure in start_connection() (#10464)
(cherry picked from commit 182198f)
1 parent 9f933bf commit 9996621

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
@@ -42,6 +42,7 @@ Andrej Antonov
4242
Andrew Leech
4343
Andrew Lytvyn
4444
Andrew Svetlov
45+
Andrew Top
4546
Andrew Zhou
4647
Andrii Soldatenko
4748
Anes Abismail

aiohttp/connector.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,7 @@ async def _wrap_create_connection(
11081108
client_error: Type[Exception] = ClientConnectorError,
11091109
**kwargs: Any,
11101110
) -> Tuple[asyncio.Transport, ResponseHandler]:
1111+
sock: Union[socket.socket, None] = None
11111112
try:
11121113
async with ceil_timeout(
11131114
timeout.sock_connect, ceil_threshold=timeout.ceil_threshold
@@ -1119,7 +1120,11 @@ async def _wrap_create_connection(
11191120
interleave=self._interleave,
11201121
loop=self._loop,
11211122
)
1122-
return await self._loop.create_connection(*args, **kwargs, sock=sock)
1123+
connection = await self._loop.create_connection(
1124+
*args, **kwargs, sock=sock
1125+
)
1126+
sock = None
1127+
return connection
11231128
except cert_errors as exc:
11241129
raise ClientConnectorCertificateError(req.connection_key, exc) from exc
11251130
except ssl_errors as exc:
@@ -1128,6 +1133,12 @@ async def _wrap_create_connection(
11281133
if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
11291134
raise
11301135
raise client_error(req.connection_key, exc) from exc
1136+
finally:
1137+
if sock is not None:
1138+
# Will be hit if an exception is thrown before the event loop takes the socket.
1139+
# In that case, proactively close the socket to guard against event loop leaks.
1140+
# For example, see https://github.com/MagicStack/uvloop/issues/653.
1141+
sock.close()
11311142

11321143
async def _wrap_existing_connection(
11331144
self,

tests/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ def start_connection():
221221
"aiohttp.connector.aiohappyeyeballs.start_connection",
222222
autospec=True,
223223
spec_set=True,
224+
return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),
224225
) as start_connection_mock:
225226
yield start_connection_mock
226227

tests/test_connector.py

+23
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,29 @@ async def certificate_error(*args, **kwargs):
617617
await conn.close()
618618

619619

620+
async def test_tcp_connector_closes_socket_on_error(
621+
loop: asyncio.AbstractEventLoop, start_connection: mock.AsyncMock
622+
) -> None:
623+
req = ClientRequest("GET", URL("https://127.0.0.1:443"), loop=loop)
624+
625+
conn = aiohttp.TCPConnector()
626+
with (
627+
mock.patch.object(
628+
conn._loop,
629+
"create_connection",
630+
autospec=True,
631+
spec_set=True,
632+
side_effect=ValueError,
633+
),
634+
pytest.raises(ValueError),
635+
):
636+
await conn.connect(req, [], ClientTimeout())
637+
638+
assert start_connection.return_value.close.called
639+
640+
await conn.close()
641+
642+
620643
async def test_tcp_connector_server_hostname_default(
621644
loop: Any, start_connection: mock.AsyncMock
622645
) -> None:

tests/test_proxy.py

+1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ async def make_conn():
207207
"aiohttp.connector.aiohappyeyeballs.start_connection",
208208
autospec=True,
209209
spec_set=True,
210+
return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),
210211
)
211212
def test_proxy_connection_error(self, start_connection: Any) -> None:
212213
async def make_conn():

0 commit comments

Comments
 (0)