FastAPI application processing new requests before sending back previous request's response #9875
-
First Check
Commit to Help
Example Codefrom time import sleep
from anyio import CapacityLimiter
from anyio.lowlevel import RunVar
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def ping() -> str:
print("start ping")
sleep(5)
print("finish ping")
return "pong!"
@app.on_event("startup")
def startup():
print("startup")
RunVar("_default_thread_limiter").set(CapacityLimiter(1))DescriptionI'm playing around with FastAPI to understand better how sync and async endpoints work. I've read about Uvicorn's event loop, about the ThreadPool used to handle sync endpoints, and various other related topics. I came across this method for changing the ThreadPool size, and when running some "experiments" I found some unexpected behavior. When changing the ThreadPool size to 1 thread, I see that only one request is processed concurrently (expected), BUT the corresponding response is not delivered to the client right away. What do I mean by this? Check these logs (they were produced by triggering 3 simultaneous requests to the same endpoint): Note: it's not only about the logs, I can also see in my client (e.g. browser, Postman, etc.) that the first response is not received until all requests are processed. Consistent behavior is seen when the ThreadPool size is increased to 2 or 3 threads. For instance, these are the logs when running the experiment with 2 threads and making 3 simultaneous requests: In this last example you can see how both the first and second request are handled immediately. But when the first request processing finishes, the response is not sent right away. Instead, the handling of the third request starts. Only when the second request processing finishes, both the first and second response are sent back to the client. I haven't been able to find the piece of code that's in charge of this logic. But from the outside it seems that Uvicorn, in order to send the response back to the client, requires a new thread (not the same in which the response was "calculated"). If so, maybe this issue is not related to FastAPI, but to Uvicorn, and apologies for wasting your time (although I'm more than happy to see what you folks think about this behavior). Let me know if I can provide any further details that could be useful! Operating SystemLinux Operating System DetailsRunning on Docker container (shouldn't be related to this though). FastAPI Version0.95.1 Python Version3.11 Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
|
Calling one
Explaining your first example:
|
Beta Was this translation helpful? Give feedback.
Calling one
GET /makes tworun_in_threadpool()calls:ping()in your example)Explaining your first example:
GET /-> run_in_threadpool(ping) -> sleep(5)GET /-> run_in_threadpool(ping) (waiting)GET /-> run_in_threadpool(ping) (waiting)