0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

refactor: fix cleanup bug for BECConnector items, renamed _registry_state to _server_registry

This commit is contained in:
2025-03-24 08:06:42 +01:00
committed by wyzula-jan
parent 1ea225d2fd
commit 6b9d1d5f2f
3 changed files with 31 additions and 18 deletions

View File

@ -207,7 +207,7 @@ class BECGuiClient(RPCBase):
self._process = None self._process = None
self._process_output_processing_thread = None self._process_output_processing_thread = None
self._exposed_widgets = [] self._exposed_widgets = []
self._registry_state = {} self._server_registry = {}
self._ipython_registry = {} self._ipython_registry = {}
self.available_widgets = AvailableWidgetsNamespace() self.available_widgets = AvailableWidgetsNamespace()
@ -329,7 +329,7 @@ class BECGuiClient(RPCBase):
) )
# Remove all reference from top level # Remove all reference from top level
self._top_level.clear() self._top_level.clear()
self._registry_state.clear() self._server_registry.clear()
def close(self): def close(self):
"""Deprecated. Use kill_server() instead.""" """Deprecated. Use kill_server() instead."""
@ -353,7 +353,7 @@ class BECGuiClient(RPCBase):
# Wait for 'bec' gui to be registered, this may take some time # Wait for 'bec' gui to be registered, this may take some time
# After 60s timeout. Should this raise an exception on timeout? # After 60s timeout. Should this raise an exception on timeout?
while time.time() < time.time() + timeout: while time.time() < time.time() + timeout:
if len(list(self._registry_state.keys())) == 0: if len(list(self._server_registry.keys())) == 0:
time.sleep(0.1) time.sleep(0.1)
else: else:
break break
@ -403,9 +403,10 @@ class BECGuiClient(RPCBase):
return self._start_server(wait=wait) return self._start_server(wait=wait)
def _handle_registry_update(self, msg: StreamMessage) -> None: def _handle_registry_update(self, msg: StreamMessage) -> None:
with self._lock: # This was causing a deadlock during shutdown, not sure why.
self._registry_state = msg["data"].state # with self._lock:
self._update_dynamic_namespace() self._server_registry = msg["data"].state
self._update_dynamic_namespace()
def _do_show_all(self): def _do_show_all(self):
rpc_client = RPCBase(gui_id=f"{self._gui_id}:window", parent=self) rpc_client = RPCBase(gui_id=f"{self._gui_id}:window", parent=self)
@ -437,12 +438,13 @@ class BECGuiClient(RPCBase):
def _cleanup_ipython_registry(self): def _cleanup_ipython_registry(self):
"""Cleanup the ipython registry""" """Cleanup the ipython registry"""
names_in_registry = list(self._ipython_registry.keys()) names_in_registry = list(self._ipython_registry.keys())
remove_ids = list(set(names_in_registry) - set(self._exposed_widgets)) names_in_server_state = list(self._server_registry.keys())
remove_ids = list(set(names_in_registry) - set(names_in_server_state))
for widget_id in remove_ids: for widget_id in remove_ids:
self._ipython_registry.pop(widget_id) self._ipython_registry.pop(widget_id)
self._cleanup_rpc_references_on_rpc_base(remove_ids) self._cleanup_rpc_references_on_rpc_base(remove_ids)
# Clear the exposed widgets # Clear the exposed widgets
self._exposed_widgets.clear() self._exposed_widgets.clear() # No longer needed I think
def _cleanup_rpc_references_on_rpc_base(self, remove_ids: list[str]) -> None: def _cleanup_rpc_references_on_rpc_base(self, remove_ids: list[str]) -> None:
"""Cleanup the rpc references on the RPCBase object""" """Cleanup the rpc references on the RPCBase object"""
@ -473,7 +475,7 @@ class BECGuiClient(RPCBase):
# Add dock areas # Add dock areas
dock_area_states = [ dock_area_states = [
state state
for state in self._registry_state.values() for state in self._server_registry.values()
if state["widget_class"] == "BECDockArea" if state["widget_class"] == "BECDockArea"
] ]
for state in dock_area_states: for state in dock_area_states:
@ -491,7 +493,7 @@ class BECGuiClient(RPCBase):
# Add docks # Add docks
dock_states = [ dock_states = [
state state
for state in self._registry_state.values() for state in self._server_registry.values()
if state["config"].get("parent_id", "") == dock_area_ref._gui_id if state["config"].get("parent_id", "") == dock_area_ref._gui_id
] ]
for state in dock_states: for state in dock_states:
@ -506,7 +508,7 @@ class BECGuiClient(RPCBase):
# Add widgets # Add widgets
widget_states = [ widget_states = [
state state
for state in self._registry_state.values() for state in self._server_registry.values()
if state["config"].get("parent_id", "") == dock_ref._gui_id if state["config"].get("parent_id", "") == dock_ref._gui_id
] ]
for state in widget_states: for state in widget_states:
@ -527,7 +529,7 @@ class BECGuiClient(RPCBase):
"""Add a widget to the namespace """Add a widget to the namespace
Args: Args:
state (dict): The state of the widget from the _registry_state. state (dict): The state of the widget from the _server_registry.
parent (object): The parent object. parent (object): The parent object.
""" """
name = state["name"] name = state["name"]

