From 6b50eaf04d666b39ae63780d8efe8005a5b82a62 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Wed, 20 May 2026 16:13:22 +0500 Subject: [PATCH] Close tracing exporter during `BatchTraceProcessor.shutdown()` ### Summary This PR ensures that `BatchTraceProcessor.shutdown()` closes the tracing exporter after the final batch has been flushed. ### Problem `BackendSpanExporter` creates a persistent `httpx.Client` for trace ingestion. Before this change, shutdown drained the queue but did not explicitly close the exporter, which left the HTTP client and connection pool open. That can leave resources allocated longer than necessary, especially in: * long-running services * tests that repeatedly create and tear down tracing components * worker processes or environments with frequent startup and shutdown cycles ### Change After the final synchronous flush, the shutdown path now looks up `close()` on the exporter and calls it when available: ```python close_fn = getattr(self._exporter, "close", None) if callable(close_fn): try: close_fn() except Exception as exc: logger.warning( "[non-fatal] Tracing: exporter close failed: %s", exc, ) ``` ### Behavior * preserves the existing final flush behavior * closes exporter resources after export completes * keeps cleanup non-fatal * remains compatible with custom exporters that do not implement `close()` ### Notes This change assumes the exporter is safe to close at processor shutdown time. If exporter ownership is later shared more broadly, that lifecycle may need to be made explicit. --- src/agents/tracing/processors.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/agents/tracing/processors.py b/src/agents/tracing/processors.py index 6711bc92de..403918ce64 100644 --- a/src/agents/tracing/processors.py +++ b/src/agents/tracing/processors.py @@ -625,6 +625,17 @@ def shutdown(self, timeout: float | None = None): # No background thread: process any remaining items synchronously. self._export_batches(force=True, deadline=deadline) + # Close the exporter so the underlying HTTP client and connection pool are released. + close_fn = getattr(self._exporter, "close", None) + if callable(close_fn): + try: + close_fn() + except Exception as exc: + logger.warning( + "[non-fatal] Tracing: exporter close failed: %s", + exc, + ) + def force_flush(self): """ Forces an immediate flush of all queued spans.