From 14a5674a7fde5ff81da2483575bb4c54b3d001d6 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 31 Jul 2022 19:24:49 -0700 Subject: [PATCH 1/4] GH-95289: Always call uncancel() when required --- Lib/asyncio/taskgroups.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 3ca65062efd272..097b4864f7ab98 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -54,21 +54,22 @@ async def __aenter__(self): async def __aexit__(self, et, exc, tb): self._exiting = True - propagate_cancellation_error = None if (exc is not None and self._is_base_error(exc) and self._base_error is None): self._base_error = exc - if et is not None: - if et is exceptions.CancelledError: - if self._parent_cancel_requested and not self._parent_task.uncancel(): - # Do nothing, i.e. swallow the error. - pass - else: - propagate_cancellation_error = exc + propagate_cancellation_error = \ + exc if et is exceptions.CancelledError else None + if self._parent_cancel_requested: + # If this flag is set we *must* call uncancel(). + if self._parent_task.uncancel() == 0: + # If there are no pending cancellations left, + # don't propagate CancelledError. + propagate_cancellation_error = None + if et is not None: if not self._aborting: # Our parent task is being cancelled: # From 10d17bc9a9efd7e4af092a5ddcff29a74541eb29 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:44:02 +0000 Subject: [PATCH 2/4] add test --- Lib/test/test_asyncio/test_taskgroups.py | 31 +++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 26fb5e466af926..691ab861c747ba 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -3,7 +3,7 @@ import asyncio import contextvars - +import contextlib from asyncio import taskgroups import unittest @@ -741,6 +741,35 @@ async def coro2(g): self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError}) + async def test_taskgroup_context_manager_exit_raises(self): + # See https://github.com/python/cpython/issues/95289 + class CustomException(Exception): + pass + + async def raise_exc(): + raise CustomException + + @contextlib.asynccontextmanager + async def database(): + try: + yield + finally: + raise CustomException + + async def main(): + task = asyncio.current_task() + try: + async with taskgroups.TaskGroup() as tg: + async with database(): + tg.create_task(raise_exc()) + await asyncio.sleep(1) + except* CustomException: + self.assertEqual(task.cancelling(), 0) + else: + self.fail('CustomException not raised') + + await asyncio.create_task(main()) + if __name__ == "__main__": unittest.main() From 8cc90a52ad11eaa7cf7b0b4b705d3ecfa91c926a Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:52:34 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-08-03-16-52-32.gh-issue-95289.FMnHlV.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-08-03-16-52-32.gh-issue-95289.FMnHlV.rst diff --git a/Misc/NEWS.d/next/Library/2022-08-03-16-52-32.gh-issue-95289.FMnHlV.rst b/Misc/NEWS.d/next/Library/2022-08-03-16-52-32.gh-issue-95289.FMnHlV.rst new file mode 100644 index 00000000000000..d802f557217b48 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-08-03-16-52-32.gh-issue-95289.FMnHlV.rst @@ -0,0 +1 @@ +Fix :class:`asyncio.TaskGroup` to propagate exception when :exc:`asyncio.CancelledError` was replaced with another exception by a context manger. Patch by Kumar Aditya and Guido van Rossum. From f8004d2c754fcee7e298e090da724a8e00a8337a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 3 Aug 2022 21:34:55 -0700 Subject: [PATCH 4/4] Check that exactly 2 CustomExceptions were raised --- Lib/test/test_asyncio/test_taskgroups.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 691ab861c747ba..99498e7b36f0f9 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -763,8 +763,10 @@ async def main(): async with database(): tg.create_task(raise_exc()) await asyncio.sleep(1) - except* CustomException: + except* CustomException as err: self.assertEqual(task.cancelling(), 0) + self.assertEqual(len(err.exceptions), 2) + else: self.fail('CustomException not raised')