bridge (dbus, pb): fix intialisation issues:
- D-Bus bridge check if it can import GLib, to raise early a BridgeInitError if it's not possible - PB bridge now do lazy import of `reactor` to avoid race condition in Twisted loop initialisation. - regenerated bridges.
This commit is contained in:
parent
84321be4cc
commit
30619f5689
8 changed files with 145 additions and 40 deletions
|
|
@ -26,6 +26,13 @@ from libervia.backend.core.exceptions import BridgeExceptionNoService, BridgeIni
|
|||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from .bridge_frontend import BridgeException
|
||||
|
||||
# We check if GLib is actually available to avoid init error at a later stage.
|
||||
try:
|
||||
from gi.repository import GLib # type: ignore
|
||||
except ImportError as e:
|
||||
raise BridgeInitError(f"GLib is required for D-Bus bridge: {e}")
|
||||
|
||||
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
|
@ -178,6 +185,9 @@ class bridge:
|
|||
|
||||
class AIOBridge(bridge):
|
||||
|
||||
async def post_init(self) -> None:
|
||||
pass
|
||||
|
||||
def register_signal(self, functionName, handler, iface="core"):
|
||||
loop = asyncio.get_running_loop()
|
||||
async_handler = lambda *args: asyncio.run_coroutine_threadsafe(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from logging import getLogger
|
|||
from functools import partial
|
||||
from pathlib import Path
|
||||
from twisted.spread import pb
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.error import ConnectionRefusedError, ConnectError
|
||||
from libervia.backend.core import exceptions
|
||||
from libervia.backend.tools import config
|
||||
|
|
@ -135,6 +135,11 @@ class bridge(object):
|
|||
raise failure_
|
||||
|
||||
def bridge_connect(self, callback, errback):
|
||||
# We lazy import "reactor" to avoid race condition during loop initialisation:
|
||||
# twisted automatically init default loop when we import reactor, and we must be
|
||||
# sure that the right loop (usually asyncioreactor) is used.
|
||||
from twisted.internet import reactor
|
||||
|
||||
factory = pb.PBClientFactory()
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.get_bridge_conf(config.PbBridgeSettings)
|
||||
|
|
@ -176,6 +181,14 @@ class AIOBridge(bridge):
|
|||
def __init__(self):
|
||||
self.signals_handler = AIOSignalsHandler()
|
||||
|
||||
async def post_init(self):
|
||||
"""Post-initialization hook.
|
||||
|
||||
Actually no post init is needed, but the "__getattr__" return "partial" for
|
||||
unknown methods, and this causes "dynamic_import.load_and_init_bridge" to fail.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _errback(self, failure_):
|
||||
"""Convert Failure to BridgeException"""
|
||||
raise BridgeException(
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ class DBusObject(objects.DBusObject):
|
|||
Method("sub_waiting_get", arguments="s", returns="a{ss}"),
|
||||
Method("subscription", arguments="sss", returns=""),
|
||||
Method("tor_infos_get", arguments="ss", returns="s"),
|
||||
Method("tor_mode_get", arguments="s", returns="s"),
|
||||
Method("version_get", arguments="", returns="s"),
|
||||
Signal("_debug", "sa{ss}s"),
|
||||
Signal("action_new", "ssis"),
|
||||
|
|
@ -533,6 +534,9 @@ class DBusObject(objects.DBusObject):
|
|||
def dbus_tor_infos_get(self, extra, profile_key):
|
||||
return self._callback("tor_infos_get", extra, profile_key)
|
||||
|
||||
def dbus_tor_mode_get(self, profile_key):
|
||||
return self._callback("tor_mode_get", profile_key)
|
||||
|
||||
def dbus_version_get(
|
||||
self,
|
||||
):
|
||||
|
|
|
|||
|
|
@ -113,12 +113,16 @@ def load_bridge(config: Settings, verbose: bool = True) -> tuple[Any, BridgeSett
|
|||
return bridge, bridge_settings
|
||||
|
||||
|
||||
async def _load_and_init_bridge(bridge_settings: BridgeSettings):
|
||||
async def _load_and_init_bridge(
|
||||
bridge_settings: BridgeSettings, verbose: bool, is_backend: bool
|
||||
):
|
||||
"""Load and set requested bridge.
|
||||
|
||||
@raise BridgeInitError: Bridge can't be initialised.
|
||||
@raise BridgeExceptionNoService: Bridge is initialised, but the backend can't be
|
||||
found.
|
||||
"""
|
||||
bridge = _load_bridge(bridge_settings)
|
||||
bridge = _load_bridge(bridge_settings, verbose)
|
||||
try:
|
||||
bridge_post_init = bridge.post_init
|
||||
except AttributeError:
|
||||
|
|
@ -127,13 +131,22 @@ async def _load_and_init_bridge(bridge_settings: BridgeSettings):
|
|||
try:
|
||||
await bridge_post_init()
|
||||
except Exception as e:
|
||||
log.warning(f"Could not initialize bridge: {e}")
|
||||
if verbose:
|
||||
log.warning(f"Could not initialize bridge: {e}")
|
||||
raise exceptions.BridgeInitError from e
|
||||
if not is_backend:
|
||||
# For frontends, we connect the bridge.
|
||||
try:
|
||||
await bridge.bridge_connect()
|
||||
except (exceptions.BridgeInitError, exceptions.BridgeExceptionNoService) as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise exceptions.BridgeInitError from e
|
||||
return bridge
|
||||
|
||||
|
||||
async def load_and_init_bridge(
|
||||
config: Settings, is_backend=False
|
||||
config: Settings, verbose: bool = True, is_backend=False
|
||||
) -> tuple[Any, BridgeSettings]:
|
||||
"""Load and initialise the bridge.
|
||||
|
||||
|
|
@ -142,7 +155,9 @@ async def load_and_init_bridge(
|
|||
@param config: Libervia Settings
|
||||
@param is_backend: Set to True if calling from backend.
|
||||
@return: initialised bridge and bridge settings
|
||||
@raise BridgeInitError: Can't initialise this bridge.
|
||||
@raise BridgeInitError: Bridge can't be initialised.
|
||||
@raise BridgeExceptionNoService: Bridge is initialised, but the backend can't be
|
||||
found.
|
||||
"""
|
||||
|
||||
if is_backend:
|
||||
|
|
@ -157,14 +172,15 @@ async def load_and_init_bridge(
|
|||
try:
|
||||
bridge_settings = DBusBridgeSettings()
|
||||
config.bridges = [bridge_settings]
|
||||
bridge = await _load_and_init_bridge(bridge_settings)
|
||||
bridge = await _load_and_init_bridge(bridge_settings, verbose, is_backend)
|
||||
except exceptions.BridgeInitError as e:
|
||||
log.warning(f"Can't load D-Bus bridge: {e}")
|
||||
log.info("Falling back to PB bridge.")
|
||||
if verbose:
|
||||
log.warning(f"Can't load D-Bus bridge: {e}")
|
||||
log.info("Falling back to PB bridge.")
|
||||
bridge_settings = PbBridgeSettings()
|
||||
config.bridges = [bridge_settings]
|
||||
|
||||
if bridge is None:
|
||||
bridge = await _load_and_init_bridge(bridge_settings)
|
||||
bridge = await _load_and_init_bridge(bridge_settings, verbose, is_backend)
|
||||
|
||||
return bridge, bridge_settings
|
||||
|
|
|
|||
|
|
@ -138,20 +138,10 @@ class LiberviaCli:
|
|||
@attribute progress_failure(callable): method to call when progress failed
|
||||
by default display a message
|
||||
"""
|
||||
main_config = config.parse_main_conf(ignore_cache=True)
|
||||
|
||||
try:
|
||||
bridge, self.bridge_settings = dynamic_import.load_bridge(
|
||||
main_config, verbose=False
|
||||
)
|
||||
except exceptions.BridgeInitError:
|
||||
log.exception("Can't import bridge")
|
||||
sys.exit(1)
|
||||
self.console = console.Console(theme=C.THEME_DEFAULT)
|
||||
self.settings = main_config
|
||||
self.cli_settings = main_config.get_settings(CLISettings)
|
||||
self.settings = config.parse_main_conf(ignore_cache=True)
|
||||
self.cli_settings = self.settings.get_settings(CLISettings)
|
||||
self.set_color_theme()
|
||||
self.bridge = bridge
|
||||
|
||||
self._on_quit_callbacks = []
|
||||
|
||||
|
|
@ -819,23 +809,25 @@ class LiberviaCli:
|
|||
|
||||
async def main(self, args, namespace):
|
||||
try:
|
||||
await self.bridge.bridge_connect()
|
||||
self.bridge, self.bridge_settings = await dynamic_import.load_and_init_bridge(
|
||||
self.settings, verbose=False
|
||||
)
|
||||
except exceptions.BridgeExceptionNoService as e:
|
||||
self.disp(
|
||||
_(
|
||||
"Can't connect to Libervia backend, are you sure that it's "
|
||||
"launched ?"
|
||||
),
|
||||
error=True,
|
||||
)
|
||||
self.quit(C.EXIT_BACKEND_NOT_FOUND, raise_exc=False)
|
||||
except exceptions.BridgeInitError as e:
|
||||
self.disp(_("Can't init bridge"), error=True)
|
||||
self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False)
|
||||
except Exception as e:
|
||||
if isinstance(e, exceptions.BridgeExceptionNoService):
|
||||
print(
|
||||
_(
|
||||
"Can't connect to Libervia backend, are you sure that it's "
|
||||
"launched ?"
|
||||
)
|
||||
)
|
||||
self.quit(C.EXIT_BACKEND_NOT_FOUND, raise_exc=False)
|
||||
elif isinstance(e, exceptions.BridgeInitError):
|
||||
print(_("Can't init bridge"))
|
||||
self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False)
|
||||
else:
|
||||
print(_("Error while initialising bridge: {e}").format(e=e))
|
||||
self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False)
|
||||
return
|
||||
raise exceptions.InternalError(
|
||||
"Something went wrong while initialising bridge."
|
||||
) from e
|
||||
# we wait on init_pre_script instead of ready_get, so the CLI frontend can be used
|
||||
# in init script.
|
||||
await self.bridge.init_pre_script()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ from libervia.backend.core.exceptions import BridgeExceptionNoService, BridgeIni
|
|||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from .bridge_frontend import BridgeException
|
||||
|
||||
# We check if GLib is actually available to avoid init error at a later stage.
|
||||
try:
|
||||
from gi.repository import GLib # type: ignore
|
||||
except ImportError as e:
|
||||
raise BridgeInitError(f"GLib is required for D-Bus bridge: {e}")
|
||||
|
||||
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
|
@ -1455,6 +1462,22 @@ class bridge:
|
|||
)
|
||||
)
|
||||
|
||||
def tor_mode_get(self, profile_key, callback=None, errback=None):
|
||||
if callback is None:
|
||||
error_handler = None
|
||||
else:
|
||||
if errback is None:
|
||||
errback = log.error
|
||||
error_handler = lambda err: errback(dbus_to_bridge_exception(err))
|
||||
return str(
|
||||
self.db_core_iface.tor_mode_get(
|
||||
profile_key,
|
||||
timeout=const_TIMEOUT,
|
||||
reply_handler=callback,
|
||||
error_handler=error_handler,
|
||||
)
|
||||
)
|
||||
|
||||
def version_get(self, callback=None, errback=None):
|
||||
if callback is None:
|
||||
error_handler = None
|
||||
|
|
@ -1472,6 +1495,9 @@ class bridge:
|
|||
|
||||
class AIOBridge(bridge):
|
||||
|
||||
async def post_init(self) -> None:
|
||||
pass
|
||||
|
||||
def register_signal(self, functionName, handler, iface="core"):
|
||||
loop = asyncio.get_running_loop()
|
||||
async_handler = lambda *args: asyncio.run_coroutine_threadsafe(
|
||||
|
|
@ -2750,6 +2776,21 @@ class AIOBridge(bridge):
|
|||
)
|
||||
return fut
|
||||
|
||||
def tor_mode_get(self, profile_key):
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
reply_handler = lambda ret=None: loop.call_soon_threadsafe(fut.set_result, ret)
|
||||
error_handler = lambda err: loop.call_soon_threadsafe(
|
||||
fut.set_exception, dbus_to_bridge_exception(err)
|
||||
)
|
||||
self.db_core_iface.tor_mode_get(
|
||||
profile_key,
|
||||
timeout=const_TIMEOUT,
|
||||
reply_handler=reply_handler,
|
||||
error_handler=error_handler,
|
||||
)
|
||||
return fut
|
||||
|
||||
def version_get(self):
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from logging import getLogger
|
|||
from functools import partial
|
||||
from pathlib import Path
|
||||
from twisted.spread import pb
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.error import ConnectionRefusedError, ConnectError
|
||||
from libervia.backend.core import exceptions
|
||||
from libervia.backend.tools import config
|
||||
|
|
@ -135,6 +135,11 @@ class bridge(object):
|
|||
raise failure_
|
||||
|
||||
def bridge_connect(self, callback, errback):
|
||||
# We lazy import "reactor" to avoid race condition during loop initialisation:
|
||||
# twisted automatically init default loop when we import reactor, and we must be
|
||||
# sure that the right loop (usually asyncioreactor) is used.
|
||||
from twisted.internet import reactor
|
||||
|
||||
factory = pb.PBClientFactory()
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.get_bridge_conf(config.PbBridgeSettings)
|
||||
|
|
@ -1019,6 +1024,15 @@ class bridge(object):
|
|||
else:
|
||||
d.addErrback(self._errback, ori_errback=errback)
|
||||
|
||||
def tor_mode_get(self, profile_key, callback=None, errback=None):
|
||||
d = self.root.callRemote("tor_mode_get", profile_key)
|
||||
if callback is not None:
|
||||
d.addCallback(callback)
|
||||
if errback is None:
|
||||
d.addErrback(self._generic_errback)
|
||||
else:
|
||||
d.addErrback(self._errback, ori_errback=errback)
|
||||
|
||||
def version_get(self, callback=None, errback=None):
|
||||
d = self.root.callRemote("version_get")
|
||||
if callback is not None:
|
||||
|
|
@ -1043,6 +1057,14 @@ class AIOBridge(bridge):
|
|||
def __init__(self):
|
||||
self.signals_handler = AIOSignalsHandler()
|
||||
|
||||
async def post_init(self):
|
||||
"""Post-initialization hook.
|
||||
|
||||
Actually no post init is needed, but the "__getattr__" return "partial" for
|
||||
unknown methods, and this causes "dynamic_import.load_and_init_bridge" to fail.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _errback(self, failure_):
|
||||
"""Convert Failure to BridgeException"""
|
||||
raise BridgeException(
|
||||
|
|
@ -1503,6 +1525,11 @@ class AIOBridge(bridge):
|
|||
d.addErrback(self._errback)
|
||||
return d.asFuture(asyncio.get_event_loop())
|
||||
|
||||
def tor_mode_get(self, profile_key):
|
||||
d = self.root.callRemote("tor_mode_get", profile_key)
|
||||
d.addErrback(self._errback)
|
||||
return d.asFuture(asyncio.get_event_loop())
|
||||
|
||||
def version_get(self):
|
||||
d = self.root.callRemote("version_get")
|
||||
d.addErrback(self._errback)
|
||||
|
|
|
|||
|
|
@ -338,7 +338,9 @@ class LiberviaTUIApp(QuickApp, InputHistory):
|
|||
AVATARS_HANDLER = False
|
||||
|
||||
def __init__(self):
|
||||
bridge_module = dynamic_import.import_bridge(bridge_name, "libervia.frontends.bridge")
|
||||
bridge_module = dynamic_import.import_bridge(
|
||||
bridge_name, "libervia.frontends.bridge"
|
||||
)
|
||||
if bridge_module is None:
|
||||
log.error("Can't import {} bridge".format(bridge_name))
|
||||
sys.exit(3)
|
||||
|
|
|
|||
Loading…
Reference in a new issue