View File

@ -19,8 +19,9 @@ from bec_widgets.qt_utils.error_popups import SafeSlot as pyqtSlot
from bec_widgets.utils.container_utils import WidgetContainerUtils from bec_widgets.utils.container_utils import WidgetContainerUtils
from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
if TYPE_CHECKING: if TYPE_CHECKING: # pragma: no cover
from bec_widgets.utils.bec_dispatcher import BECDispatcher from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.widgets.containers.dock import BECDock
logger = bec_logger.logger logger = bec_logger.logger
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",)) BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
@ -82,11 +83,13 @@ class BECConnector:
config: ConnectionConfig | None = None, config: ConnectionConfig | None = None,
gui_id: str | None = None, gui_id: str | None = None,
name: str | None = None, name: str | None = None,
parent_dock: BECDock | None = None,
parent_id: str | None = None, parent_id: str | None = None,
): ):
# BEC related connections # BEC related connections
self.bec_dispatcher = BECDispatcher(client=client) self.bec_dispatcher = BECDispatcher(client=client)
self.client = self.bec_dispatcher.client if client is None else client self.client = self.bec_dispatcher.client if client is None else client
self._parent_dock = parent_dock
if not self.client in BECConnector.EXIT_HANDLERS: if not self.client in BECConnector.EXIT_HANDLERS:
# register function to clean connections at exit; # register function to clean connections at exit;
@ -312,10 +315,14 @@ class BECConnector:
def remove(self): def remove(self):
"""Cleanup the BECConnector""" """Cleanup the BECConnector"""
if hasattr(self, "close"): # If the widget is attached to a dock, remove it from the dock.
if self._parent_dock is not None:
self._parent_dock.delete(self._name)
# If the widget is from Qt, trigger its close method.
elif hasattr(self, "close"):
self.close() self.close()
if hasattr(self, "deleteLater"): # If the widget is neither from a Dock nor from Qt, remove it from the RPC registry.
self.deleteLater() # i.e. Curve Item from Waveform
else: else:
self.rpc_register.remove_rpc(self) self.rpc_register.remove_rpc(self)

View File

@ -58,9 +58,13 @@ class BECWidget(BECConnector):
raise RuntimeError(f"{repr(self)} is not a subclass of QWidget") raise RuntimeError(f"{repr(self)} is not a subclass of QWidget")
super().__init__( super().__init__(
client=client, config=config, gui_id=gui_id, name=name, parent_id=parent_id client=client,
config=config,
gui_id=gui_id,
name=name,
parent_dock=parent_dock,
parent_id=parent_id,
) )
self._parent_dock = parent_dock
app = QApplication.instance() app = QApplication.instance()
if not hasattr(app, "theme"): if not hasattr(app, "theme"):
# DO NOT SET THE THEME TO AUTO! Otherwise, the qwebengineview will segfault # DO NOT SET THE THEME TO AUTO! Otherwise, the qwebengineview will segfault