Skip to content

Commit 2d84fe5

Browse files
GH-95289: Always call uncancel() when parent cancellation is requested (GH-95602)
Co-authored-by: Guido van Rossum <[email protected]> (cherry picked from commit 2fef275) Co-authored-by: Kumar Aditya <[email protected]>
1 parent f292635 commit 2d84fe5

File tree

3 files changed

+42
-9
lines changed

3 files changed

+42
-9
lines changed

Lib/asyncio/taskgroups.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,22 @@ async def __aenter__(self):
5454

5555
async def __aexit__(self, et, exc, tb):
5656
self._exiting = True
57-
propagate_cancellation_error = None
5857

5958
if (exc is not None and
6059
self._is_base_error(exc) and
6160
self._base_error is None):
6261
self._base_error = exc
6362

64-
if et is not None:
65-
if et is exceptions.CancelledError:
66-
if self._parent_cancel_requested and not self._parent_task.uncancel():
67-
# Do nothing, i.e. swallow the error.
68-
pass
69-
else:
70-
propagate_cancellation_error = exc
63+
propagate_cancellation_error = \
64+
exc if et is exceptions.CancelledError else None
65+
if self._parent_cancel_requested:
66+
# If this flag is set we *must* call uncancel().
67+
if self._parent_task.uncancel() == 0:
68+
# If there are no pending cancellations left,
69+
# don't propagate CancelledError.
70+
propagate_cancellation_error = None
7171

72+
if et is not None:
7273
if not self._aborting:
7374
# Our parent task is being cancelled:
7475
#

Lib/test/test_asyncio/test_taskgroups.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import asyncio
55
import contextvars
6-
6+
import contextlib
77
from asyncio import taskgroups
88
import unittest
99

@@ -741,6 +741,37 @@ async def coro2(g):
741741

742742
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
743743

744+
async def test_taskgroup_context_manager_exit_raises(self):
745+
# See https://github.com/python/cpython/issues/95289
746+
class CustomException(Exception):
747+
pass
748+
749+
async def raise_exc():
750+
raise CustomException
751+
752+
@contextlib.asynccontextmanager
753+
async def database():
754+
try:
755+
yield
756+
finally:
757+
raise CustomException
758+
759+
async def main():
760+
task = asyncio.current_task()
761+
try:
762+
async with taskgroups.TaskGroup() as tg:
763+
async with database():
764+
tg.create_task(raise_exc())
765+
await asyncio.sleep(1)
766+
except* CustomException as err:
767+
self.assertEqual(task.cancelling(), 0)
768+
self.assertEqual(len(err.exceptions), 2)
769+
770+
else:
771+
self.fail('CustomException not raised')
772+
773+
await asyncio.create_task(main())
774+
744775

745776
if __name__ == "__main__":
746777
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)