Skip to content

Commit 440ed18

Browse files
[3.12] gh-124858: fix happy eyeballs refcyles (GH-124859) (#124913)
gh-124858: fix happy eyeballs refcyles (GH-124859) (cherry picked from commit c066bf5) Co-authored-by: Thomas Grainger <[email protected]>
1 parent da3d81d commit 440ed18

File tree

4 files changed

+32
-6
lines changed

4 files changed

+32
-6
lines changed

Lib/asyncio/base_events.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import collections.abc
1818
import concurrent.futures
1919
import errno
20-
import functools
2120
import heapq
2221
import itertools
2322
import os
@@ -1106,11 +1105,18 @@ async def create_connection(
11061105
except OSError:
11071106
continue
11081107
else: # using happy eyeballs
1109-
sock, _, _ = await staggered.staggered_race(
1110-
(functools.partial(self._connect_sock,
1111-
exceptions, addrinfo, laddr_infos)
1112-
for addrinfo in infos),
1113-
happy_eyeballs_delay, loop=self)
1108+
sock = (await staggered.staggered_race(
1109+
(
1110+
# can't use functools.partial as it keeps a reference
1111+
# to exceptions
1112+
lambda addrinfo=addrinfo: self._connect_sock(
1113+
exceptions, addrinfo, laddr_infos
1114+
)
1115+
for addrinfo in infos
1116+
),
1117+
happy_eyeballs_delay,
1118+
loop=self,
1119+
))[0] # can't use sock, _, _ as it keeks a reference to exceptions
11141120

11151121
if sock is None:
11161122
exceptions = [exc for sub in exceptions for exc in sub]

Lib/asyncio/staggered.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ async def run_one_coro(ok_to_start, previous_failed) -> None:
144144
raise d.exception()
145145
return winner_result, winner_index, exceptions
146146
finally:
147+
del exceptions
147148
# Make sure no tasks are left running if we leave this function
148149
for t in running_tasks:
149150
t.cancel()

Lib/test/test_asyncio/test_streams.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,24 @@ async def handle_echo(reader, writer):
11661166
messages = self._basetest_unhandled_exceptions(handle_echo)
11671167
self.assertEqual(messages, [])
11681168

1169+
def test_open_connection_happy_eyeball_refcycles(self):
1170+
port = socket_helper.find_unused_port()
1171+
async def main():
1172+
exc = None
1173+
try:
1174+
await asyncio.open_connection(
1175+
host="localhost",
1176+
port=port,
1177+
happy_eyeballs_delay=0.25,
1178+
)
1179+
except* OSError as excs:
1180+
# can't use assertRaises because that clears frames
1181+
exc = excs.exceptions[0]
1182+
self.assertIsNotNone(exc)
1183+
self.assertListEqual(gc.get_referrers(exc), [])
1184+
1185+
asyncio.run(main())
1186+
11691187

11701188
if __name__ == '__main__':
11711189
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` when used with ``happy_eyeballs_delay``

0 commit comments

Comments
 (0)