pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
56 lines
1.6 KiB
56 lines
1.6 KiB
import asyncio
|
|
import threading
|
|
import time
|
|
from collections.abc import Iterator
|
|
|
|
from fastapi import Depends, FastAPI
|
|
from httpx import ASGITransport, AsyncClient
|
|
from pydantic import BaseModel
|
|
|
|
# Mutex, and dependency acting as our "connection pool" for a database for example
|
|
mutex = threading.Lock()
|
|
|
|
|
|
# Simulate releaasing a pooled resource in the teardown of a Depends,
|
|
# which in reality is usually a database connection or similar.
|
|
def release_resource() -> Iterator[None]:
|
|
try:
|
|
time.sleep(0.001)
|
|
yield
|
|
finally:
|
|
time.sleep(0.001)
|
|
mutex.release()
|
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
class Item(BaseModel):
|
|
name: str
|
|
id: int
|
|
|
|
|
|
# An endpoint that uses Depends for resource management and also includes
|
|
# a response_model definition would previously deadlock in the validation
|
|
# of the model and the cleanup of the Depends
|
|
@app.get("/deadlock", response_model=Item)
|
|
def get_deadlock(dep: None = Depends(release_resource)) -> Item:
|
|
mutex.acquire()
|
|
return Item(name="foo", id=1)
|
|
|
|
|
|
# Fire off 100 requests in parallel(ish) in order to create contention
|
|
# over the shared resource (simulating a fastapi server that interacts with
|
|
# a database connection pool).
|
|
def test_depends_deadlock() -> None:
|
|
async def make_request(client: AsyncClient):
|
|
await client.get("/deadlock")
|
|
|
|
async def run_requests() -> None:
|
|
async with AsyncClient(
|
|
transport=ASGITransport(app=app), base_url="http://testserver"
|
|
) as aclient:
|
|
tasks = [make_request(aclient) for _ in range(100)]
|
|
await asyncio.gather(*tasks)
|
|
|
|
asyncio.run(run_requests())
|
|
|