mirror of https://github.com/tiangolo/fastapi.git
Fix _endpoint_context_cache using id() which risks stale entries and memory leaks
The _endpoint_context_cache in routing.py used id(func) as dict keys to cache endpoint context (source file, line number, function name) for error messages. This has two problems: 1. ID reuse after garbage collection. Python id() returns the memory address of an object. Once an object is garbage collected, its ID can be reassigned to a newly created object. If an endpoint function were deallocated and a new function reused the same ID, the cache would return stale context (wrong file, line, function name) for error messages. 2. No eviction. The module-level dict grows unboundedly for every unique endpoint function. In scenarios where FastAPI instances are dynamically created and destroyed in a single process (e.g., test suites running hundreds of apps), endpoint functions and their closures are kept alive indefinitely by the cache, leaking memory. Fix: replace the plain dict with a weakref.WeakKeyDictionary keyed on the function object itself. This solves both problems: keys are looked up by object identity (no ID collision), and entries are automatically evicted when the endpoint function is garbage collected, allowing the function and its closure to be reclaimed. Endpoint functions always support weak references (regular def / async def), so WeakKeyDictionary is safe for this use case.
This commit is contained in:
parent
ed12105cce
commit
d762eb6405
|
|
@ -4,6 +4,7 @@ import functools
|
|||
import inspect
|
||||
import json
|
||||
import types
|
||||
import weakref
|
||||
from collections.abc import (
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
|
|
@ -232,16 +233,19 @@ class _DefaultLifespan:
|
|||
return self
|
||||
|
||||
|
||||
# Cache for endpoint context to avoid re-extracting on every request
|
||||
_endpoint_context_cache: dict[int, EndpointContext] = {}
|
||||
# Cache for endpoint context to avoid re-extracting on every request.
|
||||
# Uses WeakKeyDictionary so entries are automatically evicted when the endpoint
|
||||
# function is garbage collected (e.g., when a dynamically created FastAPI app is
|
||||
# destroyed), preventing both memory leaks and stale-ID lookups.
|
||||
_endpoint_context_cache: weakref.WeakKeyDictionary[Callable[..., Any], EndpointContext] = (
|
||||
weakref.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]
|
||||
if func in _endpoint_context_cache:
|
||||
return _endpoint_context_cache[func]
|
||||
|
||||
try:
|
||||
ctx: EndpointContext = {}
|
||||
|
|
@ -255,7 +259,7 @@ def _extract_endpoint_context(func: Any) -> EndpointContext:
|
|||
except Exception:
|
||||
ctx = EndpointContext()
|
||||
|
||||
_endpoint_context_cache[func_id] = ctx
|
||||
_endpoint_context_cache[func] = ctx
|
||||
return ctx
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue