summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_asyncio/test_taskgroups.py
diff options
context:
space:
mode:
authorThomas Grainger <tagrain@gmail.com>2024-10-14 15:19:56 (GMT)
committerGitHub <noreply@github.com>2024-10-14 15:19:56 (GMT)
commitd5dbbf4372cd3dbf3eead1cc70ddc4261c061fd9 (patch)
tree81f9098b3e6857cad85f38790a96765968b4074b /Lib/test/test_asyncio/test_taskgroups.py
parent45df264f3ffbc0893cbfd257131d3abe21043786 (diff)
downloadcpython-d5dbbf4372cd3dbf3eead1cc70ddc4261c061fd9.zip
cpython-d5dbbf4372cd3dbf3eead1cc70ddc4261c061fd9.tar.gz
cpython-d5dbbf4372cd3dbf3eead1cc70ddc4261c061fd9.tar.bz2
gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles (#124959)
Diffstat (limited to 'Lib/test/test_asyncio/test_taskgroups.py')
-rw-r--r--Lib/test/test_asyncio/test_taskgroups.py92
1 files changed, 90 insertions, 2 deletions
diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py
index 4852536..138f59e 100644
--- a/Lib/test/test_asyncio/test_taskgroups.py
+++ b/Lib/test/test_asyncio/test_taskgroups.py
@@ -1,7 +1,7 @@
# Adapted with permission from the EdgeDB project;
# license: PSFL.
-
+import gc
import asyncio
import contextvars
import contextlib
@@ -11,7 +11,6 @@ import warnings
from test.test_asyncio.utils import await_without_task
-
# To prevent a warning "test altered the execution environment"
def tearDownModule():
asyncio.set_event_loop_policy(None)
@@ -899,6 +898,95 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
await outer()
+ async def test_exception_refcycles_direct(self):
+ """Test that TaskGroup doesn't keep a reference to the raised ExceptionGroup"""
+ tg = asyncio.TaskGroup()
+ exc = None
+
+ class _Done(Exception):
+ pass
+
+ try:
+ async with tg:
+ raise _Done
+ except ExceptionGroup as e:
+ exc = e
+
+ self.assertIsNotNone(exc)
+ self.assertListEqual(gc.get_referrers(exc), [])
+
+
+ async def test_exception_refcycles_errors(self):
+ """Test that TaskGroup deletes self._errors, and __aexit__ args"""
+ tg = asyncio.TaskGroup()
+ exc = None
+
+ class _Done(Exception):
+ pass
+
+ try:
+ async with tg:
+ raise _Done
+ except* _Done as excs:
+ exc = excs.exceptions[0]
+
+ self.assertIsInstance(exc, _Done)
+ self.assertListEqual(gc.get_referrers(exc), [])
+
+
+ async def test_exception_refcycles_parent_task(self):
+ """Test that TaskGroup deletes self._parent_task"""
+ tg = asyncio.TaskGroup()
+ exc = None
+
+ class _Done(Exception):
+ pass
+
+ async def coro_fn():
+ async with tg:
+ raise _Done
+
+ try:
+ async with asyncio.TaskGroup() as tg2:
+ tg2.create_task(coro_fn())
+ except* _Done as excs:
+ exc = excs.exceptions[0].exceptions[0]
+
+ self.assertIsInstance(exc, _Done)
+ self.assertListEqual(gc.get_referrers(exc), [])
+
+ async def test_exception_refcycles_propagate_cancellation_error(self):
+ """Test that TaskGroup deletes propagate_cancellation_error"""
+ tg = asyncio.TaskGroup()
+ exc = None
+
+ try:
+ async with asyncio.timeout(-1):
+ async with tg:
+ await asyncio.sleep(0)
+ except TimeoutError as e:
+ exc = e.__cause__
+
+ self.assertIsInstance(exc, asyncio.CancelledError)
+ self.assertListEqual(gc.get_referrers(exc), [])
+
+ async def test_exception_refcycles_base_error(self):
+ """Test that TaskGroup deletes self._base_error"""
+ class MyKeyboardInterrupt(KeyboardInterrupt):
+ pass
+
+ tg = asyncio.TaskGroup()
+ exc = None
+
+ try:
+ async with tg:
+ raise MyKeyboardInterrupt
+ except MyKeyboardInterrupt as e:
+ exc = e
+
+ self.assertIsNotNone(exc)
+ self.assertListEqual(gc.get_referrers(exc), [])
+
if __name__ == "__main__":
unittest.main()