Skip to content

Commit 374920a

Browse files
authored
gh-153133: Fix socket leak in error path of asyncio create_connection (#153134)
1 parent a74280e commit 374920a

3 files changed

Lines changed: 63 additions & 15 deletions

File tree

Lib/asyncio/base_events.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,21 +1219,26 @@ async def _create_connection_transport(
12191219
ssl_handshake_timeout=None,
12201220
ssl_shutdown_timeout=None, context=None):
12211221

1222-
sock.setblocking(False)
1223-
context = context if context is not None else contextvars.copy_context()
1224-
1225-
protocol = protocol_factory()
1226-
waiter = self.create_future()
1227-
if ssl:
1228-
sslcontext = None if isinstance(ssl, bool) else ssl
1229-
transport = self._make_ssl_transport(
1230-
sock, protocol, sslcontext, waiter,
1231-
server_side=server_side, server_hostname=server_hostname,
1232-
ssl_handshake_timeout=ssl_handshake_timeout,
1233-
ssl_shutdown_timeout=ssl_shutdown_timeout,
1234-
context=context)
1235-
else:
1236-
transport = self._make_socket_transport(sock, protocol, waiter, context=context)
1222+
try:
1223+
sock.setblocking(False)
1224+
context = context if context is not None else contextvars.copy_context()
1225+
1226+
protocol = protocol_factory()
1227+
waiter = self.create_future()
1228+
if ssl:
1229+
sslcontext = None if isinstance(ssl, bool) else ssl
1230+
transport = self._make_ssl_transport(
1231+
sock, protocol, sslcontext, waiter,
1232+
server_side=server_side, server_hostname=server_hostname,
1233+
ssl_handshake_timeout=ssl_handshake_timeout,
1234+
ssl_shutdown_timeout=ssl_shutdown_timeout,
1235+
context=context)
1236+
else:
1237+
transport = self._make_socket_transport(sock, protocol, waiter, context=context)
1238+
except:
1239+
# gh-153133: close the socket if the transport is never created.
1240+
sock.close()
1241+
raise
12371242

12381243
try:
12391244
await waiter

Lib/test/test_asyncio/test_base_events.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,47 @@ def getaddrinfo(*args, **kw):
12821282
self.loop.run_until_complete(coro)
12831283
self.assertTrue(sock.close.called)
12841284

1285+
def test_create_connection_sock_transport_error_closes_sock(self):
1286+
# gh-153133: a user-provided socket is closed if the transport is
1287+
# never created.
1288+
sock = mock.Mock()
1289+
sock.type = socket.SOCK_STREAM
1290+
1291+
def factory():
1292+
raise ZeroDivisionError
1293+
1294+
coro = self.loop.create_connection(factory, sock=sock)
1295+
with self.assertRaises(ZeroDivisionError):
1296+
self.loop.run_until_complete(coro)
1297+
self.assertTrue(sock.close.called)
1298+
1299+
@patch_socket
1300+
def test_create_connection_transport_error_closes_sock(self, m_socket):
1301+
# gh-153133: an internally created socket is closed if the transport
1302+
# is never created.
1303+
sock = mock.Mock()
1304+
m_socket.socket.return_value = sock
1305+
1306+
def getaddrinfo(*args, **kw):
1307+
fut = self.loop.create_future()
1308+
addr = (socket.AF_INET, socket.SOCK_STREAM, 0, '',
1309+
('127.0.0.1', 80))
1310+
fut.set_result([addr])
1311+
return fut
1312+
self.loop.getaddrinfo = getaddrinfo
1313+
1314+
async def sock_connect(sock, address):
1315+
return None
1316+
1317+
def factory():
1318+
raise ZeroDivisionError
1319+
1320+
with mock.patch.object(self.loop, 'sock_connect', sock_connect):
1321+
coro = self.loop.create_connection(factory, '127.0.0.1', 80)
1322+
with self.assertRaises(ZeroDivisionError):
1323+
self.loop.run_until_complete(coro)
1324+
self.assertTrue(sock.close.called)
1325+
12851326
@patch_socket
12861327
def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
12871328
# See gh-135836: Fix IndexError when Happy Eyeballs algorithm
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a socket leak in :meth:`asyncio.loop.create_connection` when the
2+
transport cannot be created.

0 commit comments

Comments
 (0)