1

I create simple app using (python 3.11, uvicorn 0.38.0, fastapi 0.120.2):

import os
import time
import asyncio


import uvicorn
from fastapi import FastAPI
from fastapi import BackgroundTasks

app = FastAPI()


async def background_task():
    print(os.getpid())
    print(id(asyncio.get_running_loop()))
    print(id(asyncio.current_task()))
    time.sleep(10)


@app.get("/background_task")
async def get(background_tasks: BackgroundTasks):
    print(os.getpid())
    print(id(asyncio.get_running_loop()))
    print(id(asyncio.current_task()))
    background_tasks.add_task(background_task)
    return {200: "ok"}


if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        workers=1,
        port=8000,
        reload=True
    )

But it behaves strange:

  1. reload = True or workers > 1: I immediately get response in my browser and then background task blocks eventloop.
  2. reload = False and workers = 1: I will wait for response in my browser until background task complete execution.

Seems like reload flag and workers count shouldn't change app behavior so as I understand both setups should return response and then start background tasks or vise versa firstly run background tasks which in this case implies eventloop blocking and then return response but how it behaves look strange for me. I add logs to verify that in all cases both view and background task run in the same eventloop, use the same task and run in same the process.

Log example(same for all cases):

8420 
1825609933264 
1825610022336 
INFO:     127.0.0.1:53184 - "GET /background_task HTTP/1.1" 200 OK 
8420 
1825609933264 
1825610022336

Even though logs are the same, response is not returned under conditions described above. Is it intended behavior or bug, if intended can someone explain me how and why it works in this way?

3
  • 2
    stackoverflow.com/questions/71369164/… Commented Nov 1 at 21:25
  • the premise here is somewhat wrong - async task order is not guaranteed and you should always avoid blocking the loop by wrapping whatever time.sleep() represents in a task in your real case stackoverflow.com/questions/41063331/… Commented Nov 1 at 22:54
  • You might find this answer helpful. Commented Nov 7 at 6:59

1 Answer 1

1

From uvicorn documentation:

Note
The --reload and --workers arguments are mutually exclusive. You cannot use both at the same time.

But I think your problem is that you use:

time.sleep(10)

instead of

await asyncio.sleep(10)

So you have a single worker running async loop and you block that loop with plain sleep function.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this clarification, but could you help me understand why anyway if there is single worker I will wait for background task but if there are multiple workers then get response immediately, I mean looks like both view and background task(regardless of workers count) run in the same task, use the same eventloop and run in same process, so why workers count affect this if another worker is not involved in this process?
OK, your question is not about reload and workers but workers=1 vs workers>1. I have a hypothesis why it's blocking but answer to this is buried deep inside starlette code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.