I am new to fastapi and relatively new to async, so this might be a little basic. I am trying to get calls to an endpoint to run asynchronously with the following code:
import asyncio
import time
import uvicorn
import fastapi
print(fastapi.__version__)
app = fastapi.FastAPI()
counter = 5
start_time = time.perf_counter()
@app.get("/")
async def read_root():
global counter
counter -= 1
print('to sleep:', counter, 'at', round(time.perf_counter()-start_time, 3))
await asyncio.sleep(counter)
print('wake up :', counter, 'at', round(time.perf_counter()-start_time, 3))
return {"Hello": f"World {counter}"}
if __name__ == '__main__':
asyncio.run(uvicorn.run(app, host="127.0.0.1", port=8000, loop='asyncio', debug=False))
To see the problem, i run this code and open the page in my browser 5 times in quick succession. Because the sleep time decreases with every call, i expect the browser to get the results in a different order and the counter to be printed out of order is well. I have tried a bunch of ways, including creating tasks, but no dice:
INFO: Started server process [13400]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
to sleep: 4 at 1.801
wake up : 4 at 5.802
INFO: 127.0.0.1:63760 - "GET / HTTP/1.1" 200 OK
to sleep: 3 at 5.805
wake up : 3 at 8.804
INFO: 127.0.0.1:63760 - "GET / HTTP/1.1" 200 OK
to sleep: 2 at 8.806
wake up : 2 at 10.815
INFO: 127.0.0.1:63760 - "GET / HTTP/1.1" 200 OK
to sleep: 1 at 10.818
wake up : 1 at 11.791
INFO: 127.0.0.1:63760 - "GET / HTTP/1.1" 200 OK
to sleep: 0 at 11.793
wake up : 0 at 11.793
INFO: 127.0.0.1:63760 - "GET / HTTP/1.1" 200 OK
Spend hours searching and trying to fix this, but no success. Does the endpoint function get scheduled as a task?
On: windows 10, python 3.7, fastapi 0.57.0
I actually don't know the answer- my instinct is something to do with the GIL and executing the same piece of code more than once at the same time.
I _can_ tell you that different endpoints should run at the same time:
import asyncio
import time
import uvicorn
import fastapi
app = fastapi.FastAPI()
start_time = time.perf_counter()
counter = 5
@app.get("/")
async def read_root():
global counter
counter -= 1
print('to sleep:', counter, 'at', round(time.perf_counter()-start_time, 3))
await asyncio.sleep(counter)
print('wake up :', counter, 'at', round(time.perf_counter()-start_time, 3))
return {"Hello": f"World {counter}"}
@app.get("/2")
async def read_root_2():
global counter
counter -= 1
print('to sleep:', counter, 'at', round(time.perf_counter()-start_time, 3))
await asyncio.sleep(counter)
print('wake up :', counter, 'at', round(time.perf_counter()-start_time, 3))
return {"Hello": f"World {counter}"}
if __name__ == '__main__':
uvicorn.run(app)
will get you something like
to sleep: 4 at 1.855
to sleep: 3 at 3.269
wake up : 3 at 5.858
INFO: 127.0.0.1:50684 - "GET / HTTP/1.1" 200 OK
wake up : 3 at 6.269
INFO: 127.0.0.1:50685 - "GET /2 HTTP/1.1" 200 OK
Maybe someone more experienced than me can give the technical explanation.
It does seem to work when i run uvicorn from command line, but not when i run it in a separate process (using multiprocessing).
Should this be considered a bug? (i don't think it's the GIL, and having an IO calling endpoint running synchronously, seems to defy the nature of webservers)
Given that uvicorn worked from the command line, I decided to try out hypercorn to see if there was any different behavior. Seems that running that server programmatically works fine, so it must be a limitation of (or bug with) uvicorn. I'd suggest opening an issue there and linking back to this one so we can see what the solution ends up being.
Here's the hypercorn code looked like it was working as expected:
import asyncio
import time
import fastapi
from hypercorn.config import Config
from hypercorn.asyncio import serve
app = fastapi.FastAPI()
start_time = time.perf_counter()
counter = 5
@app.get("/")
async def read_root():
global counter
counter -= 1
print('to sleep:', counter, 'at', round(time.perf_counter()-start_time, 3))
await asyncio.sleep(counter)
print('wake up :', counter, 'at', round(time.perf_counter()-start_time, 3))
return {"Hello": f"World {counter}"}
if __name__ == '__main__':
asyncio.run(serve(app, Config()))
some webbrowser avoid parralelle request, I think it's your issue
try to change the url by adding a querystring
one tab with <ip>:<port>?anything=foo
a second <ip>:<port>?anything=bar
(I learned it from https://www.tornadoweb.org/en/stable/faq.html#my-code-is-asynchronous-why-is-it-not-running-in-parallel-in-two-browser-tabs)
Thanks for the help here everyone! :clap: :bow:
If that solves the original problem, then you can close this issue @gemerden :heavy_check_mark:
Cheers @flapili: that seems to work. Thanks all!
Still strange that running from command line would not show the same (browser?) behavior ..