Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.7k
Closed
Description
Bug report
Bug description:
asyncio.TaskGroup attempts to avoid refcycles in raised exceptions by deletingself._errors but when I reviewed the code it doesn't actually achieve this:
see
cpython/Lib/asyncio/taskgroups.py
Lines 152 to 156 in5e9e506
| try: | |
| me=BaseExceptionGroup('unhandled errors in a TaskGroup',self._errors) | |
| raisemefromNone | |
| finally: | |
| self._errors=None |
There's a refcycle inme is me.__traceback__.tb_next.tb_frame.f_locals["me"]
I wrote a few tests to route out all the refcycles in tracebacks
importasyncioimportgcimportunittestclassTestTaskGroup(unittest.IsolatedAsyncioTestCase):asyncdeftest_exception_refcycles_direct(self):"""Test that TaskGroup doesn't keep a reference to the raised ExceptionGroup"""tg=asyncio.TaskGroup()exc=Noneclass_Done(Exception):passtry:asyncwithtg:raise_DoneexceptExceptionGroupase:exc=eself.assertIsNotNone(exc)self.assertListEqual(gc.get_referrers(exc), [])asyncdeftest_exception_refcycles_errors(self):"""Test that TaskGroup deletes self._errors, and __aexit__ args"""tg=asyncio.TaskGroup()exc=Noneclass_Done(Exception):passtry:asyncwithtg:raise_Done except*_Doneasexcs:exc=excs.exceptions[0]self.assertIsInstance(exc,_Done)self.assertListEqual(gc.get_referrers(exc), [])asyncdeftest_exception_refcycles_parent_task(self):"""Test that TaskGroup deletes self._parent_task"""tg=asyncio.TaskGroup()exc=Noneclass_Done(Exception):passasyncdefcoro_fn():asyncwithtg:raise_Donetry:asyncwithasyncio.TaskGroup()astg2:tg2.create_task(coro_fn()) except*_Doneasexcs:exc=excs.exceptions[0].exceptions[0]self.assertIsInstance(exc,_Done)self.assertListEqual(gc.get_referrers(exc), [])asyncdeftest_exception_refcycles_propagate_cancellation_error(self):"""Test that TaskGroup deletes propagate_cancellation_error"""tg=asyncio.TaskGroup()exc=Nonetry:asyncwithasyncio.timeout(-1):asyncwithtg:awaitasyncio.sleep(0)exceptTimeoutErrorase:exc=e.__cause__self.assertIsInstance(exc,asyncio.CancelledError)self.assertListEqual(gc.get_referrers(exc), [])asyncdeftest_exception_refcycles_base_error(self):"""Test that TaskGroup deletes self._base_error"""classMyKeyboardInterrupt(KeyboardInterrupt):passtg=asyncio.TaskGroup()exc=Nonetry:asyncwithtg:raiseMyKeyboardInterruptexceptMyKeyboardInterruptase:exc=eself.assertIsNotNone(exc)self.assertListEqual(gc.get_referrers(exc), [])
in writing all these tests I noticed refcycles in PyFuture:
cpython/Lib/asyncio/futures.py
Lines 197 to 198 in58f7763
| exc=self._make_cancelled_error() | |
| raiseexc |
cpython/Lib/asyncio/futures.py
Lines 215 to 216 in58f7763
| exc=self._make_cancelled_error() | |
| raiseexc |
classBaseFutureTests:deftest_future_cancelled_result_refcycles(self):f=self._new_future(loop=self.loop)f.cancel()exc=Nonetry:f.result()exceptasyncio.CancelledErrorase:exc=eself.assertIsNotNone(exc)self.assertListEqual(gc.get_referrers(exc), [])deftest_future_cancelled_exception_refcycles(self):f=self._new_future(loop=self.loop)f.cancel()exc=Nonetry:f.exception()exceptasyncio.CancelledErrorase:exc=eself.assertIsNotNone(exc)self.assertListEqual(gc.get_referrers(exc), [])
CPython versions tested on:
3.12, 3.13
Operating systems tested on:
Linux
Linked PRs
- gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles #124959
- [3.13] gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles (GH-124959) #125463
- [3.12] gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles (#124959) #125466
- gh-124958: Revert "gh-125472: Revert "gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles ... #125486
Metadata
Metadata
Assignees
Labels
Projects
Status
Done