aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDong Uk, Kang <nailbrainz@gmail.com>2022-11-24 03:37:24 +0900
committerGitHub <noreply@github.com>2022-11-23 10:37:24 -0800
commit24fad64cefec583a6678a59c558508aace077dba (patch)
tree29cd5a9d3608d4366e8c063fe6e4753a21067f5e
parentgh-99619: fix error in documentation of ExceptionGroup.derive() (GH-99621) (diff)
downloadcpython-24fad64cefec583a6678a59c558508aace077dba.tar.gz
cpython-24fad64cefec583a6678a59c558508aace077dba.tar.bz2
cpython-24fad64cefec583a6678a59c558508aace077dba.zip
[3.11] gh-88863: Clear ref cycles to resolve leak when asyncio.open_connection raises (GH-95739) (#99721)
Break reference cycles to resolve memory leak, by removing local exception and future instances from the frame. (cherry picked from commit 995f6170c78570eca818f7e7dbd8a7661c171a81) Co-authored-by: Dong Uk, Kang <nailbrainz@gmail.com>
-rw-r--r--Lib/asyncio/base_events.py27
-rw-r--r--Lib/asyncio/selector_events.py10
-rw-r--r--Lib/asyncio/windows_events.py8
-rw-r--r--Misc/NEWS.d/next/Library/2022-08-06-12-18-07.gh-issue-88863.NnqsuJ.rst3
4 files changed, 36 insertions, 12 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 9b8167d7a13..8f3e1b3df2a 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -975,6 +975,8 @@ class BaseEventLoop(events.AbstractEventLoop):
if sock is not None:
sock.close()
raise
+ finally:
+ exceptions = my_exceptions = None
async def create_connection(
self, protocol_factory, host=None, port=None,
@@ -1072,17 +1074,20 @@ class BaseEventLoop(events.AbstractEventLoop):
if sock is None:
exceptions = [exc for sub in exceptions for exc in sub]
- 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 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:
@@ -1875,6 +1880,8 @@ class BaseEventLoop(events.AbstractEventLoop):
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
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index c9bbe2ac014..8ab420d5bd7 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -630,7 +630,11 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
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()
@@ -652,6 +656,8 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
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():
@@ -675,6 +681,8 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
fut.set_exception(exc)
else:
fut.set_result(None)
+ finally:
+ fut = None
async def sock_accept(self, sock):
"""Accept a connection.
diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py
index 90b259cbafe..8ee7e1ebd94 100644
--- a/Lib/asyncio/windows_events.py
+++ b/Lib/asyncio/windows_events.py
@@ -439,7 +439,11 @@ class IocpProactor:
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()
@@ -841,6 +845,8 @@ class IocpProactor:
else:
f.set_result(value)
self._results.append(f)
+ finally:
+ f = None
# Remove unregistered futures
for ov in self._unregistered:
diff --git a/Misc/NEWS.d/next/Library/2022-08-06-12-18-07.gh-issue-88863.NnqsuJ.rst b/Misc/NEWS.d/next/Library/2022-08-06-12-18-07.gh-issue-88863.NnqsuJ.rst
new file mode 100644
index 00000000000..23f8cb01cf0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-08-06-12-18-07.gh-issue-88863.NnqsuJ.rst
@@ -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.