13

I'm probably not understanding the asynchronous concept correctly in FastAPI.

I'm accessing the root endpoint of the following app from two clients at the same time. I'd expect FastAPI to print Started twice in a row at the start of the execution:

from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get("/")
async def read_root():
    print('Started')
    await asyncio.sleep(5)
    print('Finished')
    return {"Hello": "World"}

Instead I get the following, which looks very much non asynchronous:

Started
Finished
INFO: ('127.0.0.1', 49655) - "GET / HTTP/1.1" 200
Started
Finished
INFO: ('127.0.0.1', 49655) - "GET / HTTP/1.1" 200

What am I missing?

Florentin Hennecker
  • 1,974
  • 23
  • 37
  • " I'd expect FastAPI to print Started twice". It is printed twice! – TomTom101 May 08 '21 at 13:03
  • Good point, I edited the question to make it clearer – Florentin Hennecker May 08 '21 at 15:09
  • you think they are supposed to run on separate threads right but its not , imagine why ? You havent defined any asgi server , to maintain seprate threads , you shouuld use either uvicorn and for bigger app combine it with gunicorn – dev May 24 '23 at 09:18

5 Answers5

11

How do you ensure there are multiple simultaneous requests?

Your code is fine - try using following command for testing:

for n in {1..5}; do curl http://localhost:8000/ & ; done

Your browser might be caching subsequent requests to the same URL.

  • Ok I don't really understand why it would do it. FastApi is supposed to run every request in a separate Thread when using normal `def` functions. However from Chrome when open two tabs to run the same url it waits for one to finish and then executes another. It does not do that if I open two endpoints in two chrome tabs – Nilan Saha Jun 11 '21 at 02:49
  • @nilan-saha you need gunicorn/uvirocn – dev May 24 '23 at 09:20
4

Well from what the demos in this github issue show, it's probably not due to FastAPI but the client that runs the requests.

Florentin Hennecker
  • 1,974
  • 23
  • 37
3

I did the same experiment with Chrome browser and the result was the same as what was originally reported. The requests from two separate Chrome browsers were processed one after another (as if in serial).

@app.get("/test")
async def test():
    r = {"message": "Hello by /test api"}
    r['timestamp'] = datetime.datetime.utcnow()
    await asyncio.sleep(10)
    return r

2 requests took 20 seconds (10 secs each) to finish whole process and this is obviously not a concurrent way!

However, when I tried with curl as suggested in the answer, it was processed in parallel (!)

I did the last experiment with 2 Firefox browsers and the result was also parallel execution.

And finally, I was able to find the clue from the logs of FastAPI. When I try with 2 Chrome browsers, the source of the request (ip:port) were recorded identical

INFO:     10.10.62.106:54668 - "GET /test HTTP/1.1" 200 OK
INFO:     10.10.62.106:54668 - "GET /test HTTP/1.1" 200 OK

However, if I try with Firefox, the source were different.

INFO:     10.10.62.106:54746 - "GET /test HTTP/1.1" 200 OK
INFO:     10.10.62.106:54748 - "GET /test HTTP/1.1" 200 OK

From the log above, I can conclude that FastAPI (or uvicorn in front) handles requests only in parallel when the source address is different.

Please someone add comments on the above conclusion. Thanks.

Jamie Cha
  • 79
  • 1
0

If I understand correctly, You tried to combine the print executing from different requests. You should probably implement print("started") as a coroutine and use it this way:

async def started():
    print("Started")

@app.get("/")
async def read_root():
    await started()
    await asyncio.sleep(5)
    print('Finished')
    return {"Hello": "World"}

But It won't work as you want as well. You can see truly async if You use difficult requests with DB connection, authentication, and other computation, especially with requests to third-party API.

Good luck )

0

When you declare a path operation function with normal def instead of async def, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).

You can find more details in this article, where I explained where to use async in FastAPI.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-ask). – Community Sep 14 '21 at 17:58
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/29824765) – Brody Critchlow Sep 14 '21 at 19:37