anyio
anyio copied to clipboard
`get_coro` is missing a `None` check in `_task_started`
Things to check first
-
[X] I have searched the existing issues and didn't find my bug already reported there
-
[X] I have checked that my bug is still present in the latest release
AnyIO version
4.4.0
Python version
3.12
What happened?
Hello!
This was discovered upstream in CPython, see this issue. In AnyIO's _task_started function (see here), get_coro() can return None as of 3.12, with asyncio's new eager_task_factory.
Unfortunately, there's not all that much you can do about this at this very moment -- a bug in C caused this code to segfault on current versions (this is fixed now, but not released).
How can we reproduce the bug?
This was the reproducer used in the CPython issue, which eventually fails at _task_started. It's not all that minimal, sorry! I haven't used AnyIO enough to come up with my own reproducer.
import asyncio
import httpx
import uvicorn
from fastapi import FastAPI
from starlette.responses import StreamingResponse
async def main():
loop = asyncio.get_running_loop()
loop.set_task_factory(asyncio.eager_task_factory)
app = FastAPI()
@app.get("/")
async def get():
async def _():
yield "1"
return StreamingResponse(
_(),
)
server = uvicorn.Server(
uvicorn.Config(
app,
host="0.0.0.0",
port=8080,
)
)
asyncio.create_task(server.serve())
client = httpx.AsyncClient()
await client.get("http://localhost:8080")
if __name__ == "__main__":
asyncio.run(main())
Again, this code segfaults on 3.12.4, you'll need to wait for the next patch release for the fix. In the meantime, it might be a good idea to throw an exception if using AnyIO with eager_task_factory.
It's bizarre that get_coro() can return None when the eager task has a reference to the coro
What is the expected outcome here? Even if I patch _task_started() to check for None and return False then, it still crashes with CancelledError at asyncio/queues.py line 158 (await getter).
This happens even if I comment out the httpx code.
Also same if I comment out the set_task_factory() call.