From 0f1ca84df585ecb911aa45d49e59ed3ded3ab492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Mon, 23 Jun 2025 09:08:50 +0200 Subject: [PATCH] client: updates proxy.serialize logic The proxy needs to properly handle serialization requests. If such a requests comes from the asyncio loop used by the socketio client, this would result in a deadlock. This happens, for example, when the observer is notified of a change triggered within a socketio event. To prevent this, I am checking the current loop against the socketio client loop. If it's the same, return the _service_representation value, which is set when pydase.Client connects to the server. I do the same when the client is not connected (to prevent BadNamespaceErrors). Every other invokation of serialize results in an API call to the server. --- src/pydase/client/proxy_class.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/pydase/client/proxy_class.py b/src/pydase/client/proxy_class.py index f1c8c62..51ae799 100644 --- a/src/pydase/client/proxy_class.py +++ b/src/pydase/client/proxy_class.py @@ -65,19 +65,31 @@ class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection): self.reconnect = reconnect def serialize(self) -> SerializedObject: - if self._service_representation is None: - serialization_future = cast( + current_loop = asyncio.get_event_loop() + + if not self.connected or current_loop == self._loop: + logger.debug( + "Client not connected, or called from within client event loop - using " + "fallback serialization" + ) + if self._service_representation is None: + serialized_service = pydase.components.DeviceConnection().serialize() + else: + serialized_service = self._service_representation + + else: + future = cast( "asyncio.Future[SerializedDataService]", asyncio.run_coroutine_threadsafe( self._sio.call("service_serialization"), self._loop ), ) + result = future.result() # need to use object.__setattr__ to not trigger an observer notification - object.__setattr__( - self, "_service_representation", serialization_future.result() - ) + object.__setattr__(self, "_service_representation", result) if TYPE_CHECKING: - self._service_representation = serialization_future.result() + self._service_representation = result + serialized_service = result device_connection_value = cast( "dict[str, SerializedObject]", @@ -93,7 +105,7 @@ class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection): "dict[str, SerializedObject]", # need to deepcopy to not overwrite the _service_representation dict # when adding a prefix with add_prefix_to_full_access_path - deepcopy(self._service_representation["value"]), + deepcopy(serialized_service["value"]), ), **device_connection_value, }