gh-124858: fix happy eyeballs refcyles (#124859)

This commit is contained in:
Thomas Grainger 2024-10-03 00:32:31 +01:00 committed by GitHub
parent 6810928927
commit c066bf5535
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 32 additions and 6 deletions

View File

@ -17,7 +17,6 @@ import collections
import collections.abc import collections.abc
import concurrent.futures import concurrent.futures
import errno import errno
import functools
import heapq import heapq
import itertools import itertools
import os import os
@ -1140,11 +1139,18 @@ class BaseEventLoop(events.AbstractEventLoop):
except OSError: except OSError:
continue continue
else: # using happy eyeballs else: # using happy eyeballs
sock, _, _ = await staggered.staggered_race( sock = (await staggered.staggered_race(
(functools.partial(self._connect_sock, (
exceptions, addrinfo, laddr_infos) # can't use functools.partial as it keeps a reference
for addrinfo in infos), # to exceptions
happy_eyeballs_delay, loop=self) 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: if sock is None:
exceptions = [exc for sub in exceptions for exc in sub] exceptions = [exc for sub in exceptions for exc in sub]

View File

@ -133,6 +133,7 @@ async def staggered_race(coro_fns, delay, *, loop=None):
raise d.exception() raise d.exception()
return winner_result, winner_index, exceptions return winner_result, winner_index, exceptions
finally: finally:
del exceptions
# Make sure no tasks are left running if we leave this function # Make sure no tasks are left running if we leave this function
for t in running_tasks: for t in running_tasks:
t.cancel() t.cancel()

View File

@ -1200,6 +1200,24 @@ class StreamTests(test_utils.TestCase):
messages = self._basetest_unhandled_exceptions(handle_echo) messages = self._basetest_unhandled_exceptions(handle_echo)
self.assertEqual(messages, []) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -0,0 +1 @@
Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` when used with ``happy_eyeballs_delay``