Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 27 additions & 4 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"""
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -1430,7 +1453,7 @@ def get_tracer(
schema_url,
attributes,
),
meter_provider=self._meter_provider,
tracer_metrics=self._get_tracer_metrics(),
_tracer_provider=self,
)

Expand Down