Skip to content

Commit 162bd7e

Browse files
authored
[3.14] gh-153133: Fix socket leak in error path of asyncio create_connection (GH-153134) (#154897)
1 parent c21e7c4 commit 162bd7e

3 files changed

Lines changed: 60 additions & 12 deletions

File tree

Lib/asyncio/base_events.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,19 +1214,24 @@ async def _create_connection_transport(
12141214
ssl_handshake_timeout=None,
12151215
ssl_shutdown_timeout=None):
12161216

1217-
sock.setblocking(False)
1217+
try:
1218+
sock.setblocking(False)
12181219

1219-
protocol = protocol_factory()
1220-
waiter = self.create_future()
1221-
if ssl:
1222-
sslcontext = None if isinstance(ssl, bool) else ssl
1223-
transport = self._make_ssl_transport(
1224-
sock, protocol, sslcontext, waiter,
1225-
server_side=server_side, server_hostname=server_hostname,
1226-
ssl_handshake_timeout=ssl_handshake_timeout,
1227-
ssl_shutdown_timeout=ssl_shutdown_timeout)
1228-
else:
1229-
transport = self._make_socket_transport(sock, protocol, waiter)
1220+
protocol = protocol_factory()
1221+
waiter = self.create_future()
1222+
if ssl:
1223+
sslcontext = None if isinstance(ssl, bool) else ssl
1224+
transport = self._make_ssl_transport(
1225+
sock, protocol, sslcontext, waiter,
1226+
server_side=server_side, server_hostname=server_hostname,
1227+
ssl_handshake_timeout=ssl_handshake_timeout,
1228+
ssl_shutdown_timeout=ssl_shutdown_timeout)
1229+
else:
1230+
transport = self._make_socket_transport(sock, protocol, waiter)
1231+
except:
1232+
# gh-153133: close the socket if the transport is never created.
1233+
sock.close()
1234+
raise
12301235

12311236
try:
12321237
await waiter

Lib/test/test_asyncio/test_base_events.py

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

1197+
def test_create_connection_sock_transport_error_closes_sock(self):
1198+
# gh-153133: a user-provided socket is closed if the transport is
1199+
# never created.
1200+
sock = mock.Mock()
1201+
sock.type = socket.SOCK_STREAM
1202+
1203+
def factory():
1204+
raise ZeroDivisionError
1205+
1206+
coro = self.loop.create_connection(factory, sock=sock)
1207+
with self.assertRaises(ZeroDivisionError):
1208+
self.loop.run_until_complete(coro)
1209+
self.assertTrue(sock.close.called)
1210+
1211+
@patch_socket
1212+
def test_create_connection_transport_error_closes_sock(self, m_socket):
1213+
# gh-153133: an internally created socket is closed if the transport
1214+
# is never created.
1215+
sock = mock.Mock()
1216+
m_socket.socket.return_value = sock
1217+
1218+
def getaddrinfo(*args, **kw):
1219+
fut = self.loop.create_future()
1220+
addr = (socket.AF_INET, socket.SOCK_STREAM, 0, '',
1221+
('127.0.0.1', 80))
1222+
fut.set_result([addr])
1223+
return fut
1224+
self.loop.getaddrinfo = getaddrinfo
1225+
1226+
async def sock_connect(sock, address):
1227+
return None
1228+
1229+
def factory():
1230+
raise ZeroDivisionError
1231+
1232+
with mock.patch.object(self.loop, 'sock_connect', sock_connect):
1233+
coro = self.loop.create_connection(factory, '127.0.0.1', 80)
1234+
with self.assertRaises(ZeroDivisionError):
1235+
self.loop.run_until_complete(coro)
1236+
self.assertTrue(sock.close.called)
1237+
11971238
@patch_socket
11981239
def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
11991240
# 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)