Skip to content

Eager task factory segfaults when hosting and calling an API from the same event loop #122332

Closed

Description

@irgolic

Crash report

What happened?

Love eager task factory, but it's crashing one of my tests. I'm using uvicorn and fastapi to host an API, and testing it with httpx running in the same event loop. This reliably produces a segfault.

It seems to only happen with streaming responses.

Here's a minimal code example:

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())

CPython versions tested on:

3.12

Operating systems tested on:

macOS

Output from running 'python -VV' on the command line:

Python 3.12.3 | packaged by conda-forge | (main, Apr 15 2024, 18:35:20) [Clang 16.0.6 ]

Linked PRs

Activity

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Jul 26, 2024
  2. ZeroIntensity commented on Jul 27, 2024

    @ZeroIntensity
    Member

    I was able to reproduce this. The segfault comes from _asyncio_Task_get_coro_impl: see here, self->task_coro is NULL.

  3. ZeroIntensity commented on Jul 27, 2024

    @ZeroIntensity
    Member

    Ah, I think this is just a missing NULL check. Based on the docs, this should be able to return None. This needs to be reflected in typeshed as well. I'll submit a PR for this.

  4. ZeroIntensity commented on Jul 27, 2024

    @ZeroIntensity
    Member

    FWIW, here's a reproducer that does not need third party packages:

    import asyncio
    
    
    async def main():
        loop = asyncio.get_running_loop()
        loop.set_task_factory(asyncio.eager_task_factory)
    
        async def foo(): ...
    
        task = asyncio.create_task(foo())
        await task
        print(task.get_coro())
    
    
    asyncio.run(main())
  5. added
    3.12only security fixes
    3.13only security fixes
    3.14bugs and security fixes
    on Jul 27, 2024
  6. added a commit that references this issue on Jul 27, 2024
  7. added 2 commits that reference this issue on Jul 27, 2024
  8. moved this from Todo to Done in asyncioon Jul 27, 2024
  9. added 2 commits that reference this issue on Jul 27, 2024
  10. irgolic commented on Jul 27, 2024

    @irgolic
    Author

    Nice, thanks for the fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixes3.13only security fixes3.14bugs and security fixestopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions