DEV Community

Cover image for How FastAPI handle requests
Mahendra Patel
Mahendra Patel

Posted on

How FastAPI handle requests

Introduction

Undoubtedly, πŸ™ƒ FastAPI is one of the popular python framework. In this article we will explore πŸ—ΊοΈ, how FastAPI handle request and some Gotchas. So, what happen when we execute uvicorn main:app, this launches a single Uvicorn worker with a default thread pool of 40 threads. It is tempting to assume this automatically guarantees the ability to handle 40 concurrent requests.

Now, can we say one worker will be able to handle max 40 requests concurrently? πŸ‘ And the answer is, it depends on the application code.

The Mechanism βš™οΈ

To understand performance bottlenecks, you must understand where your code executes:

  1. Async APIs (async def): Run directly on the Event Loop.
  2. Sync APIs (def): Run in the Thread Pool.

The "Gotcha" 😬

Here is the critical architectural dependency: The Event Loop is responsible for scheduling Sync requests onto the Thread Pool.

If your Event Loop is blocked (busy executing a long-running task), it cannot schedule new work. Even if your thread pool is completely empty, your Sync requests will sit in a queue, waiting for the Event Loop to free up and assign them a thread.

The Evidence ✍️

Let's look at a common anti-pattern: running blocking I/O (like requests) inside an async function.

from fastapi import FastAPI
import requests
import time

app = FastAPI()

# Blocking I/O function
def make_request(delay: int):
    url = f"[https://httpbin.org/delay/](https://httpbin.org/delay/){delay}"
    with requests.session() as session:
        resp = session.get(url)
    return resp.json()

# BAD: Running blocking code in the Event Loop
@app.get("/async/{delay}")
async def async_call(delay: int):
    print(f"Async call started at {time.time()}")
    return make_request(delay)

# GOOD: Running blocking code in the Thread Pool
@app.get("/sync/{delay}")
def sync_call(delay: int):
    print(f"Sync call started at {time.time()}")
    return make_request(delay)

Enter fullscreen mode Exit fullscreen mode

The Result πŸ”–: If you trigger the async endpoint followed immediately by the sync endpoint, you will see that the sync request does not even start until the async request finishes.

Async request followed by Sync request - No concurrency

Key Takeaways

  1. async def runs on the main Event Loop.
  2. def runs on the Thread Pool, but relies on the Event Loop for scheduling.
  3. Never run blocking code in async def. It prevents the Event Loop, from accepting new requests.

I hope, πŸ™‚, this article will help you to write better code using FastAPI, and, Happy Coding.

Top comments (0)