Skip to content

Commit 995f617

Browse files
authored
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
1 parent 9a91182 commit 995f617

File tree

4 files changed

+38
-14
lines changed

4 files changed

+38
-14
lines changed

Lib/asyncio/base_events.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,8 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None):
986986
if sock is not None:
987987
sock.close()
988988
raise
989+
finally:
990+
exceptions = my_exceptions = None
989991

990992
async def create_connection(
991993
self, protocol_factory, host=None, port=None,
@@ -1084,19 +1086,22 @@ async def create_connection(
10841086

10851087
if sock is None:
10861088
exceptions = [exc for sub in exceptions for exc in sub]
1087-
if all_errors:
1088-
raise ExceptionGroup("create_connection failed", exceptions)
1089-
if len(exceptions) == 1:
1090-
raise exceptions[0]
1091-
else:
1092-
# If they all have the same str(), raise one.
1093-
model = str(exceptions[0])
1094-
if all(str(exc) == model for exc in exceptions):
1089+
try:
1090+
if all_errors:
1091+
raise ExceptionGroup("create_connection failed", exceptions)
1092+
if len(exceptions) == 1:
10951093
raise exceptions[0]
1096-
# Raise a combined exception so the user can see all
1097-
# the various error messages.
1098-
raise OSError('Multiple exceptions: {}'.format(
1099-
', '.join(str(exc) for exc in exceptions)))
1094+
else:
1095+
# If they all have the same str(), raise one.
1096+
model = str(exceptions[0])
1097+
if all(str(exc) == model for exc in exceptions):
1098+
raise exceptions[0]
1099+
# Raise a combined exception so the user can see all
1100+
# the various error messages.
1101+
raise OSError('Multiple exceptions: {}'.format(
1102+
', '.join(str(exc) for exc in exceptions)))
1103+
finally:
1104+
exceptions = None
11001105

11011106
else:
11021107
if sock is None:
@@ -1904,6 +1909,8 @@ def _run_once(self):
19041909

19051910
event_list = self._selector.select(timeout)
19061911
self._process_events(event_list)
1912+
# Needed to break cycles when an exception occurs.
1913+
event_list = None
19071914

19081915
# Handle 'later' callbacks that are ready.
19091916
end_time = self.time() + self._clock_resolution

Lib/asyncio/selector_events.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,11 @@ async def sock_connect(self, sock, address):
633633

634634
fut = self.create_future()
635635
self._sock_connect(fut, sock, address)
636-
return await fut
636+
try:
637+
return await fut
638+
finally:
639+
# Needed to break cycles when an exception occurs.
640+
fut = None
637641

638642
def _sock_connect(self, fut, sock, address):
639643
fd = sock.fileno()
@@ -655,6 +659,8 @@ def _sock_connect(self, fut, sock, address):
655659
fut.set_exception(exc)
656660
else:
657661
fut.set_result(None)
662+
finally:
663+
fut = None
658664

659665
def _sock_write_done(self, fd, fut, handle=None):
660666
if handle is None or not handle.cancelled():
@@ -678,6 +684,8 @@ def _sock_connect_cb(self, fut, sock, address):
678684
fut.set_exception(exc)
679685
else:
680686
fut.set_result(None)
687+
finally:
688+
fut = None
681689

682690
async def sock_accept(self, sock):
683691
"""Accept a connection.

Lib/asyncio/windows_events.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,11 @@ def select(self, timeout=None):
439439
self._poll(timeout)
440440
tmp = self._results
441441
self._results = []
442-
return tmp
442+
try:
443+
return tmp
444+
finally:
445+
# Needed to break cycles when an exception occurs.
446+
tmp = None
443447

444448
def _result(self, value):
445449
fut = self._loop.create_future()
@@ -793,6 +797,8 @@ def _poll(self, timeout=None):
793797
else:
794798
f.set_result(value)
795799
self._results.append(f)
800+
finally:
801+
f = None
796802

797803
# Remove unregistered futures
798804
for ov in self._unregistered:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
To avoid apparent memory leaks when :func:`asyncio.open_connection` raises,
2+
break reference cycles generated by local exception and future instances
3+
(which has exception instance as its member var). Patch by Dong Uk, Kang.

0 commit comments

Comments
 (0)