Skip to content

Commit

Permalink
pythongh-124858: fix happy eyeballs refcyles (python#124859)
Browse files Browse the repository at this point in the history
  • Loading branch information
graingert authored Oct 2, 2024
1 parent 6810928 commit c066bf5
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 6 deletions.
18 changes: 12 additions & 6 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import collections.abc
import concurrent.futures
import errno
import functools
import heapq
import itertools
import os
Expand Down Expand Up @@ -1140,11 +1139,18 @@ async def create_connection(
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]
Expand Down
1 change: 1 addition & 0 deletions Lib/asyncio/staggered.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ async def run_one_coro(previous_failed) -> 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()
18 changes: 18 additions & 0 deletions Lib/test/test_asyncio/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,24 @@ async def handle_echo(reader, writer):
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()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` when used with ``happy_eyeballs_delay``

0 comments on commit c066bf5

Please sign in to comment.