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

asyncio: odd behaviour with TaskGroup.start() #517

Closed
arthur-tacca opened this issue Jan 9, 2023 · 6 comments · Fixed by #597
Closed

asyncio: odd behaviour with TaskGroup.start() #517

arthur-tacca opened this issue Jan 9, 2023 · 6 comments · Fixed by #597

Comments

@arthur-tacca
Copy link

Consider the following code:

import anyio

async def sleep_and_raise(task_status=anyio.TASK_STATUS_IGNORED):
    await anyio.sleep(3)
    raise RuntimeError

async def sleep_only(task_status=anyio.TASK_STATUS_IGNORED):
    await anyio.sleep(20)
    task_status.started()

async def foo():
    async with anyio.create_task_group() as run_tg:
        async with anyio.create_task_group() as start_tg:
            start_tg.start_soon(run_tg.start, sleep_and_raise)
            print("here")
            await run_tg.start(sleep_only)
            print("there")

anyio.run(foo)

sleep_and_raise raises an exception, which propagates into start_tg (because task_status.started() hasn't been called yet, so the task is still running start_tg). This happens after 3 seconds, so sleep_only() (which is also still running under start_tg) gets cancelled.

Except, sleep_only() doesn't get cancelled, and the whole thing waits for it to complete. At that point, the exception is noticed and the exception gets raised.

If you add await anyio.sleep(0) immediately before print("there") then a cancellation happens then instead, so it's not a matter of having to reach the end of the task group context manager.

Changing await run_tg.start(sleep_only) to await start_tg.start(sleep_only) also fixes the problem.

@arthur-tacca
Copy link
Author

I should've made clear - this came up while I was writing an automated test (for aioresult), not because I need it for any real-world situation. Typically the start part of a task is something very quick, like opening a port of listening, so in practice this is perhaps not likely to be a big deal.

@agronholm
Copy link
Owner

There are corner cases where cancellation might not work as expected, but I'm working to resolve these for AnyIO 4.0. It's a daunting task (cancellation is hard to get right), so I'm not sure when it's going to be released.

@arthur-tacca
Copy link
Author

"cancellation is hard to get right" I can imagine it is! And this is an especially obscure case, and doesn't even actually affect me. I'm happy to choose this of you like, I just wanted to let you know about it.

@agronholm
Copy link
Owner

I investigated this today, and the main task (foo()) isn't getting cancelled by the RuntimeError because it's in a shielded scope, waiting for await run_tg.start(sleep_only) to complete. Obviously this differs from Trio behavior, so I'm going to see if I can get this fixed for v4.0.

agronholm added a commit that referenced this issue Jul 26, 2023
@agronholm
Copy link
Owner

While I wasn't able to figure out a proper fix yet, I've at least added a minimal reproducing test to the test suite.

@agronholm
Copy link
Owner

I did in fact figure it out! @arthur-tacca would you mind reviewing the PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants