diff --git a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py index 4496cfe5..10bbb9f2 100644 --- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py +++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py @@ -36,6 +36,7 @@ const_ERROR_PREFIX = const_INT_PREFIX + ".error" const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" +const_CONNECT_TIMEOUT = 5 class ParseError(Exception): @@ -123,7 +124,23 @@ class bridge: async def post_init(self): try: - self.conn = await client.connect(reactor) + d = client.connect(reactor) + reactor.callLater(const_CONNECT_TIMEOUT, d.cancel) + + def on_cancel(failure): + if failure.check(defer.CancelledError): + log.error( + f"D-Bus connection timed out after {const_CONNECT_TIMEOUT} s, " + "please check the D-Bus service" + ) + return failure + + d.addErrback(on_cancel) + self.conn = await d + except defer.CancelledError: + # The connect deferred was cancelled by the timeout, we raise BridgeInitError + # so that the "auto" bridge can fall back to the next one. + raise BridgeInitError from None except error.DBusException as e: if e.errName == "org.freedesktop.DBus.Error.NotSupported": log.error( diff --git a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py index b8c92174..86e11e26 100644 --- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py +++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py @@ -19,7 +19,7 @@ import asyncio from functools import partial -from twisted.internet import reactor +from twisted.internet import defer, reactor from txdbus import client from .bridge_frontend import BridgeException @@ -40,6 +40,27 @@ const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" const_TIMEOUT = 120 +const_CONNECT_TIMEOUT = 5 + + +def _connect_dbus(): + """Connect to the D-Bus session bus with a timeout. + + @return: Deferred to the D-Bus connection + """ + d = client.connect(reactor) + reactor.callLater(const_CONNECT_TIMEOUT, d.cancel) + + def on_cancel(failure): + if failure.check(defer.CancelledError): + log.error( + f"D-Bus connection timed out after {const_CONNECT_TIMEOUT} s, " + "please check the D-Bus service" + ) + return failure + + d.addErrback(on_cancel) + return d def dbus_to_bridge_exception(failure): @@ -192,7 +213,7 @@ class bridge: async def bridge_connect(self) -> None: """Connect to the D-Bus bridge.""" try: - self.conn = await client.connect(reactor) + self.conn = await _connect_dbus() self.db_object = await self.conn.getRemoteObject( const_INT_PREFIX, const_OBJ_PATH ) @@ -218,6 +239,10 @@ class bridge: ) except BridgeInitError as e: raise e + except defer.CancelledError: + # The connect deferred was cancelled by the timeout, we raise BridgeInitError + # so that the "auto" bridge can fall back to the next one. + raise BridgeInitError from None except Exception as e: msg = str(e) if ( @@ -281,7 +306,7 @@ class AIOBridge(bridge): async def bridge_connect(self): """Connect to the D-Bus bridge (async version).""" try: - self.conn = await client.connect(reactor).asFuture(asyncio.get_event_loop()) + self.conn = await _connect_dbus().asFuture(asyncio.get_event_loop()) self.db_object = await self.conn.getRemoteObject( const_INT_PREFIX, const_OBJ_PATH ).asFuture(asyncio.get_event_loop()) @@ -308,6 +333,10 @@ class AIOBridge(bridge): ) except BridgeInitError as e: raise e + except defer.CancelledError: + # The connect deferred was cancelled by the timeout, we raise BridgeInitError + # so that the "auto" bridge can fall back to the next one. + raise BridgeInitError from None except Exception as e: msg = str(e) if ( diff --git a/libervia/backend/bridge/dbus_bridge.py b/libervia/backend/bridge/dbus_bridge.py index e4cd534f..e0e31847 100644 --- a/libervia/backend/bridge/dbus_bridge.py +++ b/libervia/backend/bridge/dbus_bridge.py @@ -36,6 +36,7 @@ const_ERROR_PREFIX = const_INT_PREFIX + ".error" const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" +const_CONNECT_TIMEOUT = 5 class ParseError(Exception): @@ -571,7 +572,23 @@ class bridge: async def post_init(self): try: - self.conn = await client.connect(reactor) + d = client.connect(reactor) + reactor.callLater(const_CONNECT_TIMEOUT, d.cancel) + + def on_cancel(failure): + if failure.check(defer.CancelledError): + log.error( + f"D-Bus connection timed out after {const_CONNECT_TIMEOUT} s, " + "please check the D-Bus service" + ) + return failure + + d.addErrback(on_cancel) + self.conn = await d + except defer.CancelledError: + # The connect deferred was cancelled by the timeout, we raise BridgeInitError + # so that the "auto" bridge can fall back to the next one. + raise BridgeInitError from None except error.DBusException as e: if e.errName == "org.freedesktop.DBus.Error.NotSupported": log.error( diff --git a/libervia/frontends/bridge/dbus_bridge.py b/libervia/frontends/bridge/dbus_bridge.py index 4efa1844..cc3a0fc9 100644 --- a/libervia/frontends/bridge/dbus_bridge.py +++ b/libervia/frontends/bridge/dbus_bridge.py @@ -19,7 +19,7 @@ import asyncio from functools import partial -from twisted.internet import reactor +from twisted.internet import defer, reactor from txdbus import client from .bridge_frontend import BridgeException @@ -40,6 +40,27 @@ const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" const_TIMEOUT = 120 +const_CONNECT_TIMEOUT = 5 + + +def _connect_dbus(): + """Connect to the D-Bus session bus with a timeout. + + @return: Deferred to the D-Bus connection + """ + d = client.connect(reactor) + reactor.callLater(const_CONNECT_TIMEOUT, d.cancel) + + def on_cancel(failure): + if failure.check(defer.CancelledError): + log.error( + f"D-Bus connection timed out after {const_CONNECT_TIMEOUT} s, " + "please check the D-Bus service" + ) + return failure + + d.addErrback(on_cancel) + return d def dbus_to_bridge_exception(failure): @@ -192,7 +213,7 @@ class bridge: async def bridge_connect(self) -> None: """Connect to the D-Bus bridge.""" try: - self.conn = await client.connect(reactor) + self.conn = await _connect_dbus() self.db_object = await self.conn.getRemoteObject( const_INT_PREFIX, const_OBJ_PATH ) @@ -218,6 +239,10 @@ class bridge: ) except BridgeInitError as e: raise e + except defer.CancelledError: + # The connect deferred was cancelled by the timeout, we raise BridgeInitError + # so that the "auto" bridge can fall back to the next one. + raise BridgeInitError from None except Exception as e: msg = str(e) if ( @@ -963,7 +988,7 @@ class AIOBridge(bridge): async def bridge_connect(self): """Connect to the D-Bus bridge (async version).""" try: - self.conn = await client.connect(reactor).asFuture(asyncio.get_event_loop()) + self.conn = await _connect_dbus().asFuture(asyncio.get_event_loop()) self.db_object = await self.conn.getRemoteObject( const_INT_PREFIX, const_OBJ_PATH ).asFuture(asyncio.get_event_loop()) @@ -990,6 +1015,10 @@ class AIOBridge(bridge): ) except BridgeInitError as e: raise e + except defer.CancelledError: + # The connect deferred was cancelled by the timeout, we raise BridgeInitError + # so that the "auto" bridge can fall back to the next one. + raise BridgeInitError from None except Exception as e: msg = str(e) if (