diff options
author | Dong Uk, Kang <nailbrainz@gmail.com> | 2022-11-24 03:37:24 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-23 10:37:24 -0800 |
commit | 24fad64cefec583a6678a59c558508aace077dba (patch) | |
tree | 29cd5a9d3608d4366e8c063fe6e4753a21067f5e | |
parent | gh-99619: fix error in documentation of ExceptionGroup.derive() (GH-99621) (diff) | |
download | cpython-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.py | 27 | ||||
-rw-r--r-- | Lib/asyncio/selector_events.py | 10 | ||||
-rw-r--r-- | Lib/asyncio/windows_events.py | 8 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2022-08-06-12-18-07.gh-issue-88863.NnqsuJ.rst | 3 |
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. |