Skip to content

Instantly share code, notes, and snippets.

@timmwagener
Created May 23, 2020 12:56
Show Gist options
  • Save timmwagener/dfed038dc2081c8b5a770e175ba3756b to your computer and use it in GitHub Desktop.
Save timmwagener/dfed038dc2081c8b5a770e175ba3756b to your computer and use it in GitHub Desktop.
Test gather cancellation and resulting state.
# -*- coding: utf-8 -*-
"""Test gather cancellation and resulting state.
SeeAlso:
https://stackoverflow.com/questions/61942306/asyncio-gather-task-cancelled-is-false-after-task-cancel
Here's my take on what's happening:
* future_gather.cancel() cancels task_child
* await future_gather will cause task_child to run and raise CancelledError
* task_child's done callback for future_gather is invoked
* _done_callback(task_child) calls future_gather.set_exception(CancelledError()) which sets:
* future_gather._exception = CancelledError()
* future_gather._state = _FINISHED
* await calls future_gather.result() which raises self._exception if set
Therefore the end result is a future whose:
* .cancel() has been called
* has explicitly raised a CancelledError to clients
* returns a CancelledError for .exception()
* returns False for .cancelled()
* has a _state of FINISHED (not CANCELLED)
"""
import logging
from asyncio import CancelledError
from asyncio import ensure_future
from asyncio import gather
from asyncio import run
from asyncio import sleep
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def main():
"""Cancel a gather() future and child and return it."""
task_child = ensure_future(sleep(1.0))
future_gather = gather(task_child)
future_gather.cancel()
try:
await future_gather
except CancelledError:
pass
return future_gather, task_child
# run
future_gather, task_child = run(main())
# log gather state
logger.info(f"gather:")
logger.info(f"cancelled: {future_gather.cancelled()}")
logger.info(f"done: {future_gather.done()}")
logger.info(f"exception: {future_gather.exception()!r}")
logger.info(f"state: {future_gather._state}")
logger.info(f"")
# log child state
logger.info(f"child:")
logger.info(f"cancelled: {task_child.cancelled()}")
logger.info(f"done: {task_child.done()}")
# logger.info(f"exception: {task_child.exception()}") Raises because _state is CANCELLED
logger.info(f"state: {task_child._state}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment