StarletteIntegration eagerly drains the ASGI receive channel for request bodies under max_request_body_size, breaking handlers that read the body themselves afterward
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Facilidade para iniciantes
- 48/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Stack de tecnologia
- python
- Domínio
- backend-api-design
Direção de pesquisa
Comece por StarletteIntegration.patch_request_response() e StarletteRequestExtractor.extract_request_info() e, em seguida, inspecione request_body_within_bounds() em _wsgi_common.py para rastrear quando o canal de recebimento ASGI é consumido. Reproduza com o app Starlette fornecido e compare as requisições abaixo e acima de 10.000 bytes; está concluído quando ambas terminarem ao o endpoint ler o body por meio de uma nova StarletteRequest.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Summary
StarletteIntegration.patch_request_response() wraps every Starlette route endpoint. Before the real endpoint runs, StarletteRequestExtractor.extract_request_info() calls await self.request.body() (via .form()) whenever content_length is within max_request_body_size (default "medium" → content_length <= 10_000 bytes, see request_body_within_bounds() in _wsgi_common.py). That fully drains the request's raw ASGI receive() channel, purely to populate breadcrumb/event data.
If the wrapped endpoint later performs its own manual body read against the same receive callable (rather than through Starlette's cached Request.body()/.stream()), that second read hangs forever: the ASGI transport already sent its final http.request message with more_body: False, so there is nothing left to receive, and (on a normal keep-alive connection) no http.disconnect will arrive either. The request just hangs until something upstream times out.
This is exactly what happens with Streamlit's st.file_uploader (streamlit/web/server/starlette/starlette_routes.py, _upload_put): to enforce a streaming size cap without buffering the whole upload in memory, it builds a new StarletteRequest(request.scope, limited_receive) where limited_receive calls await request.receive() directly. Sentry has already exhausted that channel for any upload under ~10,000 bytes, so the second read blocks indefinitely — every small file upload hangs, every upload over ~10KB (where Sentry skips body capture) works fine. Full writeup and a minimal, Sentry-only repro (isolating exactly which integration is responsible) filed against Streamlit: https://github.com/streamlit/streamlit/issues/16697, gist: https://gist.github.com/tbrambor/d41b40950a0061ca404fc0d67e4768ed
This isn't Streamlit-specific. Any ASGI handler that reads its own request body (streaming multipart parsers, chunked uploads, proxies, WebSocket-adjacent code, etc.) rather than going through Starlette's Request.body()/.form()/.stream() cache will break the same way, silently, as a hang rather than an error — for any request whose Content-Length happens to fall under max_request_body_size. The failure mode (indefinite hang, not an exception) makes it especially hard to trace back to Sentry.
How do you use Sentry?
Self-hosted/on-premise, via sentry-sdk in a Starlette/FastAPI-family ASGI app.
Version
sentry-sdk==2.61.1, starlette==1.3.1 (as bundled with streamlit==1.61.1), Python 3.12.
Steps to Reproduce
"""
pip install starlette==1.3.1 sentry-sdk==2.61.1 uvicorn
python repro.py
Then: curl -s -X POST http://localhost:8000/upload -F "file=@small.txt" --max-time 5
(any file under 10000 bytes hangs and times out; a file over 10000 bytes succeeds)
"""
import sentry_sdk
sentry_sdk.init(dsn=None)
from starlette.applications import Starlette
from starlette.requests import Request as StarletteRequest
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def upload(request):
# Mirrors Streamlit's _upload_put: read the body through a fresh
# Request wrapping the same raw ASGI `receive`, instead of the
# cached Request.body()/.form(). This is what breaks once Sentry
# has already drained `receive` for content_length <= 10000 bytes.
limited_request = StarletteRequest(request.scope, request.receive)
form = await limited_request.form()
return PlainTextResponse("ok")
app = Starlette(routes=[Route("/upload", upload, methods=["POST"])])
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
- Run the script above.
curl -X POST http://localhost:8000/upload -F "file=@small.txt"wheresmall.txtis under 10,000 bytes — hangs indefinitely (times out client-side).- Same request with a file over 10,000 bytes — succeeds immediately.
- Comment out
sentry_sdk.init(dsn=None)— both sizes succeed immediately.
Expected Result
The request completes regardless of file size, and regardless of whether the endpoint reads the body itself vs. relying on Sentry having already read it.
Actual Result
Requests with a body at or under max_request_body_size (default 10,000 bytes) hang forever if the endpoint performs its own body/receive read rather than using Starlette's own cached Request.body()/.form(). No exception is raised on either side — it's a silent hang, not an error, which makes it very hard to diagnose from application logs alone.
- Linguagem predominante
- Python
- Estrelas
- 2.2k
- Forks
- 672
- Merge médio
- 23h 14min
- PRs com merge (30d)
- 218
Guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de getsentry/sentry-python
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
getsentry/sentry-python#7543 · 2 comentários · 1 responsável ·
-
Python
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
getsentry/sentry-python#6992 · 1 comentário ·
-
Improvement Python
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 65/100
getsentry/sentry-python#6970 · 1 comentário ·
-
Bug Python
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 74/100
getsentry/sentry-python#6504 · 1 comentário ·
-
Improvement Python Spans
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 85/100
getsentry/sentry-python#5833 · 1 comentário ·
Todas as issues de getsentry/sentry-python
Issues semelhantes
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 86/100
EleutherAI/lm-evaluation-harness#4207 ·
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
ClickHouse/clickhouse-connect#1057 ·
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 70/100
open-telemetry/sig-end-user#406 ·
-
bug ci good first issue
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 88/100