Skip to content
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

GH-95289: Always call uncancel() when required #95513

Closed
wants to merge 1 commit into from

Conversation

gvanrossum
Copy link
Member

@gvanrossum gvanrossum commented Aug 1, 2022

In __aexit__() we should always call self._parent_task.uncancel() when we called self._parent_task.cancel() -- even if the exception being received by __aexit__() is not CancelledError (since it may have been suppressed or replaced by another exception).

The logic is a bit contorted since we must propagate a cancellation error in two separate situations:

  • if uncancel() is called and it returns a value greater than zero
  • if we don't call uncancel() at all

@YvesDup
Copy link
Contributor

YvesDup commented Aug 1, 2022

Hi,
I ran this code with your changes, for the required test in the 'externally cancel' case.
Here a CancelledError is raised, _parent_cancel_requested is False, so uncancel() is never called. Is the expected behavior ?

import asyncio

class MyException(Exception):
    pass

async def async_fn2():
    try:
        await asyncio.sleep(0.0)
        raise MyException
    finally:
        print("raise done")

async def main():
    task = asyncio.current_task()
    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(async_fn2())
            await asyncio.sleep(1)

    except* (MyException, asyncio.CancelledError) as e:
        # How to check a private `_parent_cancel_requested` attr here, outside the class ?
        print(f"{e.exceptions} -> {task.cancelling()=}, {tg._parent_cancel_requested=}")

async def supermain():
    t = asyncio.create_task(main())
    await asyncio.sleep(0)

    t.cancel()
    print("cancel main task")
    await asyncio.sleep(1)

    print(f"{t.cancelling()=} must be 1, confirm ?")

asyncio.run(supermain())

@gvanrossum
Copy link
Member Author

That code looks racy with the sleep(0.0) calls, but it is correct that since your supermain code cancels main, uncancel will not be called. Note that _parent_cancel_requested is not truly private (single underscore) so for debugging can check it.

@YvesDup
Copy link
Contributor

YvesDup commented Aug 1, 2022

I wonder whether it is important to check if parent task is not cancelling when starts the async with ?
In that case, should we raise a RuntimeError in the __aenter__ ?

@gvanrossum
Copy link
Member Author

I wonder whether it is important to check if parent task is not cancelling when starts the async with ? In that case, should we raise a RuntimeError in the __aenter__ ?

What problem would that solve?

@gvanrossum gvanrossum force-pushed the fix-taskgroup-uncancel branch from dde8683 to 839f7f3 Compare August 1, 2022 20:46
@gvanrossum gvanrossum force-pushed the fix-taskgroup-uncancel branch from 839f7f3 to 6cd25fc Compare August 1, 2022 20:50
@YvesDup
Copy link
Contributor

YvesDup commented Aug 2, 2022

I wonder whether it is important to check if parent task is not cancelling when starts the async with ? In that case, should we raise a RuntimeError in the aenter ?

What problem would that solve?

Problem is already solved with the 'externally cancel' case.
Sorry for waste your time.

@kumaraditya303
Copy link
Contributor

Superseded by #95602 which includes tests.

@gvanrossum gvanrossum deleted the fix-taskgroup-uncancel branch August 7, 2022 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants