-
-
Notifications
You must be signed in to change notification settings - Fork 178
The documentation on task cancellation is unclear #253
Comments
I don't think this is violating the specification of wait_for(). Also, your example is way too complicated for anyone to follow. If you really think there is a bug in asyncio please provide a simpler example, and show the exact flow through your code rather than trying to guess what happened based on assertions. |
@gvanrossum I've updated the example to something simpler. |
Also, as a workaround, I've implemented my own
|
OK, your workaround helped me understand what's going on. I don't think we can add this behavior to wait_for() -- it is totally legal (if uncommon) for a task to absorb a cancellation (thrown in) and take its time (waiting for something else) before actually returning, or even simply ignoring the cancellation altogether. In any case a task's response to cancellation (as opposed to a plain Future's) is asynchronous. I don't think wait_for() should be required to wait any further when the timeout is reached. So I'm closing this since it's a feature, not a bug. |
If you believe that the documentation is uncomplete about cancellation, please propose a new text, and I will update the doc. Currently, the doc says "If the wait is cancelled, the future fut is also cancelled." Maybe we need to enhance the documentation to explain better how tasks handle cancellation. For example, add a section and add a link from wait_for() doc to this new section? Currently, the explanation of task cancellation lives at: |
Change that to:
|
This sentence is unclear, or wrong. If a task is cancelled, wait_for() immediatly returns (raise CancelledError). If wait_for() is used on a task and a task doesn't complete before the timeout, the task is cancelled. The problem is that the cancellation is only "scheduled": cancelled() returns False. It's counter intuitive. A shorter example is: "task.cancel(); assert not task.cancelled()". Ok, I now have a better understanding of the documentation issue. |
OK I'll rephrase:
|
I'd like to add another point for improved docs. A minimal example to demonstrate its necessity: import asyncio
async def something():
try:
print('do')
await asyncio.sleep(1)
except asyncio.CancelledError:
print('cancelled')
finally:
print('finally')
await asyncio.sleep(1)
print('done')
async def cancel_it(task):
await asyncio.sleep(0.5)
task.cancel()
await task # if not awaited, above finally block would not finish.
if __name__ == '__main__':
loop = asyncio.get_event_loop()
t = loop.create_task(something())
try:
loop.run_until_complete(cancel_it(t))
except KeyboardInterrupt:
loop.stop()
finally:
loop.close() I believe "await-after-cancel" must be explained in asyncio docs. |
You are catching asyncio.CancelledError inside the task, and not
re-raising. IIRC that means that means to ignore cancellation.
That is the reason you need to await the task in this example, it is
because the task has not been cancelled and you have to wait for it to
complete normally.
…On 10 January 2017 at 08:55, Joongi Kim ***@***.***> wrote:
I'd like to add another point for improved docs.
If a task is cancelled but the task has remaining await statements, it
*must* be awaited again.
I found this in aiohttp's documentation
<http://aiohttp.readthedocs.io/en/stable/web.html#background-tasks> (see
cleanup_background_tasks function in the example).
A minimal example to demonstrate its necessity:
import asyncio
async def something():
try:
print('do')
await asyncio.sleep(1)
except asyncio.CancelledError:
print('cancelled')
finally:
print('finally')
await asyncio.sleep(1)
print('done')
async def cancel_it(task):
await asyncio.sleep(0.5)
task.cancel()
await task # if not awaited, above finally block would not finish.
if __name__ == '__main__':
loop = asyncio.get_event_loop()
t = loop.create_task(something())
try:
loop.run_until_complete(cancel_it(t))
except KeyboardInterrupt:
loop.stop()
finally:
loop.close()
I believe "await-after-cancel" must be explained in asyncio docs.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#253 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ACGGaGiOwPjLCv1rz4pdWJb0WqHXzx8Nks5rQ0d4gaJpZM4FUzTo>
.
--
Gustavo J. A. M. Carneiro
Gambit Research
"The universe is always one step beyond logic." -- Frank Herbert
|
Yes, in my example I'm using CancelledError as a signal for graceful shutdown. |
The point that confused me before some experience was:
I think the docs need to carefully guide the asyncio beginners not to fall into misunderstandings (like me...), by describing when to use cancellation, what it stands for whether a task absorbs CancelledError or re-raises, etc. The learning experience should be consistent no matter which asyncio-based library you start with. |
Check the documentation
You are catching Not a bug. |
I'm not saying it's a bug but we should improve the docs.
This part gives only a hint. I think it would be bettter to provide a concrete example to cancel tasks and await them to clean up because the readers without background knowledge may miss the await-again part. |
To make sure I got this: calling async def coroutine():
result = await work() # task.cancel() is called while this task is stopped here
process(result) # never executes
async def main():
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
# ...
task.cancel() By default, this will directly abort the task as soon as it executes on a posterior cycle, as any exception generated within async def coroutine():
try:
result = await work()
process(result)
except asyncio.CancelledError:
pass
# the task will end and be marked as done, but not as cancelled However, by encapsulating the async def coroutine():
try:
result = await work()
except asyncio.CancelledError:
some_cleanup()
raise # safely terminates, but still gets marked as cancelled
process(result) I think some of the confusion stems from the fact that any The aiohttp code is exceptional because the cleanup code itself calls other coroutines, so after cancelling you still need to await it one more time to give those an opportunity to execute. When this happens, it should be well documented, as ordinarily you'd expect the task to fully terminate by itself after the |
Here is an example:
And the output:
So
wait_for()
throwsTimeoutError
, but the coroutine is not yet cancelled.Basically, I think that the task should first be cancelled, and then
wait_for()
should return.OR.
It should be more explicitly documented that the task will cancel, but not necessarily before
wait_for()
raises an error.The text was updated successfully, but these errors were encountered: