Skip to content

Commit 77d7faa

Browse files
committed
Fix memory leaks with weakref module
1 parent 2bcbbcc commit 77d7faa

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from threading import Lock
1919
from time import time_ns
2020
from typing import Optional, Sequence
21+
import weakref
2122

2223
# This kind of import is needed to avoid Sphinx errors.
2324
import opentelemetry.sdk.metrics
@@ -393,7 +394,7 @@ class MeterProvider(APIMeterProvider):
393394
"""
394395

395396
_all_metric_readers_lock = Lock()
396-
_all_metric_readers = set()
397+
_all_metric_readers = weakref.WeakSet()
397398

398399
def __init__(
399400
self,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from threading import Event, Lock, RLock, Thread
2323
from time import time_ns
2424
from typing import IO, Callable, Dict, Iterable, Optional
25+
import weakref
2526

2627
from typing_extensions import final
2728

@@ -491,7 +492,7 @@ def __init__(
491492
self._daemon_thread.start()
492493
if hasattr(os, "register_at_fork"):
493494
os.register_at_fork(
494-
after_in_child=self._at_fork_reinit
495+
after_in_child=weakref.WeakMethod(self._at_fork_reinit)
495496
) # pylint: disable=protected-access
496497
elif self._export_interval_millis <= 0:
497498
raise ValueError(

reproduce.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import gc
2+
import logging
3+
import weakref
4+
5+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
6+
OTLPMetricExporter,
7+
)
8+
from opentelemetry.sdk.metrics import MeterProvider
9+
from opentelemetry.sdk.metrics._internal.export import (
10+
PeriodicExportingMetricReader,
11+
)
12+
13+
logging.basicConfig(level=logging.DEBUG)
14+
15+
16+
def create_and_clean():
17+
# setup_otlp_exporter
18+
otlp_exporter = OTLPMetricExporter(
19+
endpoint="http://localhost:4318/v1/metrics"
20+
)
21+
otlp_exporter_weakref = weakref.ref(otlp_exporter)
22+
23+
reader = PeriodicExportingMetricReader(
24+
otlp_exporter, export_interval_millis=5000
25+
)
26+
reader_weakref = weakref.ref(reader)
27+
28+
provider = MeterProvider(metric_readers=[reader])
29+
provider_weakref = weakref.ref(provider)
30+
31+
provider.shutdown()
32+
return otlp_exporter_weakref, reader_weakref, provider_weakref
33+
34+
35+
def check_referrers(wr, name):
36+
if wr() is not None:
37+
logging.warning("%s was not properly garbage collected", name)
38+
referrers = gc.get_referrers(wr())
39+
logging.debug(f"Direct referrers to %s: {len(referrers)}", name)
40+
for ref in referrers:
41+
logging.debug(f"%s referrer {str(ref)} type: {type(ref)}", name)
42+
else:
43+
logging.info("%s was properly garbage collected", name)
44+
45+
46+
def main():
47+
otlp_exporter_weakref, reader_weakref, provider_weakref = (
48+
create_and_clean()
49+
)
50+
gc.collect()
51+
52+
check_referrers(otlp_exporter_weakref, "OTLP EXPORTER")
53+
check_referrers(reader_weakref, "READER")
54+
check_referrers(provider_weakref, "PROVIDER")
55+
56+
57+
if __name__ == "__main__":
58+
main()

0 commit comments

Comments
 (0)