From be79af007a2c776689f91993811cc4a3b7b78238 Mon Sep 17 00:00:00 2001 From: Sagar Bhandari <071bct532.sagar@pcampus.edu.np> Date: Sat, 20 Dec 2025 10:06:32 -0500 Subject: [PATCH] Use weak refs for endpoint context cache --- fastapi/routing.py | 18 +++++++++----- tests/test_routing_endpoint_context_cache.py | 25 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 tests/test_routing_endpoint_context_cache.py diff --git a/fastapi/routing.py b/fastapi/routing.py index fa6904a6b6..a957dcb1a4 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -76,6 +76,7 @@ from starlette.routing import Mount as Mount # noqa from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send from starlette.websockets import WebSocket from typing_extensions import deprecated +from weakref import WeakKeyDictionary # Copy of starlette.routing.request_response modified to include the @@ -212,15 +213,17 @@ def _merge_lifespan_context( # Cache for endpoint context to avoid re-extracting on every request -_endpoint_context_cache: dict[int, EndpointContext] = {} +_endpoint_context_cache: WeakKeyDictionary[Any, EndpointContext] = WeakKeyDictionary() def _extract_endpoint_context(func: Any) -> EndpointContext: """Extract endpoint context with caching to avoid repeated file I/O.""" - func_id = id(func) - - if func_id in _endpoint_context_cache: - return _endpoint_context_cache[func_id] + try: + cached = _endpoint_context_cache.get(func) + except TypeError: + cached = None + if cached is not None: + return cached try: ctx: EndpointContext = {} @@ -234,7 +237,10 @@ def _extract_endpoint_context(func: Any) -> EndpointContext: except Exception: ctx = EndpointContext() - _endpoint_context_cache[func_id] = ctx + try: + _endpoint_context_cache[func] = ctx + except TypeError: + pass return ctx diff --git a/tests/test_routing_endpoint_context_cache.py b/tests/test_routing_endpoint_context_cache.py new file mode 100644 index 0000000000..30435fda8e --- /dev/null +++ b/tests/test_routing_endpoint_context_cache.py @@ -0,0 +1,25 @@ +import gc +import weakref + +from fastapi.routing import _endpoint_context_cache, _extract_endpoint_context + + +def _make_endpoint(): + def endpoint(): + return None + + return endpoint + + +def test_endpoint_context_cache_releases_endpoints(): + endpoint = _make_endpoint() + _extract_endpoint_context(endpoint) + assert endpoint in _endpoint_context_cache + + ref = weakref.ref(endpoint) + size_with_endpoint = len(_endpoint_context_cache) + del endpoint + gc.collect() + + assert ref() is None + assert len(_endpoint_context_cache) <= size_with_endpoint - 1