Browse Source

Cover double-checked rebuild paths deterministically

pull/15975/head
uditDewan 1 week ago
parent
commit
22518a8442
  1. 64
      tests/test_included_router_concurrent_rebuild.py

64
tests/test_included_router_concurrent_rebuild.py

@ -1,5 +1,6 @@
import sys import sys
import threading import threading
from typing import Any
from fastapi import APIRouter, FastAPI from fastapi import APIRouter, FastAPI
from fastapi.routing import _IncludedRouter from fastapi.routing import _IncludedRouter
@ -48,3 +49,66 @@ def test_concurrent_first_requests_do_not_corrupt_candidate_cache() -> None:
route for route in app.router.routes if isinstance(route, _IncludedRouter) route for route in app.router.routes if isinstance(route, _IncludedRouter)
) )
assert len(included.effective_candidates()) == N_ROUTES assert len(included.effective_candidates()) == N_ROUTES
def get_included_router(app: FastAPI) -> _IncludedRouter:
return next(
route for route in app.router.routes if isinstance(route, _IncludedRouter)
)
class StampCacheOnEnter:
"""Lock stand-in simulating another thread finishing the rebuild first.
Entering the lock stamps the cache and its version, the same state a
waiting thread observes after losing the rebuild race.
"""
def __init__(
self,
included: _IncludedRouter,
list_attr: str,
version_attr: str,
cache: list,
) -> None:
self.included = included
self.list_attr = list_attr
self.version_attr = version_attr
self.cache = cache
def __enter__(self) -> None:
version = self.included.original_router._get_routes_version()
setattr(self.included, self.list_attr, self.cache)
setattr(self.included, self.version_attr, version)
def __exit__(self, *args: Any) -> None:
return None
def test_candidates_rebuilt_while_waiting_for_lock_are_reused() -> None:
app = FastAPI()
router = APIRouter()
router.add_api_route("/a", lambda: {}, methods=["GET"])
app.include_router(router)
included = get_included_router(app)
cache: list = []
included._rebuild_lock = StampCacheOnEnter( # type: ignore[assignment]
included, "_effective_candidates", "_effective_candidates_version", cache
)
assert included.effective_candidates() is cache
def test_low_priority_routes_rebuilt_while_waiting_for_lock_are_reused() -> None:
app = FastAPI()
router = APIRouter()
router.add_api_route("/a", lambda: {}, methods=["GET"])
app.include_router(router)
included = get_included_router(app)
cache: list = []
included._rebuild_lock = StampCacheOnEnter( # type: ignore[assignment]
included,
"_effective_low_priority_routes",
"_effective_low_priority_routes_version",
cache,
)
assert included.effective_low_priority_routes() is cache

Loading…
Cancel
Save