Skip to content

Commit b191bc3

Browse files
authored
[3.10] gh-88863: Clear ref cycles to resolve leak when asyncio.open_connection raises (GH-95739) (#99722)
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 40a4b40 commit b191bc3

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
@@ -971,6 +971,8 @@ async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None):
971971
if sock is not None:
972972
sock.close()
973973
raise
974+
finally:
975+
exceptions = my_exceptions = None
974976

975977
async def create_connection(
976978
self, protocol_factory, host=None, port=None,
@@ -1063,17 +1065,20 @@ async def create_connection(
10631065

10641066
if sock is None:
10651067
exceptions = [exc for sub in exceptions for exc in sub]
1066-
if len(exceptions) == 1:
1067-
raise exceptions[0]
1068-
else:
1069-
# If they all have the same str(), raise one.
1070-
model = str(exceptions[0])
1071-
if all(str(exc) == model for exc in exceptions):
1068+
try:
1069+
if len(exceptions) == 1:
10721070
raise exceptions[0]
1073-
# Raise a combined exception so the user can see all
1074-
# the various error messages.
1075-
raise OSError('Multiple exceptions: {}'.format(
1076-
', '.join(str(exc) for exc in exceptions)))
1071+
else:
1072+
# If they all have the same str(), raise one.
1073+
model = str(exceptions[0])
1074+
if all(str(exc) == model for exc in exceptions):
1075+
raise exceptions[0]
1076+
# Raise a combined exception so the user can see all
1077+
# the various error messages.
1078+
raise OSError('Multiple exceptions: {}'.format(
1079+
', '.join(str(exc) for exc in exceptions)))
1080+
finally:
1081+
exceptions = None
10771082

10781083
else:
10791084
if sock is None:
@@ -1862,6 +1867,8 @@ def _run_once(self):
18621867

18631868
event_list = self._selector.select(timeout)
18641869
self._process_events(event_list)
1870+
# Needed to break cycles when an exception occurs.
1871+
event_list = None
18651872

18661873
# Handle 'later' callbacks that are ready.
18671874
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
@@ -497,7 +497,11 @@ async def sock_connect(self, sock, address):
497497

498498
fut = self.create_future()
499499
self._sock_connect(fut, sock, address)
500-
return await fut
500+
try:
501+
return await fut
502+
finally:
503+
# Needed to break cycles when an exception occurs.
504+
fut = None
501505

502506
def _sock_connect(self, fut, sock, address):
503507
fd = sock.fileno()
@@ -519,6 +523,8 @@ def _sock_connect(self, fut, sock, address):
519523
fut.set_exception(exc)
520524
else:
521525
fut.set_result(None)
526+
finally:
527+
fut = None
522528

523529
def _sock_write_done(self, fd, fut, handle=None):
524530
if handle is None or not handle.cancelled():
@@ -542,6 +548,8 @@ def _sock_connect_cb(self, fut, sock, address):
542548
fut.set_exception(exc)
543549
else:
544550
fut.set_result(None)
551+
finally:
552+
fut = None
545553

546554
async def sock_accept(self, sock):
547555
"""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()
@@ -821,6 +825,8 @@ def _poll(self, timeout=None):
821825
else:
822826
f.set_result(value)
823827
self._results.append(f)
828+
finally:
829+
f = None
824830

825831
# Remove unregistered futures
826832
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)