]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-88863: Clear ref cycles to resolve leak when asyncio.open_connection raises (...
authorDong Uk, Kang <nailbrainz@gmail.com>
Tue, 22 Nov 2022 15:06:20 +0000 (00:06 +0900)
committerGitHub <noreply@github.com>
Tue, 22 Nov 2022 15:06:20 +0000 (07:06 -0800)
Break reference cycles to resolve memory leak, by
removing local exception and future instances from the frame

Lib/asyncio/base_events.py
Lib/asyncio/selector_events.py
Lib/asyncio/windows_events.py
Misc/NEWS.d/next/Library/2022-08-06-12-18-07.gh-issue-88863.NnqsuJ.rst [new file with mode: 0644]

index c8a2f9f25634ef2fc687d501daa48f481a6441c3..91d32e3939dcd36854e7c046cfe8a7caf73085de 100644 (file)
@@ -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
index bfa4590154f37210b105eceb1a7c95f8c38d459e..3d30006198f6716699f95572a362a7bca671ab0a 100644 (file)
@@ -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.
index acc97daafecc0b3b2aa4205118202091e7e3b86b..4dad436fb4187fe84f0dc21c4ef0c25438c56293 100644 (file)
@@ -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 (file)
index 0000000..23f8cb0
--- /dev/null
@@ -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.