Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

gh-128588: gh-128550: remove eager tasks optimization that missed and introduced incorrect cancellations #129063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 20, 2025
Merged
12 changes: 5 additions & 7 deletions 12 Lib/asyncio/taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,12 @@ def create_task(self, coro, *, name=None, context=None):
else:
task = self._loop.create_task(coro, name=name, context=context)

# optimization: Immediately call the done callback if the task is
# Always schedule the done callback even if the task is
# already done (e.g. if the coro was able to complete eagerly),
# and skip scheduling a done callback
if task.done():
self._on_task_done(task)
else:
self._tasks.add(task)
task.add_done_callback(self._on_task_done)
# otherwise if the task completes with an exception then it will cancel
# the current task too early. gh-128550, gh-128588
self._tasks.add(task)
task.add_done_callback(self._on_task_done)
try:
return task
finally:
Expand Down
44 changes: 44 additions & 0 deletions 44 Lib/test/test_asyncio/test_taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,50 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
self.assertListEqual(gc.get_referrers(exc), no_other_refs())


async def test_cancels_task_if_created_during_creation(self):
# regression test for gh-128550
ran = False
class MyError(Exception):
pass

exc = None
try:
async with asyncio.TaskGroup() as tg:
async def third_task():
raise MyError("third task failed")

async def second_task():
nonlocal ran
tg.create_task(third_task())
with self.assertRaises(asyncio.CancelledError):
await asyncio.sleep(0) # eager tasks cancel here
await asyncio.sleep(0) # lazy tasks cancel here
ran = True

tg.create_task(second_task())
except* MyError as excs:
exc = excs.exceptions[0]

self.assertTrue(ran)
self.assertIsInstance(exc, MyError)


async def test_cancellation_does_not_leak_out_of_tg(self):
class MyError(Exception):
pass

async def throw_error():
raise MyError

try:
async with asyncio.TaskGroup() as tg:
tg.create_task(throw_error())
except* MyError:
pass
kumaraditya303 marked this conversation as resolved.
Show resolved Hide resolved
graingert marked this conversation as resolved.
Show resolved Hide resolved

await asyncio.sleep(0)
graingert marked this conversation as resolved.
Show resolved Hide resolved


class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
loop_factory = asyncio.EventLoop

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
remove a problematic optimization relating to eager tasks in :class:`asyncio.TaskGroup` that resulted in cancellations being missed
graingert marked this conversation as resolved.
Show resolved Hide resolved
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.