mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
GH-95704: Don't suppress errors from tasks when TG is cancelled (#95761)
When a task catches CancelledError and raises some other error, the other error should not silently be suppressed. Any scenario where a task crashes in cleanup upon cancellation will now result in an ExceptionGroup wrapping the crash(es) instead of propagating CancelledError and ignoring the side errors. NOTE: This represents a change in behavior (hence the need to change several tests). But it is only an edge case. Co-authored-by: Thomas Grainger <tagrain@gmail.com>
This commit is contained in:
parent
9b30b965f0
commit
f51f54f39d
3 changed files with 35 additions and 28 deletions
|
@ -116,10 +116,9 @@ class TaskGroup:
|
||||||
if self._base_error is not None:
|
if self._base_error is not None:
|
||||||
raise self._base_error
|
raise self._base_error
|
||||||
|
|
||||||
if propagate_cancellation_error is not None:
|
# Propagate CancelledError if there is one, except if there
|
||||||
# The wrapping task was cancelled; since we're done with
|
# are other errors -- those have priority.
|
||||||
# closing all child tasks, just propagate the cancellation
|
if propagate_cancellation_error and not self._errors:
|
||||||
# request now.
|
|
||||||
raise propagate_cancellation_error
|
raise propagate_cancellation_error
|
||||||
|
|
||||||
if et is not None and et is not exceptions.CancelledError:
|
if et is not None and et is not exceptions.CancelledError:
|
||||||
|
|
|
@ -230,10 +230,12 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
self.assertEqual(NUM, 15)
|
self.assertEqual(NUM, 15)
|
||||||
|
|
||||||
async def test_cancellation_in_body(self):
|
async def test_taskgroup_08(self):
|
||||||
|
|
||||||
async def foo():
|
async def foo():
|
||||||
await asyncio.sleep(0.1)
|
try:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
finally:
|
||||||
1 / 0
|
1 / 0
|
||||||
|
|
||||||
async def runner():
|
async def runner():
|
||||||
|
@ -241,18 +243,16 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
g.create_task(foo())
|
g.create_task(foo())
|
||||||
|
|
||||||
try:
|
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
r = asyncio.create_task(runner())
|
r = asyncio.create_task(runner())
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
self.assertFalse(r.done())
|
self.assertFalse(r.done())
|
||||||
r.cancel()
|
r.cancel()
|
||||||
with self.assertRaises(asyncio.CancelledError) as cm:
|
with self.assertRaises(ExceptionGroup) as cm:
|
||||||
await r
|
await r
|
||||||
|
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
|
||||||
|
|
||||||
async def test_taskgroup_09(self):
|
async def test_taskgroup_09(self):
|
||||||
|
|
||||||
|
@ -316,7 +316,9 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
async def test_taskgroup_11(self):
|
async def test_taskgroup_11(self):
|
||||||
|
|
||||||
async def foo():
|
async def foo():
|
||||||
await asyncio.sleep(0.1)
|
try:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
finally:
|
||||||
1 / 0
|
1 / 0
|
||||||
|
|
||||||
async def runner():
|
async def runner():
|
||||||
|
@ -325,23 +327,25 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
g2.create_task(foo())
|
g2.create_task(foo())
|
||||||
|
|
||||||
try:
|
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
r = asyncio.create_task(runner())
|
r = asyncio.create_task(runner())
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
self.assertFalse(r.done())
|
self.assertFalse(r.done())
|
||||||
r.cancel()
|
r.cancel()
|
||||||
with self.assertRaises(asyncio.CancelledError):
|
with self.assertRaises(ExceptionGroup) as cm:
|
||||||
await r
|
await r
|
||||||
|
|
||||||
|
self.assertEqual(get_error_types(cm.exception), {ExceptionGroup})
|
||||||
|
self.assertEqual(get_error_types(cm.exception.exceptions[0]), {ZeroDivisionError})
|
||||||
|
|
||||||
async def test_taskgroup_12(self):
|
async def test_taskgroup_12(self):
|
||||||
|
|
||||||
async def foo():
|
async def foo():
|
||||||
await asyncio.sleep(0.1)
|
try:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
finally:
|
||||||
1 / 0
|
1 / 0
|
||||||
|
|
||||||
async def runner():
|
async def runner():
|
||||||
|
@ -352,19 +356,19 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
g2.create_task(foo())
|
g2.create_task(foo())
|
||||||
|
|
||||||
try:
|
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
r = asyncio.create_task(runner())
|
r = asyncio.create_task(runner())
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
self.assertFalse(r.done())
|
self.assertFalse(r.done())
|
||||||
r.cancel()
|
r.cancel()
|
||||||
with self.assertRaises(asyncio.CancelledError):
|
with self.assertRaises(ExceptionGroup) as cm:
|
||||||
await r
|
await r
|
||||||
|
|
||||||
|
self.assertEqual(get_error_types(cm.exception), {ExceptionGroup})
|
||||||
|
self.assertEqual(get_error_types(cm.exception.exceptions[0]), {ZeroDivisionError})
|
||||||
|
|
||||||
async def test_taskgroup_13(self):
|
async def test_taskgroup_13(self):
|
||||||
|
|
||||||
async def crash_after(t):
|
async def crash_after(t):
|
||||||
|
@ -424,8 +428,9 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
self.assertFalse(r.done())
|
self.assertFalse(r.done())
|
||||||
r.cancel()
|
r.cancel()
|
||||||
with self.assertRaises(asyncio.CancelledError):
|
with self.assertRaises(ExceptionGroup) as cm:
|
||||||
await r
|
await r
|
||||||
|
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
|
||||||
|
|
||||||
async def test_taskgroup_16(self):
|
async def test_taskgroup_16(self):
|
||||||
|
|
||||||
|
@ -451,8 +456,9 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
self.assertFalse(r.done())
|
self.assertFalse(r.done())
|
||||||
r.cancel()
|
r.cancel()
|
||||||
with self.assertRaises(asyncio.CancelledError):
|
with self.assertRaises(ExceptionGroup) as cm:
|
||||||
await r
|
await r
|
||||||
|
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
|
||||||
|
|
||||||
async def test_taskgroup_17(self):
|
async def test_taskgroup_17(self):
|
||||||
NUM = 0
|
NUM = 0
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
When a task catches :exc:`asyncio.CancelledError` and raises some other error,
|
||||||
|
the other error should generally not silently be suppressed.
|
Loading…
Add table
Add a link
Reference in a new issue