Fix Python ASGI adaptor to handle streaming responses correctly#6173
Fix Python ASGI adaptor to handle streaming responses correctly#6173ryanking13 wants to merge 2 commits intomainfrom
Conversation
There was a problem hiding this comment.
I'm Bonk, and I've done a quick review of your PR.
This PR fixes the ASGI adaptor so that non-SSE streaming responses (with more_body=True) are no longer silently truncated to their first chunk, by using ctx.waitUntil plus a TransformStream when ctx is available, and falling back to full buffering when it is not.
Issues
-
Dead code — SSE-without-ctx error is unreachable (medium): On line 238,
if is_sse and ctx is Noneis inside theif use_streaming:branch, butuse_streamingisctx is not None(line 122). Soctx is Noneis alwaysFalsehere, making this guard dead code. The backwards-compatibleRuntimeErrorfor SSE-without-ctx that the PR description mentions preserving will never fire — instead the caller silently gets a buffered SSE response. If the intent is to keep that error, the check needs to move before/outside theuse_streamingbranch. -
Typo (low): Line 198 has "inefficident" — should be "inefficient".
| if use_streaming: | ||
| # Let the application continue running in the background to stream | ||
| # the response body via the TransformStream. | ||
| if is_sse and ctx is None: | ||
| raise RuntimeError( | ||
| "Server-Side-Events require ctx to be passed to asgi.fetch" | ||
| ) |
There was a problem hiding this comment.
use_streaming is ctx is not None (line 122), so inside this if use_streaming: branch ctx is guaranteed non-None. The if is_sse and ctx is None guard is dead code and the RuntimeError for SSE-without-ctx will never fire.
If you want to preserve the error for callers who omit ctx and happen to get an SSE response, you could move the check before the branch:
| if use_streaming: | |
| # Let the application continue running in the background to stream | |
| # the response body via the TransformStream. | |
| if is_sse and ctx is None: | |
| raise RuntimeError( | |
| "Server-Side-Events require ctx to be passed to asgi.fetch" | |
| ) | |
| if is_sse and ctx is None: | |
| raise RuntimeError( | |
| "Server-Side-Events require ctx to be passed to asgi.fetch" | |
| ) | |
| if use_streaming: | |
| # Let the application continue running in the background to stream | |
| # the response body via the TransformStream. | |
| ctx.waitUntil(create_proxy(app_task)) |
| finished_response.set() | ||
| # Buffer chunks in Python to avoid TransformStream deadlock | ||
| # TODO(soon): This is inefficident, make `ctx` mandatory and let | ||
| # the runtime handle the streaming. |
There was a problem hiding this comment.
Tiny typo:
| # the runtime handle the streaming. | |
| # TODO(soon): This is inefficient, make `ctx` mandatory and let |
|
I'm Bonk, and I've done a quick review of your PR. I posted a review with 2 findings:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6173 +/- ##
==========================================
- Coverage 70.70% 70.63% -0.07%
==========================================
Files 409 413 +4
Lines 109411 109821 +410
Branches 18018 18097 +79
==========================================
+ Hits 77356 77570 +214
- Misses 21246 21438 +192
- Partials 10809 10813 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| result = Future() | ||
| is_sse = False | ||
| finished_response = Event() | ||
| use_streaming = ctx is not None |
There was a problem hiding this comment.
What's the use case for supporting this when ctx isn't specified? It's a user error to not pass it in, isn't it? Why don't we instead throw an error if it isn't specified?
There was a problem hiding this comment.
Ahh, I see you've got a TODO for this
There was a problem hiding this comment.
It's a user error to not pass it in, isn't it?
Well, I found that we don't document passing ctx anywhere. (1, 2 ). So it is our fault 😂.
I think we should make that parameter as required, but there should be workers in production lying around so we cannot make that change without compat flag.
Actually, after we unvendor the Python SDK from the workerd, it can be done by releasing a new SDK version, but that's not done yet.
There was a problem hiding this comment.
We shipped importable waitUntil for javascript a while back, we should ship it for python as well, similar to import env from workers and then remove the dependency on this being passed in as a parameter
There was a problem hiding this comment.
I see. That sounds more sense. Let me see if I can do that instead of raising error when ctx is not passed.
|
Well, now I feel shitty for only doing this for the SSE branch |
Resolves cloudflare/workers-py#67
Our ASGI adaptor is not handling streaming responses correctly for non-SSE cases. We can basically apply the same thing in SSE and non-SSE (waiting until the stream is all consumed) by calling
ctx.waitUntil. Therefore this PR removes the is_sse branch and usectx.waitUntilfor both SSE and non-SSE cases.However, that can be a breaking change, as
ctxparameter is optional (at least for now), so I added a separate branch to pre-consume all the response inside ASGI when ctx is not available.