python-trio / trio

Trio – a friendly Python library for async concurrency and I/O
https://trio.readthedocs.io
Other
5.98k stars 325 forks source link

open_nursery is unable to call move_on_after when it is active, but it can still work. #2998

Closed allrobot closed 1 month ago

allrobot commented 1 month ago

chrome_6hk4BUQDAC

Note that what matters here is the scopes that were active when open_nursery() was called, not the scopes active when start_soon is called. So for example, the timeout block below does nothing at all:

async with trio.open_nursery() as nursery:
    with trio.move_on_after(TIMEOUT):  # don't do this!
        nursery.start_soon(child)

Actually, the example code runs fine in PyCharm. Did I do something wrong? Or did I misunderstand the meaning of this code and the instructions in the documentation?

oremanj commented 1 month ago

The point that the documentation is trying to make is that a timeout in that position will not cause child to be cancelled. Your example doesn't demonstrate this, because it uses a child task that completes immediately, so you can't tell whether the timeout fired or not. If you do instead something like:

async def child():
    print("going to sleep")
    await trio.sleep(5)
    print("done sleeping")

then you should be able to tell a difference between

async with trio.open_nursery() as nursery:
    with trio.move_on_after(1):
        nursery.start_soon(child)

(which will wait 5 seconds and print the "done sleeping" message) and

with trio.move_on_after(1):
    async with trio.open_nursery() as nursery:
        nursery.start_soon(child)

(which will cancel its sleep after 1 second, so you never get to "done sleeping").

By "the timeout block below does nothing at all" we mean that the behavior is the same as if you didn't write the move_on_after() context manager at all, i.e.:

async with trio.open_nursery() as nursery:
    nursery.start_soon(child)

The reason for this behavior is that the timeout only surrounds the nursery.start_soon(child) call... but that completes immediately, because it's just requesting that the task should start running in the background. The child task logically runs directly underneath the async with trio.open_nursery() as nursery: block, so the timeout block needs to be outside of the nursery in order to affect the child task.

allrobot commented 1 month ago

Oh, I see, I misunderstood (I didn't catch on at first), the correct mean is: The trio.move_on_after(1) underneath trio.open_nursery() cannot perform the timeout operation. It will continue to execute the trio.sleep(x) within it until it completes, instead of ending all the code within the scope of trio.move_on_after(1) after 1 second.