gh-88863: Clear ref cycles to resolve leak when asyncio.open_connection raises (#95739)

Break reference cycles to resolve memory leak, by
removing local exception and future instances from the frame
This commit is contained in:
Dong Uk, Kang 2022-11-23 00:06:20 +09:00 committed by GitHub
parent 9a91182d4a
commit 995f6170c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 14 deletions

View File

@ -986,6 +986,8 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None):
if sock is not None:
sock.close()
raise
finally:
exceptions = my_exceptions = None
async def create_connection(
self, protocol_factory, host=None, port=None,
@ -1084,19 +1086,22 @@ async def create_connection(
if sock is None:
exceptions = [exc for sub in exceptions for exc in sub]
if all_errors:
raise ExceptionGroup("create_connection failed", exceptions)
if len(exceptions) == 1:
raise exceptions[0]
else:
# If they all have the same str(), raise one.
model = str(exceptions[0])
if all(str(exc) == model for exc in exceptions):
try:
if all_errors:
raise ExceptionGroup("create_connection failed", exceptions)
if len(exceptions) == 1:
raise exceptions[0]
# Raise a combined exception so the user can see all
# the various error messages.
raise OSError('Multiple exceptions: {}'.format(
', '.join(str(exc) for exc in exceptions)))
else:
# If they all have the same str(), raise one.
model = str(exceptions[0])
if all(str(exc) == model for exc in exceptions):
raise exceptions[0]
# Raise a combined exception so the user can see all
# the various error messages.
raise OSError('Multiple exceptions: {}'.format(
', '.join(str(exc) for exc in exceptions)))
finally:
exceptions = None
else:
if sock is None:
@ -1904,6 +1909,8 @@ def _run_once(self):
event_list = self._selector.select(timeout)
self._process_events(event_list)
# Needed to break cycles when an exception occurs.
event_list = None
# Handle 'later' callbacks that are ready.
end_time = self.time() + self._clock_resolution

View File

@ -633,7 +633,11 @@ async def sock_connect(self, sock, address):
fut = self.create_future()
self._sock_connect(fut, sock, address)
return await fut
try:
return await fut
finally:
# Needed to break cycles when an exception occurs.
fut = None
def _sock_connect(self, fut, sock, address):
fd = sock.fileno()
@ -655,6 +659,8 @@ def _sock_connect(self, fut, sock, address):
fut.set_exception(exc)
else:
fut.set_result(None)
finally:
fut = None
def _sock_write_done(self, fd, fut, handle=None):
if handle is None or not handle.cancelled():
@ -678,6 +684,8 @@ def _sock_connect_cb(self, fut, sock, address):
fut.set_exception(exc)
else:
fut.set_result(None)
finally:
fut = None
async def sock_accept(self, sock):
"""Accept a connection.

View File

@ -439,7 +439,11 @@ def select(self, timeout=None):
self._poll(timeout)
tmp = self._results
self._results = []
return tmp
try:
return tmp
finally:
# Needed to break cycles when an exception occurs.
tmp = None
def _result(self, value):
fut = self._loop.create_future()
@ -793,6 +797,8 @@ def _poll(self, timeout=None):
else:
f.set_result(value)
self._results.append(f)
finally:
f = None
# Remove unregistered futures
for ov in self._unregistered:

View File

@ -0,0 +1,3 @@
To avoid apparent memory leaks when :func:`asyncio.open_connection` raises,
break reference cycles generated by local exception and future instances
(which has exception instance as its member var). Patch by Dong Uk, Kang.