Skip to content

Commit 1f5b5c9

Browse files
committed
test(fastapi): add GC-based app collection test; refactor tracking to WeakSet and safe discard
1 parent 7218064 commit 1f5b5c9

File tree

3 files changed

+21
-164
lines changed

3 files changed

+21
-164
lines changed

instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,8 @@ def uninstrument_app(app: fastapi.FastAPI):
359359
app._is_instrumented_by_opentelemetry = False
360360

361361
# Remove the app from the set of instrumented apps to prevent memory leaks
362-
if app in _InstrumentedFastAPI._instrumented_fastapi_apps:
363-
_InstrumentedFastAPI._instrumented_fastapi_apps.remove(app)
362+
# Use discard to avoid KeyError if already GC'ed
363+
_InstrumentedFastAPI._instrumented_fastapi_apps.discard(app)
364364

365365
def instrumentation_dependencies(self) -> Collection[str]:
366366
return _instruments
@@ -414,7 +414,10 @@ class _InstrumentedFastAPI(fastapi.FastAPI):
414414
_http_capture_headers_sanitize_fields: list[str] | None = None
415415
_exclude_spans: list[Literal["receive", "send"]] | None = None
416416

417-
_instrumented_fastapi_apps = set()
417+
# Track instrumented app instances using weak references to avoid GC leaks
418+
from weakref import WeakSet as _WeakSet # type: ignore
419+
420+
_instrumented_fastapi_apps: "_InstrumentedFastAPI._WeakSet" = _WeakSet()
418421
_sem_conv_opt_in_mode = _StabilityMode.DEFAULT
419422

420423
def __init__(self, *args, **kwargs):
@@ -435,8 +438,8 @@ def __init__(self, *args, **kwargs):
435438
_InstrumentedFastAPI._instrumented_fastapi_apps.add(self)
436439

437440
def __del__(self):
438-
if self in _InstrumentedFastAPI._instrumented_fastapi_apps:
439-
_InstrumentedFastAPI._instrumented_fastapi_apps.remove(self)
441+
# Best-effort cleanup; WeakSet clears references on GC automatically
442+
_InstrumentedFastAPI._instrumented_fastapi_apps.discard(self)
440443

441444

442445
def _get_route_details(scope):

instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,19 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self):
14071407
)
14081408

14091409

1410+
class TestFastAPIGarbageCollection(unittest.TestCase):
1411+
def test_fastapi_app_is_collected_after_instrument(self):
1412+
import gc
1413+
import weakref
1414+
1415+
app = fastapi.FastAPI()
1416+
otel_fastapi.FastAPIInstrumentor().instrument_app(app)
1417+
app_ref = weakref.ref(app)
1418+
del app
1419+
gc.collect()
1420+
self.assertIsNone(app_ref())
1421+
1422+
14101423
@patch.dict(
14111424
"os.environ",
14121425
{

instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_memory_leak.py

Lines changed: 0 additions & 159 deletions
This file was deleted.

0 commit comments

Comments
 (0)