From 534392e71f120dab1680b28357b957a8500114c4 Mon Sep 17 00:00:00 2001 From: Ravi Theja Date: Wed, 1 Apr 2026 03:57:27 +0530 Subject: [PATCH 1/2] fix(sdk/trace): cache TracerMetrics on TracerProvider to prevent memory leak Fixes #5016 --- CHANGELOG.md | 5 +++ .../src/opentelemetry/sdk/trace/__init__.py | 31 ++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a35f9d763..2a3e10f70a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-sdk`: Fix memory leak in `TracerProvider.get_tracer()` where a new + `TracerMetrics` instance was created on every call, causing `ProxyMeterProvider` to + accumulate proxy meters indefinitely when no SDK `MeterProvider` was configured. + ([#5016](https://github.com/open-telemetry/opentelemetry-python/issues/5016)) + - `opentelemetry-sdk`: Add `create_resource` and `create_propagator`/`configure_propagator` to declarative file configuration, enabling Resource and propagator instantiation from config files without reading env vars ([#4979](https://github.com/open-telemetry/opentelemetry-python/pull/4979)) - `opentelemetry-sdk`: Map Python `CRITICAL` log level to OTel `FATAL` severity text per the specification diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index e0b639d81c..950a2ee5b4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -1120,6 +1120,7 @@ def __init__( instrumentation_scope: InstrumentationScope, *, meter_provider: Optional[metrics_api.MeterProvider] = None, + tracer_metrics: Optional["TracerMetrics"] = None, _tracer_provider: Optional["TracerProvider"] = None, ) -> None: self.sampler = sampler @@ -1130,9 +1131,11 @@ def __init__( self._span_limits = span_limits self._instrumentation_scope = instrumentation_scope self._tracer_provider = _tracer_provider - - meter_provider = meter_provider or metrics_api.get_meter_provider() - self._tracer_metrics = TracerMetrics(meter_provider) + if tracer_metrics is not None: + self._tracer_metrics = tracer_metrics + else: + meter_provider = meter_provider or metrics_api.get_meter_provider() + self._tracer_metrics = TracerMetrics(meter_provider) def _is_enabled(self) -> bool: """If the tracer is not enabled, start_span will create a NonRecordingSpan""" @@ -1365,6 +1368,8 @@ def __init__( self._tracer_configurator = ( _tracer_configurator or _default_tracer_configurator ) + self._tracer_metrics: Optional[TracerMetrics] = None + self._tracer_metrics_lock = threading.Lock() def _set_tracer_configurator( self, *, tracer_configurator: _TracerConfiguratorT @@ -1387,6 +1392,24 @@ def _set_tracer_configurator( def resource(self) -> Resource: return self._resource + def _get_tracer_metrics(self) -> TracerMetrics: + """Return a single cached TracerMetrics instance for this provider. + + Creating a new TracerMetrics on every get_tracer() call causes + ProxyMeterProvider to accumulate proxy meters indefinitely when no + SDK MeterProvider is configured, leading to unbounded memory growth. + + See: https://github.com/open-telemetry/opentelemetry-python/issues/5016 + """ + if self._tracer_metrics is None: + with self._tracer_metrics_lock: + if self._tracer_metrics is None: + self._tracer_metrics = TracerMetrics( + self._meter_provider + or metrics_api.get_meter_provider() + ) + return self._tracer_metrics + def get_tracer( self, instrumenting_module_name: str, @@ -1430,7 +1453,7 @@ def get_tracer( schema_url, attributes, ), - meter_provider=self._meter_provider, + tracer_metrics=self._get_tracer_metrics(), _tracer_provider=self, ) From 352b0050ac58c19113df2187e010b97910fb479a Mon Sep 17 00:00:00 2001 From: Ravi Theja Date: Wed, 1 Apr 2026 04:05:18 +0530 Subject: [PATCH 2/2] fix(sdk/trace): fixed memory leak issue.__init__ --- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 950a2ee5b4..cffb3c3b54 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -1134,8 +1134,8 @@ def __init__( if tracer_metrics is not None: self._tracer_metrics = tracer_metrics else: - meter_provider = meter_provider or metrics_api.get_meter_provider() - self._tracer_metrics = TracerMetrics(meter_provider) + meter_provider = meter_provider or metrics_api.get_meter_provider() + self._tracer_metrics = TracerMetrics(meter_provider) def _is_enabled(self) -> bool: """If the tracer is not enabled, start_span will create a NonRecordingSpan"""