Skip to content

docs: explain that Litestar closes the request container before a stream body runs - #710

Merged
lesnik512 merged 1 commit into
mainfrom
docs/litestar-streaming
Oct 17, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
docs/litestar-streaming

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Litestar runs dependency cleanup when the handler returns and before it sends the response (litestar/routes/http.py, the AsyncExitStack around the handler call). The per-connection child container is a yield dependency, so it closes at that point. A Stream or ServerSentEvent body therefore runs after REQUEST-scoped instances have been finalized.

To reproduce: a REQUEST-scoped cached factory with a finalizer, injected with FromDI and read inside a Stream generator, reports closed=True. The same setup on modern-di-starlette reports False, because its middleware keeps the child open until the response is sent.

This adds a "Streaming responses" section to the Litestar page with the workaround: open a REQUEST child of the root container inside the generator. The example runs under test-docs-integrations.

Changing the plugin to keep the container open through the stream was considered and rejected. It would need ASGI middleware, which costs about 1.5 us on every request, and finalizers would run after the client already has the response. A session that commits in its finalizer could then send a 200 for a commit that later fails.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Benchmark

Details
Benchmark suite Current: 254d0f6 Previous: 6a53052 Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 5132419.502616191 iter/sec (stddev: 7.67501088587994e-9) 3344547.4895807165 iter/sec (stddev: 1.0870968546091025e-8) 0.65
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 5134562.506515359 iter/sec (stddev: 7.221167444517781e-9) 3286913.2436675453 iter/sec (stddev: 2.013901363759457e-8) 0.64
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 23504.02948561845 iter/sec (stddev: 0.00003968158606298378) 15583.560938229652 iter/sec (stddev: 0.00005589262781052146) 0.66
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 18671.26927616026 iter/sec (stddev: 0.000127336494181869) 12100.100648398899 iter/sec (stddev: 0.0001730919170493535) 0.65
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 665.0660898460719 iter/sec (stddev: 0.00006898430678332252) 476.83130255084984 iter/sec (stddev: 0.00010632423506027354) 0.72
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 655.5714495211673 iter/sec (stddev: 0.000016949703549031357) 467.6270143742508 iter/sec (stddev: 0.00004875162860410878) 0.71
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 622.6571342825177 iter/sec (stddev: 0.00003092679358617282) 435.9540204410922 iter/sec (stddev: 0.00012794436313057943) 0.70
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 20051.539139400258 iter/sec (stddev: 0.000006748585080532474) 13159.210658012596 iter/sec (stddev: 0.000009360155545735502) 0.66
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 10187.254471243265 iter/sec (stddev: 0.000015732430592774775) 6507.355046483238 iter/sec (stddev: 0.000024518660851694014) 0.64
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 4935.6938728169725 iter/sec (stddev: 0.000025374712786500923) 3250.3015805847417 iter/sec (stddev: 0.00006717328500240406) 0.66
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] 15050.892712293216 iter/sec (stddev: 0.00000769301414293898) 7913.7564102246715 iter/sec (stddev: 0.000024317565710078463) 0.53
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] 6253.911626293615 iter/sec (stddev: 0.000028423941658151908) 4339.452829715919 iter/sec (stddev: 0.00002768221050812109) 0.69
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] 2983.61095046204 iter/sec (stddev: 0.0000253519066247147) 1760.8092374067414 iter/sec (stddev: 0.00020176978829819614) 0.59
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[1] 51851.71102160885 iter/sec (stddev: 0.000002341077981011172) 41207.2346166668 iter/sec (stddev: 0.000004021744808405116) 0.79
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[2] 19348.179191230836 iter/sec (stddev: 0.000010129486393952364) 13054.760695882296 iter/sec (stddev: 0.00001502767377752435) 0.67
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[4] 8487.884746969681 iter/sec (stddev: 0.00002362266721104138) 4482.600673431932 iter/sec (stddev: 0.00024437065037094226) 0.53
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 2142239.7952493345 iter/sec (stddev: 1.9391568934619136e-8) 1545345.33870841 iter/sec (stddev: 1.0247001917072238e-7) 0.72
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 2345035.097555192 iter/sec (stddev: 3.525753184315508e-8) 1675301.379386119 iter/sec (stddev: 2.659498828881931e-8) 0.71
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 4225.667026267729 iter/sec (stddev: 0.000009359315851851865) 3055.630518743245 iter/sec (stddev: 0.000016647056391313933) 0.72
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 74986.32854227745 iter/sec (stddev: 0.0000010295775052242614) 58397.77008535097 iter/sec (stddev: 0.0000020675509459501996) 0.78
benchmarks/test_guard_lifecycle.py::test_g7b_request_cycle_sync 570984.3619090661 iter/sec (stddev: 8.510901962037905e-8) 420635.01627977827 iter/sec (stddev: 8.226526259246025e-8) 0.74
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 90815.74856863629 iter/sec (stddev: 0.0000010261939382970725) 65491.09743504281 iter/sec (stddev: 0.0000017185360029134222) 0.72
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers 840.6606421742341 iter/sec (stddev: 0.0008649221635591658) 596.4248751282753 iter/sec (stddev: 0.0008959028010212933) 0.71
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 2961259.6902627666 iter/sec (stddev: 2.4820650212542287e-8) 2162029.5187302334 iter/sec (stddev: 3.466425150134868e-8) 0.73
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 5025979.539544054 iter/sec (stddev: 4.979058201015785e-9) 3692378.767749514 iter/sec (stddev: 8.219648124299563e-9) 0.73
benchmarks/test_guard_resolve.py::test_g3_deep_chain 1163000.6125255304 iter/sec (stddev: 2.9994145371197816e-8) 742119.3511222858 iter/sec (stddev: 1.6980225347240625e-7) 0.64
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 627872.9504098769 iter/sec (stddev: 3.3230728462983807e-7) 474631.2785390143 iter/sec (stddev: 4.1950679955608254e-7) 0.76
benchmarks/test_guard_resolve.py::test_g5_cross_scope 2719844.892679623 iter/sec (stddev: 2.3298274465621177e-8) 1999587.6850176 iter/sec (stddev: 2.4759969215001987e-8) 0.74
benchmarks/test_guard_resolve.py::test_g9_context_resolve 2074364.5158457402 iter/sec (stddev: 1.2599748948578426e-7) 1367324.472797383 iter/sec (stddev: 1.4520775370835358e-7) 0.66
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 1196684.2750771658 iter/sec (stddev: 2.7888897842068113e-8) 840914.775683592 iter/sec (stddev: 5.011210136758502e-8) 0.70
benchmarks/test_guard_resolve.py::test_g18_alias_hop 5004567.418467996 iter/sec (stddev: 5.431670102295949e-9) 3521490.5652616406 iter/sec (stddev: 2.0083148671261934e-8) 0.70
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 53014.04364994183 iter/sec (stddev: 0.000014960723607901764) 29494.93115836232 iter/sec (stddev: 0.0002111487546893482) 0.56
benchmarks/test_guard_validate.py::test_g11_validate_wide 31109.16040545038 iter/sec (stddev: 0.00001813703380700472) 20084.07004272152 iter/sec (stddev: 0.00002804624717005632) 0.65

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit 739cabe into main Oct 17, 2026
12 checks passed
@lesnik512
lesnik512 deleted the docs/litestar-streaming branch October 17, 2026 14:58
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