diff options
author | Dong Uk, Kang <nailbrainz@gmail.com> | 2022-11-22 15:06:20 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-22 15:06:20 (GMT) |
commit | 995f6170c78570eca818f7e7dbd8a7661c171a81 (patch) | |
tree | fdfaf448f9fb3bf4a601c1cb51c001deab62ea01 | |
parent | 9a91182d4a87e4511dad20ad101e3eab0e1c5088 (diff) | |
download | cpython-995f6170c78570eca818f7e7dbd8a7661c171a81.zip cpython-995f6170c78570eca818f7e7dbd8a7661c171a81.tar.gz cpython-995f6170c78570eca818f7e7dbd8a7661c171a81.tar.bz2 |
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
-rw-r--r-- | Lib/asyncio/base_events.py | 31 | ||||
-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, 38 insertions, 14 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index c8a2f9f..91d32e3 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -986,6 +986,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, @@ -1084,19 +1086,22 @@ class BaseEventLoop(events.AbstractEventLoop): 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 @@ 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 bfa4590..3d30006 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -633,7 +633,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() @@ -655,6 +659,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(): @@ -678,6 +684,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 acc97da..4dad436 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() @@ -793,6 +797,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 0000000..23f8cb0 --- /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. |