From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:35:56 +0000 (+0200) Subject: [3.13] gh-124858: fix happy eyeballs refcyles (GH-124859) (#124912) X-Git-Tag: v3.13.1~244 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e5967403993f4d491950aac01525c9d9a9b8eb1a;p=thirdparty%2FPython%2Fcpython.git [3.13] gh-124858: fix happy eyeballs refcyles (GH-124859) (#124912) gh-124858: fix happy eyeballs refcyles (GH-124859) (cherry picked from commit c066bf553577d1000e208eb078d9e758c3e41186) Co-authored-by: Thomas Grainger --- diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index e4a39f4d345c..914340426852 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -17,7 +17,6 @@ import collections import collections.abc import concurrent.futures import errno -import functools import heapq import itertools import os @@ -1140,11 +1139,18 @@ class BaseEventLoop(events.AbstractEventLoop): except OSError: continue else: # using happy eyeballs - sock, _, _ = await staggered.staggered_race( - (functools.partial(self._connect_sock, - exceptions, addrinfo, laddr_infos) - for addrinfo in infos), - happy_eyeballs_delay, loop=self) + sock = (await staggered.staggered_race( + ( + # can't use functools.partial as it keeps a reference + # to exceptions + lambda addrinfo=addrinfo: self._connect_sock( + exceptions, addrinfo, laddr_infos + ) + for addrinfo in infos + ), + happy_eyeballs_delay, + loop=self, + ))[0] # can't use sock, _, _ as it keeks a reference to exceptions if sock is None: exceptions = [exc for sub in exceptions for exc in sub] diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py index 7aafcea4d885..0f4df8855a80 100644 --- a/Lib/asyncio/staggered.py +++ b/Lib/asyncio/staggered.py @@ -144,6 +144,7 @@ async def staggered_race(coro_fns, delay, *, loop=None): raise d.exception() return winner_result, winner_index, exceptions finally: + del exceptions # Make sure no tasks are left running if we leave this function for t in running_tasks: t.cancel() diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index ae943f398698..3040ca55fae0 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1246,6 +1246,24 @@ os.close(fd) messages = self._basetest_unhandled_exceptions(handle_echo) self.assertEqual(messages, []) + def test_open_connection_happy_eyeball_refcycles(self): + port = socket_helper.find_unused_port() + async def main(): + exc = None + try: + await asyncio.open_connection( + host="localhost", + port=port, + happy_eyeballs_delay=0.25, + ) + except* OSError as excs: + # can't use assertRaises because that clears frames + exc = excs.exceptions[0] + self.assertIsNotNone(exc) + self.assertListEqual(gc.get_referrers(exc), []) + + asyncio.run(main()) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst b/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst new file mode 100644 index 000000000000..c05d24a7c5aa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst @@ -0,0 +1 @@ +Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` when used with ``happy_eyeballs_delay``