Skip to content

"RuntimeError: cannot reuse already awaited coroutine" since 08bc03ff (under PYTHONASYNCIODEBUG) #143939

Description

@vytas7

Bug report

Bug description:

When running Falcon's CI on 3.15, we started experiencing a strange error that I cannot really understand.

See, e.g., https://github.com/falconry/falcon/actions/runs/21010040119/job/60402427099:

<...>
        resp_event_collector = helpers.ASGIResponseEventCollector()
    
        if not _one_shot:
            task_req = asyncio.create_task(
                app(http_scope, req_event_emitter, resp_event_collector)
            )
    
            if _stream_result:
                # NOTE(kgriffs): Wait until the response has been started and give
                #   the task a chance to progress. Otherwise, we won't have a
                #   status or headers to pass to StreamedResult.
                while not resp_event_collector.status:
                    await asyncio.sleep(0)
    
                return StreamedResult(
                    resp_event_collector.body_chunks,
                    code_to_http_status(resp_event_collector.status),
                    resp_event_collector.headers,
                    task_req,
                    req_event_emitter,
                )
    
            req_event_emitter.disconnect()
>           await task_req
E           RuntimeError: cannot reuse already awaited coroutine

task_req is created above using asyncio.create_task(...), and normally it can be awaited as many times as desired.

Printing it just before the failure gives:

<Task pending name='Task-3' coro=<App.__call__() running at /tmp/falcon/falcon/asgi/app.py:459> created at /usr/local/lib/python3.15/asyncio/tasks.py:395>

Interestingly, the error is only raised when running under PYTHONASYNCIODEBUG=1. Otherwise it passes just fine.

I was unable to isolate an independent MRE... Using Falcon's source tree @ falconry/falcon@6432f40, the following is enough to reproduce, given a venv using CPython dev version:

FALCON_DISABLE_CYTHON=1 pip install -e . pytest
# Works fine without asyncio debug: pytest tests/asgi/test_sse.py
PYTHONASYNCIODEBUG=1 pytest tests/asgi/test_sse.py

I ran a similar script under git bisect (first compiling the whole CPython source tree from scratch in a container), and bisected it down to this commit:

commit 08bc03f
Author: Sam Gross colesbury@gmail.com
Date: Fri Dec 19 14:10:37 2025 -0500

cc @colesbury @hugovk

CPython versions tested on:

3.15, CPython main branch

Operating systems tested on:

Linux

Other

I'm not sure whether this is a bug in CPython, or just sloppy code on our end...
But I find the error itself, and the fact that it only happens under PYTHONASYNCIODEBUG=1, puzzling.

Thanks in advance for any ideas!

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Jan 16, 2026
  2. changed the title [-]Puzzling "RuntimeError: cannot reuse already awaited coroutine" since 08bc03ff (under PYTHONASYNCIODEBUG)[/-] [+]"RuntimeError: cannot reuse already awaited coroutine" since 08bc03ff (under PYTHONASYNCIODEBUG)[/+] on Jan 16, 2026
  3. johnslavik commented on Jan 17, 2026

    @johnslavik
    Member

    Smaller repro, still with Falcon:

    import asyncio
    from falcon import testing
    from falcon.asgi import App
    
    async def test_multiple_events() -> None:
        class SomeResource:
            async def on_get(self, _, resp):
                async def emitter():
                    yield
                    await asyncio.sleep(0)
                    yield
    
                resp.sse = emitter()
    
        app = App()
        app.add_route("/", SomeResource())
    
        async with testing.TestClient(app) as tc:
            await tc.simulate_get()
    
    asyncio.run(test_multiple_events())

    I've observed that changing the emitter to yield only once or to not sleep avoids the issue.
    Maybe some task step interleaved with emitter() causes the failure.

    Perhaps this could be helpful (this is right before the failing await):

    ❯ python -m asyncio pstree 705111
    └── (T) Task-1
        └──  test_multiple_events /home/bswck/Python/cpython/main/falc/falcon/../../t.py:20
    └── (T) Task-2
        └──  App.__call__ /home/bswck/Python/cpython/main/falc/falcon/falcon/asgi/app.py:499
            └──  App._call_lifespan_handlers /home/bswck/Python/cpython/main/falc/falcon/falcon/asgi/app.py:1127
                └──  ASGILifespanEventEmitter.emit /home/bswck/Python/cpython/main/falc/falcon/falcon/testing/helpers.py:106
                    └──  Condition.wait /home/bswck/Python/cpython/main/Lib/asyncio/locks.py:272
    └── (T) Task-3
        └──  App.__call__ /home/bswck/Python/cpython/main/falc/falcon/falcon/asgi/app.py:459
    
  4. colesbury commented on Jan 17, 2026

    @colesbury
    Contributor

    Thanks for the bug report. Is this using Cython and if so, is there a way to run it without Cython?

  5. colesbury commented on Jan 17, 2026

    @colesbury
    Contributor

    Never mind -- I understand the bug now and will have a fix shortly.

  6. added 2 commits that reference this issue on Jan 17, 2026
  7. colesbury commented on Jan 20, 2026

    @colesbury
    Contributor

    This is fixed in main now. Thanks again for the bug report!

  8. moved this from Todo to Done in asyncioon Jan 20, 2026
  9. added a commit that references this issue on Feb 15, 2026
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

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions