Skip to content

Commit b4d0b26

Browse files
[3.15] gh-153133: Fix socket leak in error path of asyncio create_connection (GH-153134) (#154631)
gh-153133: Fix socket leak in error path of asyncio create_connection (GH-153134) (cherry picked from commit 374920a) Co-authored-by: Timofei <128279579+deadlovelll@users.noreply.github.com>
1 parent f188bc3 commit b4d0b26

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
@@ -1218,21 +1218,26 @@ async def _create_connection_transport(
12181218
ssl_handshake_timeout=None,
12191219
ssl_shutdown_timeout=None, context=None):
12201220

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

12371242
try:
12381243
await waiter

Lib/test/test_asyncio/test_base_events.py

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

1290+
def test_create_connection_sock_transport_error_closes_sock(self):
1291+
# gh-153133: a user-provided socket is closed if the transport is
1292+
# never created.
1293+
sock = mock.Mock()
1294+
sock.type = socket.SOCK_STREAM
1295+
1296+
def factory():
1297+
raise ZeroDivisionError
1298+
1299+
coro = self.loop.create_connection(factory, sock=sock)
1300+
with self.assertRaises(ZeroDivisionError):
1301+
self.loop.run_until_complete(coro)
1302+
self.assertTrue(sock.close.called)
1303+
1304+
@patch_socket
1305+
def test_create_connection_transport_error_closes_sock(self, m_socket):
1306+
# gh-153133: an internally created socket is closed if the transport
1307+
# is never created.
1308+
sock = mock.Mock()
1309+
m_socket.socket.return_value = sock
1310+
1311+
def getaddrinfo(*args, **kw):
1312+
fut = self.loop.create_future()
1313+
addr = (socket.AF_INET, socket.SOCK_STREAM, 0, '',
1314+
('127.0.0.1', 80))
1315+
fut.set_result([addr])
1316+
return fut
1317+
self.loop.getaddrinfo = getaddrinfo
1318+
1319+
async def sock_connect(sock, address):
1320+
return None
1321+
1322+
def factory():
1323+
raise ZeroDivisionError
1324+
1325+
with mock.patch.object(self.loop, 'sock_connect', sock_connect):
1326+
coro = self.loop.create_connection(factory, '127.0.0.1', 80)
1327+
with self.assertRaises(ZeroDivisionError):
1328+
self.loop.run_until_complete(coro)
1329+
self.assertTrue(sock.close.called)
1330+
12901331
@patch_socket
12911332
def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
12921333
# 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)