Repository navigation
fix: keep the request container open until a streamed response closes - #44
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On Flask 3.1.2 and later, a
stream_with_contextgenerator gotContainerClosedErrorwhen it resolved a REQUEST dependency. For streamed responses, the request container now closes when the response closes.Cause
Since Flask 3.1.2,
stream_with_contextonly records the request and app contexts when it is created, and pushes them again when the body is iterated (flask/helpers.py,with app_ctx, req_ctx:insidegenerator()).wsgi_apppops the request context right after returning the response. The app context then has one token, soAppContext.poprunsdo_teardown_appcontext(flask/ctx.py) and our close runs before the body is iterated. Iterating pushes the same contexts again, sogand the closed container come back, and the pop at the end runs teardown a second time. On Flask 3.0 the generator pushed the context at creation, so the first pop was a no-op and teardown ran once, after the body.A
teardown_appcontexthandler has no public way to know that a stream will push the context again, so the fix moves the decision to the response.Fix
setup_diconnects aflask.request_finishedreceiver for the app. It gets the final response after allafter_requesthooks and the session save. If the response is streamed (response.is_streamed) and notdirect_passthrough, it registerschild.close_syncwithresponse.call_on_closeand marks the child as deferred ong.call_on_closefunctions fromResponse.close, which the WSGI server calls after sending the body or when the client disconnects. HEAD and 204/304 responses go through that close too.direct_passthroughresponses (for examplesend_file) are handed to the server as the raw iterable, soResponse.closenever runs. Those still close at teardown.request_finishednever fires and teardown closes as before.Plain generator responses without
stream_with_contextare deferred as well, so a dependency resolved in the view stays live while the body streams. Before, it was finalized before the first chunk went out.Public API only:
flask.request_finished,Response.is_streamed,Response.direct_passthrough,Response.call_on_close.Tests
direct_passthroughresponse still finalizes at teardown.Ran locally on Flask 3.1.3, and with the floor constraints on Flask 3.0.0, 3.1.1 and 3.1.2.
Closes #43