Skip to content

fix(examples): don't await state.startup() in the mount example — it deadlocks - #76

Open
Ang-dot wants to merge 1 commit into
bnb-chain:mainfrom
Ang-dot:fix/agent-server-mount-startup-deadlock
Open

fix(examples): don't await state.startup() in the mount example — it deadlocks#76
Ang-dot wants to merge 1 commit into
bnb-chain:mainfrom
Ang-dot:fix/agent-server-mount-startup-deadlock

Conversation

@Ang-dot

@Ang-dot Ang-dot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

python/examples/agent-server/src/service_mount.py is unrunnable as written. Its parent lifespan awaits erc8183_app.state.startup():

@asynccontextmanager
async def lifespan(app: FastAPI):
    await erc8183_app.state.startup()   # never returns
    yield

state.startup is assigned at erc8183_server.py:470 as:

erc8183_app.state.startup = lambda: _spawn(_funded_poll_loop())

That is a sync lambda returning the poll loop's asyncio.Task. _funded_poll_loop is while True, exiting only when stop_event is set at shutdown. So await startup() awaits a Task that never completes: the lifespan never reaches yield, and uvicorn comes up without ever serving a request.

Fix

Call it without awaiting — returning the Task is the point.

erc8183_app.state.startup()

Plus a comment explaining why it must not be awaited, so it doesn't get "corrected" back.

Tests

service_mount.py had no test coverage, which is why this shipped. Added tests/test_mount_startup.py pinning the three properties that made it a bug:

Test Asserts
test_startup_is_a_sync_callable not a coroutine function — so await applies to the return value
test_startup_returns_a_still_running_task returns an asyncio.Task that is not done
test_awaiting_startup_never_returns awaiting that Task times out

Uses the same monkeypatch(create_erc8183_state) stub convention as test_routes_poll.py, so no chain, wallet, or network access.

Verified with fastapi 0.128.8 on Python 3.11:

before:  9 passed
after:  12 passed

Notes for the maintainer

Two related things I did not change, since they are design calls rather than bugs:

  1. state.startup is only assigned when on_job is passed (erc8183_server.py:469-470). Calling it on an app built without on_job raises AttributeError rather than being a no-op.
  2. A sync callable returning a Task is easy to mis-await — exactly what happened here. An async def startup() that spawns and returns immediately would make the correct usage the natural one.

The same deadlock existed in the published docs (docs.bnbchain.org SDK quickstart, Option 2) and is fixed separately in bnb-chain.github.io#882.

service_mount.py's parent lifespan awaited erc8183_app.state.startup().
state.startup is a sync lambda returning the funded-job poll loop's
asyncio.Task, and that loop only exits on shutdown - so awaiting it never
returns. The lifespan never reached `yield`, so uvicorn started and then
served nothing. The example was unrunnable as written.

Fix is to call it without awaiting, which is what the returned Task is for.

Also adds tests/test_mount_startup.py, pinning the three properties that
made this a bug: startup is a sync callable, it returns a still-running
Task, and awaiting that Task does not return. service_mount.py had no
test coverage at all, which is why this shipped.

Verified with fastapi 0.128.8 on py3.11: 9 passed before, 12 after.
@hashdit-bot

hashdit-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown

Pull Request Review

This PR fixes a FastAPI mount-example startup deadlock by invoking the synchronous erc8183_app.state.startup() callable without awaiting the long-running task it returns. It also adds regression tests confirming that startup is synchronous, returns an active asyncio.Task, and does not complete when awaited.

Sensitive Content

No sensitive content detected.

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant