From 314e89ba38fb8599c2626851f452d7615d4280c1 Mon Sep 17 00:00:00 2001 From: Martin Stadler Date: Mon, 20 May 2024 17:25:10 +0200 Subject: [PATCH] Use weak references in dict/list mappping to avoid memory leak --- .../observable/observable_object.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pydase/observer_pattern/observable/observable_object.py b/src/pydase/observer_pattern/observable/observable_object.py index 82f0e70..72aa1d1 100644 --- a/src/pydase/observer_pattern/observable/observable_object.py +++ b/src/pydase/observer_pattern/observable/observable_object.py @@ -4,6 +4,7 @@ from collections.abc import Iterable from typing import TYPE_CHECKING, Any, ClassVar, SupportsIndex from pydase.utils.helpers import parse_serialized_key +import weakref if TYPE_CHECKING: from pydase.observer_pattern.observer.observer import Observer @@ -88,19 +89,17 @@ class ObservableObject(ABC): if isinstance(value, list): if id(value) in self._list_mapping: # If the list `value` was already referenced somewhere else - new_value = self._list_mapping[id(value)] + new_value = self._list_mapping[id(value)]() else: # convert the builtin list into a ObservableList new_value = _ObservableList(original_list=value) - self._list_mapping[id(value)] = new_value elif isinstance(value, dict): if id(value) in self._dict_mapping: # If the dict `value` was already referenced somewhere else - new_value = self._dict_mapping[id(value)] + new_value = self._dict_mapping[id(value)]() else: - # convert the builtin list into a ObservableList + # convert the builtin dict into a ObservableDict new_value = _ObservableDict(original_dict=value) - self._dict_mapping[id(value)] = new_value if isinstance(new_value, ObservableObject): new_value.add_observer(self, attr_name_or_key) return new_value @@ -138,6 +137,10 @@ class _ObservableList(ObservableObject, list[Any]): list.__init__(self, self._original_list) for i, item in enumerate(self._original_list): super().__setitem__(i, self._initialise_new_objects(f"[{i}]", item)) + self._list_mapping[id(self._original_list)] = weakref.ref(self) + + def __del__(self): + self._list_mapping.pop(id(self._original_list)) def __setitem__(self, key: int, value: Any) -> None: # type: ignore[override] if hasattr(self, "_observers"): @@ -236,6 +239,10 @@ class _ObservableDict(ObservableObject, dict[str, Any]): dict.__init__(self) for key, value in self._original_dict.items(): self.__setitem__(key, self._initialise_new_objects(f'["{key}"]', value)) + self._dict_mapping[id(self._original_dict)] = weakref.ref(self) + + def __del__(self): + self._dict_mapping.pop(id(self._original_dict)) def __setitem__(self, key: str, value: Any) -> None: if not isinstance(key, str):