Skip to content

fix(runtime-sdk): chain the app's exception onto a reported lifespan failure - #237

Open
whitphx wants to merge 1 commit into
cloudflare:mainfrom
whitphx:fix/asgi-lifespan-chain-cause
Open

fix(runtime-sdk): chain the app's exception onto a reported lifespan failure#237
whitphx wants to merge 1 commit into
cloudflare:mainfrom
whitphx:fix/asgi-lifespan-chain-cause

Conversation

@whitphx

@whitphx whitphx commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

When an app reports lifespan.startup.failed, the caller gets a RuntimeError carrying only the message text, so the app's own exception object reaches nobody. This attaches it as the reported error's __cause__, on the startup and shutdown paths alike.

Test Plan

$ uv run pytest 'tests/test_in_workerd.py::test_in_workerd[asgi-3.13]' -v

(run from packages/runtime-sdk)

@whitphx
whitphx marked this pull request as draft August 29, 2026 12:49
…failure

A reported startup or shutdown failure surfaced as a bare RuntimeError
carrying only the message text, so the app's own exception reached
callers nowhere. Frameworks re-raise it right after sending the failed
event, and in that same-tick case the awaiter has not resumed, so
attaching it there delivers it as the reported error's cause.
@whitphx
whitphx force-pushed the fix/asgi-lifespan-chain-cause branch from 1d73c14 to f7783dd Compare August 30, 2026 02:56
@whitphx
whitphx marked this pull request as ready for review August 30, 2026 05:15
@ryanking13

Copy link
Copy Markdown
Contributor

Sounds reasonable to me, but let me check if this behavior matches with other packages

@ryanking13

Copy link
Copy Markdown
Contributor

@ask-bonk review

# and the attachment still reaches it as the cause:
# https://github.com/encode/starlette/blob/1.3.1/starlette/routing.py
for reported in (startup, shutdown_complete):
if reported.done() and not reported.cancelled():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future.done() remains true after await startup or await shutdown_complete has already resumed. If an app yields after sending lifespan.*.failed and then raises, the caller can observe the RuntimeError with no cause before this code retroactively mutates it; this branch also returns instead of logging that later exception, which regresses the previous behavior. Please limit attachment to the same event-loop turn as the failed event, or otherwise fall through to the existing logging path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future.done() remains true after await startup or await shutdown_complete has already resumed. If an app yields after sending lifespan.*.failed and then raises, the caller can observe the RuntimeError with no cause before this code retroactively mutates it;

Not sure this matters.

this branch also returns instead of logging that later exception, which regresses the previous behavior.

This part makes sense to me.

@ask-bonk

ask-bonk Bot commented Sep 8, 2026

Copy link
Copy Markdown

Submitted 1 actionable inline finding.

github run

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.

2 participants