bridge (dbus): Add time out on connection:

- Add a 5s timeout on connection to avoid infinite hang when socket is not working.
- Raise `BridgeInitError` on time-out (needed to make `auto` bridge fallback correctly).
This commit is contained in:
Goffi 2026-10-30 22:19:59 +01:00
commit c6ea6ea27b
4 changed files with 100 additions and 8 deletions

View file

@ -36,6 +36,7 @@ const_ERROR_PREFIX = const_INT_PREFIX + ".error"
const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_OBJ_PATH = "/org/libervia/Libervia/bridge"
const_CORE_SUFFIX = ".core" const_CORE_SUFFIX = ".core"
const_PLUGIN_SUFFIX = ".plugin" const_PLUGIN_SUFFIX = ".plugin"
const_CONNECT_TIMEOUT = 5
class ParseError(Exception): class ParseError(Exception):
@ -123,7 +124,23 @@ class bridge:
async def post_init(self): async def post_init(self):
try: 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: except error.DBusException as e:
if e.errName == "org.freedesktop.DBus.Error.NotSupported": if e.errName == "org.freedesktop.DBus.Error.NotSupported":
log.error( log.error(

View file

@ -19,7 +19,7 @@
import asyncio import asyncio
from functools import partial from functools import partial
from twisted.internet import reactor from twisted.internet import defer, reactor
from txdbus import client from txdbus import client
from .bridge_frontend import BridgeException from .bridge_frontend import BridgeException
@ -40,6 +40,27 @@ const_OBJ_PATH = "/org/libervia/Libervia/bridge"
const_CORE_SUFFIX = ".core" const_CORE_SUFFIX = ".core"
const_PLUGIN_SUFFIX = ".plugin" const_PLUGIN_SUFFIX = ".plugin"
const_TIMEOUT = 120 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): def dbus_to_bridge_exception(failure):
@ -192,7 +213,7 @@ class bridge:
async def bridge_connect(self) -> None: async def bridge_connect(self) -> None:
"""Connect to the D-Bus bridge.""" """Connect to the D-Bus bridge."""
try: try:
self.conn = await client.connect(reactor) self.conn = await _connect_dbus()
self.db_object = await self.conn.getRemoteObject( self.db_object = await self.conn.getRemoteObject(
const_INT_PREFIX, const_OBJ_PATH const_INT_PREFIX, const_OBJ_PATH
) )
@ -218,6 +239,10 @@ class bridge:
) )
except BridgeInitError as e: except BridgeInitError as e:
raise 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: except Exception as e:
msg = str(e) msg = str(e)
if ( if (
@ -281,7 +306,7 @@ class AIOBridge(bridge):
async def bridge_connect(self): async def bridge_connect(self):
"""Connect to the D-Bus bridge (async version).""" """Connect to the D-Bus bridge (async version)."""
try: 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( self.db_object = await self.conn.getRemoteObject(
const_INT_PREFIX, const_OBJ_PATH const_INT_PREFIX, const_OBJ_PATH
).asFuture(asyncio.get_event_loop()) ).asFuture(asyncio.get_event_loop())
@ -308,6 +333,10 @@ class AIOBridge(bridge):
) )
except BridgeInitError as e: except BridgeInitError as e:
raise 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: except Exception as e:
msg = str(e) msg = str(e)
if ( if (

View file

@ -36,6 +36,7 @@ const_ERROR_PREFIX = const_INT_PREFIX + ".error"
const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_OBJ_PATH = "/org/libervia/Libervia/bridge"
const_CORE_SUFFIX = ".core" const_CORE_SUFFIX = ".core"
const_PLUGIN_SUFFIX = ".plugin" const_PLUGIN_SUFFIX = ".plugin"
const_CONNECT_TIMEOUT = 5
class ParseError(Exception): class ParseError(Exception):
@ -571,7 +572,23 @@ class bridge:
async def post_init(self): async def post_init(self):
try: 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: except error.DBusException as e:
if e.errName == "org.freedesktop.DBus.Error.NotSupported": if e.errName == "org.freedesktop.DBus.Error.NotSupported":
log.error( log.error(

View file

@ -19,7 +19,7 @@
import asyncio import asyncio
from functools import partial from functools import partial
from twisted.internet import reactor from twisted.internet import defer, reactor
from txdbus import client from txdbus import client
from .bridge_frontend import BridgeException from .bridge_frontend import BridgeException
@ -40,6 +40,27 @@ const_OBJ_PATH = "/org/libervia/Libervia/bridge"
const_CORE_SUFFIX = ".core" const_CORE_SUFFIX = ".core"
const_PLUGIN_SUFFIX = ".plugin" const_PLUGIN_SUFFIX = ".plugin"
const_TIMEOUT = 120 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): def dbus_to_bridge_exception(failure):
@ -192,7 +213,7 @@ class bridge:
async def bridge_connect(self) -> None: async def bridge_connect(self) -> None:
"""Connect to the D-Bus bridge.""" """Connect to the D-Bus bridge."""
try: try:
self.conn = await client.connect(reactor) self.conn = await _connect_dbus()
self.db_object = await self.conn.getRemoteObject( self.db_object = await self.conn.getRemoteObject(
const_INT_PREFIX, const_OBJ_PATH const_INT_PREFIX, const_OBJ_PATH
) )
@ -218,6 +239,10 @@ class bridge:
) )
except BridgeInitError as e: except BridgeInitError as e:
raise 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: except Exception as e:
msg = str(e) msg = str(e)
if ( if (
@ -963,7 +988,7 @@ class AIOBridge(bridge):
async def bridge_connect(self): async def bridge_connect(self):
"""Connect to the D-Bus bridge (async version).""" """Connect to the D-Bus bridge (async version)."""
try: 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( self.db_object = await self.conn.getRemoteObject(
const_INT_PREFIX, const_OBJ_PATH const_INT_PREFIX, const_OBJ_PATH
).asFuture(asyncio.get_event_loop()) ).asFuture(asyncio.get_event_loop())
@ -990,6 +1015,10 @@ class AIOBridge(bridge):
) )
except BridgeInitError as e: except BridgeInitError as e:
raise 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: except Exception as e:
msg = str(e) msg = str(e)
if ( if (