Skip to content

Commit 72464d4

Browse files
fix(metrics): use weakref in fork handler to allow GC after provider shutdown
os.register_at_fork holds callbacks for the lifetime of the process with no unregister API. Passing a bound method created a permanent strong reference chain: fork callback → SynchronousMeasurementConsumer → _reader_storages → MetricReader → exporter, preventing garbage collection even after MeterProvider.shutdown(). Replace the bound method with a weakref-based closure so the consumer and everything it owns can be collected normally. The fork handler is a no-op once the consumer is gone. Fixes test_meter_provider_shutdown_cleans_up_successfully.
1 parent ce9c6ec commit 72464d4

2 files changed

Lines changed: 13 additions & 3 deletions

File tree

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# pylint: disable=unused-import
55

66
import os
7+
import weakref
78
from abc import ABC, abstractmethod
89
from collections.abc import Mapping
910
from threading import Lock
@@ -68,7 +69,14 @@ def __init__(
6869
] = []
6970
self._needs_storage_reinit = False
7071
if hasattr(os, "register_at_fork"):
71-
os.register_at_fork(after_in_child=self._at_fork_reinit)
72+
weak_self = weakref.ref(self)
73+
74+
def _fork_handler():
75+
obj = weak_self()
76+
if obj is not None:
77+
obj._at_fork_reinit() # pylint: disable=protected-access
78+
79+
os.register_at_fork(after_in_child=_fork_handler)
7280

7381
def _at_fork_reinit(self):
7482
"""Reinitialize lock in child process after fork"""

opentelemetry-sdk/tests/metrics/test_measurement_consumer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,10 @@ def test_collect_deadline(
208208
"opentelemetry.sdk.metrics._internal."
209209
"measurement_consumer.MetricReaderStorage"
210210
)
211-
class TestSynchronousMeasurementConsumerForkHandler(TestCase): # pylint: disable=protected-access
212-
"""Exhaustive tests for fork handler, needs_storage_reinit, and lazy _reinit_storages."""
211+
class TestSynchronousMeasurementConsumerForkHandler(TestCase):
212+
"""Exhaustive tests for fork handler, needs_storage_reinit, and lazy _reinit_storages."""
213+
214+
# pylint: disable=protected-access
213215

214216
def test_register_at_fork_called_when_available(
215217
self, MockMetricReaderStorage

0 commit comments

Comments
 (0)