|
|
|
@ -1,5 +1,6 @@ |
|
|
|
import sys |
|
|
|
import threading |
|
|
|
from typing import Any |
|
|
|
|
|
|
|
from fastapi import APIRouter, FastAPI |
|
|
|
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) |
|
|
|
) |
|
|
|
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 |
|
|
|
|