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.
This commit is contained in:
Mose Müller
2025-06-23 09:08:50 +02:00
parent 6438a07305
commit 0f1ca84df5

View File

@@ -65,19 +65,31 @@ class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection):
self.reconnect = reconnect self.reconnect = reconnect
def serialize(self) -> SerializedObject: def serialize(self) -> SerializedObject:
if self._service_representation is None: current_loop = asyncio.get_event_loop()
serialization_future = cast(
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.Future[SerializedDataService]",
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self._sio.call("service_serialization"), self._loop self._sio.call("service_serialization"), self._loop
), ),
) )
result = future.result()
# need to use object.__setattr__ to not trigger an observer notification # need to use object.__setattr__ to not trigger an observer notification
object.__setattr__( object.__setattr__(self, "_service_representation", result)
self, "_service_representation", serialization_future.result()
)
if TYPE_CHECKING: if TYPE_CHECKING:
self._service_representation = serialization_future.result() self._service_representation = result
serialized_service = result
device_connection_value = cast( device_connection_value = cast(
"dict[str, SerializedObject]", "dict[str, SerializedObject]",
@@ -93,7 +105,7 @@ class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection):
"dict[str, SerializedObject]", "dict[str, SerializedObject]",
# need to deepcopy to not overwrite the _service_representation dict # need to deepcopy to not overwrite the _service_representation dict
# when adding a prefix with add_prefix_to_full_access_path # when adding a prefix with add_prefix_to_full_access_path
deepcopy(self._service_representation["value"]), deepcopy(serialized_service["value"]),
), ),
**device_connection_value, **device_connection_value,
} }