Skip to content

Commit 24fad64

Browse files
authored
[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 995f617) Co-authored-by: Dong Uk, Kang <[email protected]>
1 parent 609273e commit 24fad64

File tree

4 files changed

+36
-12
lines changed

4 files changed

+36
-12
lines changed

Lib/asyncio/base_events.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,8 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None):
975975
if sock is not None:
976976
sock.close()
977977
raise
978+
finally:
979+
exceptions = my_exceptions = None
978980

979981
async def create_connection(
980982
self, protocol_factory, host=None, port=None,
@@ -1072,17 +1074,20 @@ async def create_connection(
10721074

10731075
if sock is None:
10741076
exceptions = [exc for sub in exceptions for exc in sub]
1075-
if len(exceptions) == 1:
1076-
raise exceptions[0]
1077-
else:
1078-
# If they all have the same str(), raise one.
1079-
model = str(exceptions[0])
1080-
if all(str(exc) == model for exc in exceptions):
1077+
try:
1078+
if len(exceptions) == 1:
10811079
raise exceptions[0]
1082-
# Raise a combined exception so the user can see all
1083-
# the various error messages.
1084-
raise OSError('Multiple exceptions: {}'.format(
1085-
', '.join(str(exc) for exc in exceptions)))
1080+
else:
1081+
# If they all have the same str(), raise one.
1082+
model = str(exceptions[0])
1083+
if all(str(exc) == model for exc in exceptions):
1084+
raise exceptions[0]
1085+
# Raise a combined exception so the user can see all
1086+
# the various error messages.
1087+
raise OSError('Multiple exceptions: {}'.format(
1088+
', '.join(str(exc) for exc in exceptions)))
1089+
finally:
1090+
exceptions = None
10861091

10871092
else:
10881093
if sock is None:
@@ -1875,6 +1880,8 @@ def _run_once(self):
18751880

18761881
event_list = self._selector.select(timeout)
18771882
self._process_events(event_list)
1883+
# Needed to break cycles when an exception occurs.
1884+
event_list = None
18781885

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

Lib/asyncio/selector_events.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,11 @@ async def sock_connect(self, sock, address):
630630

631631
fut = self.create_future()
632632
self._sock_connect(fut, sock, address)
633-
return await fut
633+
try:
634+
return await fut
635+
finally:
636+
# Needed to break cycles when an exception occurs.
637+
fut = None
634638

635639
def _sock_connect(self, fut, sock, address):
636640
fd = sock.fileno()
@@ -652,6 +656,8 @@ def _sock_connect(self, fut, sock, address):
652656
fut.set_exception(exc)
653657
else:
654658
fut.set_result(None)
659+
finally:
660+
fut = None
655661

656662
def _sock_write_done(self, fd, fut, handle=None):
657663
if handle is None or not handle.cancelled():
@@ -675,6 +681,8 @@ def _sock_connect_cb(self, fut, sock, address):
675681
fut.set_exception(exc)
676682
else:
677683
fut.set_result(None)
684+
finally:
685+
fut = None
678686

679687
async def sock_accept(self, sock):
680688
"""Accept a connection.

Lib/asyncio/windows_events.py

Lines changed: 7 additions & 1 deletion
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()
@@ -841,6 +845,8 @@ def _poll(self, timeout=None):
841845
else:
842846
f.set_result(value)
843847
self._results.append(f)
848+
finally:
849+
f = None
844850

845851
# Remove unregistered futures
846852
for ov in self._unregistered:
Lines changed: 3 additions & 0 deletions
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)