Skip to content

Commit 5de1ccb

Browse files
authored
Fix memory leak in exporter and reader (#4224)
1 parent 679297f commit 5de1ccb

File tree

5 files changed

+98
-5
lines changed

5 files changed

+98
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
([#4222](https://github.com/open-telemetry/opentelemetry-python/pull/4222))
2828
- Record logger name as the instrumentation scope name
2929
([#4208](https://github.com/open-telemetry/opentelemetry-python/pull/4208))
30+
- Fix memory leak in exporter and reader
31+
([#4224](https://github.com/open-telemetry/opentelemetry-python/pull/4224))
3032
- Drop `OTEL_PYTHON_EXPERIMENTAL_DISABLE_PROMETHEUS_UNIT_NORMALIZATION` environment variable
3133
([#4217](https://github.com/open-telemetry/opentelemetry-python/pull/4217))
3234

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
import weakref
1515
from atexit import register, unregister
1616
from logging import getLogger
1717
from os import environ
@@ -386,7 +386,7 @@ class MeterProvider(APIMeterProvider):
386386
"""
387387

388388
_all_metric_readers_lock = Lock()
389-
_all_metric_readers = set()
389+
_all_metric_readers = weakref.WeakSet()
390390

391391
def __init__(
392392
self,

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import math
1616
import os
17+
import weakref
1718
from abc import ABC, abstractmethod
1819
from enum import Enum
1920
from logging import getLogger
@@ -488,7 +489,11 @@ def __init__(
488489
)
489490
self._daemon_thread.start()
490491
if hasattr(os, "register_at_fork"):
491-
os.register_at_fork(after_in_child=self._at_fork_reinit) # pylint: disable=protected-access
492+
weak_at_fork = weakref.WeakMethod(self._at_fork_reinit)
493+
494+
os.register_at_fork(
495+
after_in_child=lambda: weak_at_fork()() # pylint: disable=unnecessary-lambda, protected-access
496+
)
492497
elif self._export_interval_millis <= 0:
493498
raise ValueError(
494499
f"interval value {self._export_interval_millis} is invalid \
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import gc
16+
import time
17+
import weakref
18+
from typing import Sequence
19+
from unittest import TestCase
20+
21+
from opentelemetry.sdk.metrics import MeterProvider
22+
from opentelemetry.sdk.metrics.export import (
23+
Metric,
24+
MetricExporter,
25+
MetricExportResult,
26+
PeriodicExportingMetricReader,
27+
)
28+
29+
30+
class FakeMetricsExporter(MetricExporter):
31+
def __init__(
32+
self, wait=0, preferred_temporality=None, preferred_aggregation=None
33+
):
34+
self.wait = wait
35+
self.metrics = []
36+
self._shutdown = False
37+
super().__init__(
38+
preferred_temporality=preferred_temporality,
39+
preferred_aggregation=preferred_aggregation,
40+
)
41+
42+
def export(
43+
self,
44+
metrics_data: Sequence[Metric],
45+
timeout_millis: float = 10_000,
46+
**kwargs,
47+
) -> MetricExportResult:
48+
time.sleep(self.wait)
49+
self.metrics.extend(metrics_data)
50+
return True
51+
52+
def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
53+
self._shutdown = True
54+
55+
def force_flush(self, timeout_millis: float = 10_000) -> bool:
56+
return True
57+
58+
59+
class TestMeterProviderShutdown(TestCase):
60+
def test_meter_provider_shutdown_cleans_up_successfully(self):
61+
def create_and_shutdown():
62+
exporter = FakeMetricsExporter()
63+
exporter_wr = weakref.ref(exporter)
64+
65+
reader = PeriodicExportingMetricReader(exporter)
66+
reader_wr = weakref.ref(reader)
67+
68+
provider = MeterProvider(metric_readers=[reader])
69+
provider_wr = weakref.ref(provider)
70+
71+
provider.shutdown()
72+
73+
return exporter_wr, reader_wr, provider_wr
74+
75+
# When: the provider is shutdown
76+
(
77+
exporter_weakref,
78+
reader_weakref,
79+
provider_weakref,
80+
) = create_and_shutdown()
81+
gc.collect()
82+
83+
# Then: the provider, exporter and reader should be garbage collected
84+
self.assertIsNone(exporter_weakref())
85+
self.assertIsNone(reader_weakref())
86+
self.assertIsNone(provider_weakref())

opentelemetry-sdk/tests/metrics/test_metrics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# pylint: disable=protected-access,no-self-use
1616

17-
17+
import weakref
1818
from logging import WARNING
1919
from time import sleep
2020
from typing import Iterable, Sequence
@@ -66,7 +66,7 @@ def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
6666

6767
class TestMeterProvider(ConcurrencyTestBase, TestCase):
6868
def tearDown(self):
69-
MeterProvider._all_metric_readers = set()
69+
MeterProvider._all_metric_readers = weakref.WeakSet()
7070

7171
@patch.object(Resource, "create")
7272
def test_init_default(self, resource_patch):

0 commit comments

Comments
 (0)