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 2fef27589e)

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2022-08-04 07:50:54 -07:00 committed by GitHub
parent f2926358d1
commit 2d84fe59c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 9 deletions

View file

@ -54,21 +54,22 @@ class TaskGroup:
async def __aexit__(self, et, exc, tb): async def __aexit__(self, et, exc, tb):
self._exiting = True self._exiting = True
propagate_cancellation_error = None
if (exc is not None and if (exc is not None and
self._is_base_error(exc) and self._is_base_error(exc) and
self._base_error is None): self._base_error is None):
self._base_error = exc self._base_error = exc
if et is not None: propagate_cancellation_error = \
if et is exceptions.CancelledError: exc if et is exceptions.CancelledError else None
if self._parent_cancel_requested and not self._parent_task.uncancel(): if self._parent_cancel_requested:
# Do nothing, i.e. swallow the error. # If this flag is set we *must* call uncancel().
pass if self._parent_task.uncancel() == 0:
else: # If there are no pending cancellations left,
propagate_cancellation_error = exc # don't propagate CancelledError.
propagate_cancellation_error = None
if et is not None:
if not self._aborting: if not self._aborting:
# Our parent task is being cancelled: # Our parent task is being cancelled:
# #

View file

@ -3,7 +3,7 @@
import asyncio import asyncio
import contextvars import contextvars
import contextlib
from asyncio import taskgroups from asyncio import taskgroups
import unittest import unittest
@ -741,6 +741,37 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError}) 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__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -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.