Open ac000 opened 9 months ago
Thanks to @bunny-therapist for providing a reproducer, summarised below...
gh1100.py
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from fastapi.responses import Response
class RequestBody(BaseModel):
number: int
app = FastAPI()
@app.middleware("http")
async def my_middleware(request, call_next):
response = await call_next(request)
return response
@app.post("/predict")
async def post_predict(body: RequestBody):
return JSONResponse(content={"got": body.number})
@app.get("/data")
async def get_data():
return Response()
gh1100.json
{
"listeners": {
"[::1]:8080": {
"pass": "applications/fastapi"
}
},
"applications": {
"fastapi": {
"type": "python",
"path": "/home/andrew/src/python",
"module": "gh1100",
"callable": "app",
"protocol": "asgi"
}
}
}
gh1100-client.py
#!/usr/bin/python
import requests
N = 2
with requests.Session() as session:
for _ in range(N):
r = session.post("http://localhost:8080/predict", json={"number": 1})
r.raise_for_status()
Running it produces the following in the unit log
2024/01/30 17:01:14 [error] 43868#43868 [unit] #17: Python failed to call 'future.result()'
+ Exception Group Traceback (most recent call last):
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 192, in __call__
| await response(scope, wrapped_receive, send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 254, in __call__
| async with anyio.create_task_group() as task_group:
| File "/home/andrew/.local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/andrew/.local/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
| await super().__call__(scope, receive, send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 189, in __call__
| with collapse_excgroups():
| File "/usr/lib64/python3.12/contextlib.py", line 158, in __exit__
| self.gen.throw(value)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/_utils.py", line 91, in collapse_excgroups
| raise exc
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 261, in __call__
| await wrap(partial(self.listen_for_disconnect, receive))
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 257, in wrap
| await func()
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 234, in listen_for_disconnect
| message = await receive()
| ^^^^^^^^^^^^^^^
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 56, in wrapped_receive
| raise RuntimeError(f"Unexpected message received: {msg['type']}")
| RuntimeError: Unexpected message received: http.request
+------------------------------------
During handling of the above exception, another exception occurred:
+ Exception Group Traceback (most recent call last):
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| yield
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 190, in __call__
| async with anyio.create_task_group() as task_group:
| File "/home/andrew/.local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 192, in __call__
| await response(scope, wrapped_receive, send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 254, in __call__
| async with anyio.create_task_group() as task_group:
| File "/home/andrew/.local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/andrew/.local/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
| await super().__call__(scope, receive, send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 189, in __call__
| with collapse_excgroups():
| File "/usr/lib64/python3.12/contextlib.py", line 158, in __exit__
| self.gen.throw(value)
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/_utils.py", line 91, in collapse_excgroups
| raise exc
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 261, in __call__
| await wrap(partial(self.listen_for_disconnect, receive))
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 257, in wrap
| await func()
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 234, in listen_for_disconnect
| message = await receive()
| ^^^^^^^^^^^^^^^
| File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 56, in wrapped_receive
| raise RuntimeError(f"Unexpected message received: {msg['type']}")
| RuntimeError: Unexpected message received: http.request
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/andrew/.local/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 189, in __call__
with collapse_excgroups():
File "/usr/lib64/python3.12/contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/_utils.py", line 91, in collapse_excgroups
raise exc
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 261, in __call__
await wrap(partial(self.listen_for_disconnect, receive))
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 257, in wrap
await func()
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/responses.py", line 234, in listen_for_disconnect
message = await receive()
^^^^^^^^^^^^^^^
File "/home/andrew/.local/lib/python3.12/site-packages/starlette/middleware/base.py", line 56, in wrapped_receive
raise RuntimeError(f"Unexpected message received: {msg['type']}")
RuntimeError: Unexpected message received: http.request
This was with Fedora 39 with Python 3.12.1 and fastapi 0.109.0 installed via pip(1)
Looking through git-log(1) these look potentially interesting to look at
commit 567545213d95e608b54ce92bfc33fac4327a9f93
Author: Max Romanov <max.romanov@gmail.com>
Date: Tue Jul 20 10:37:50 2021 +0300
Python: fixing ASGI receive() issues.
The receive() call never blocks for a GET request and always returns the sam
e
empty body message. The Starlette framework creates a separate task when
receive() is called in a loop until an 'http.disconnect' message is received
.
The 'http.disconnect' message was previously issued after the response heade
r
had been sent. However, the correct behavior is to respond with
'http.disconnect' after sending the response is complete.
This closes #564 issue on GitHub.
commit dfbdc1c11a201e46d61f4bc61cfbe5741fc4fd70
Author: Max Romanov <max.romanov@gmail.com>
Date: Tue Jul 20 10:37:53 2021 +0300
Python: fixing exceptions in Future.set_result for ASGI implementation.
An ASGI application can cancel the Future object returned by the receive()
call. In this case, Unit's ASGI implementation should not call set_result()
because the Future is already handled. In particular, the Starlette framewo
rk
was noted to cancel the received Future.
This patch adds a done() check for the Future before attempting a set_result
().
This is related to #564 issue on GitHub.
Tomorrow, I plan to test out specific starlette commits to pinpoint when exactly the problems appeared.
(You can pip install specific commits from github, see, e.g., https://stackoverflow.com/questions/13685920/install-specific-git-commit-with-pip)
Thanks, that would be helpful...
I can confirm that the RuntimeError: Unexpected message received
error for POST requests started with starlette commit https://github.com/encode/starlette/commit/554b9e21f6161a6d83b4ebb90909282114266317
If I have that commit, I get the error. If i go one commit back in time, the error goes away.
As for the "invalid state" error for the GET requests, they start with starlette commit https://github.com/encode/starlette/commit/11a3f1227e69a29fc9dc56f348f982cb5372a1d1
Thanks! I'll take a look...
@ac000 bumping on this one, any updates? thanks!
Not yet I'm afraid, I'm sure any updates will be posted here, but I don't think there is anyone actively working on this currently, so far it has really just been an information gathering exercise.
Hi I am facing the exact same issue. Is there any progress, or has anyone found some workaround for it?
I am not following this very closely anymore because I switched from FastAPI to litestar, which works great. Litestar even recommends NGINX Unit in their docs and it is very actively maintained.
With FastAPI, the only way I found around it was to use an older version, fastapi==0.107.0 starlette==0.28.0 (Then again, I have not tried the latest version of FastAPI/starlette because I stopped using it.)
Discussed in https://github.com/nginx/unit/discussions/1100