From 2d84fe59c0a8bba689d79cefa7110970dd781d46 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
 <31488909+miss-islington@users.noreply.github.com>
Date: Thu, 4 Aug 2022 07:50:54 -0700
Subject: [PATCH] GH-95289: Always call uncancel() when parent cancellation is
 requested (GH-95602)

Co-authored-by: Guido van Rossum <guido@python.org>
(cherry picked from commit 2fef27589e44c91042c2598b5cad6c6ad0516d93)

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/taskgroups.py                     | 17 +++++-----
 Lib/test/test_asyncio/test_taskgroups.py      | 33 ++++++++++++++++++-
 ...2-08-03-16-52-32.gh-issue-95289.FMnHlV.rst |  1 +
 3 files changed, 42 insertions(+), 9 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2022-08-03-16-52-32.gh-issue-95289.FMnHlV.rst

diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index 3ca65062efd..097b4864f7a 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:
                 #
diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py
index 26fb5e466af..99498e7b36f 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,37 @@ 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 as err:
+                self.assertEqual(task.cancelling(), 0)
+                self.assertEqual(len(err.exceptions), 2)
+
+            else:
+                self.fail('CustomException not raised')
+
+        await asyncio.create_task(main())
+
 
 if __name__ == "__main__":
     unittest.main()
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 00000000000..d802f557217
--- /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.
-- 
GitLab