core (main, xmpp): fix cleaning methods:

Cleaning methods per-profile (`profile_disconnected`) and per-plugin (`unload`) were not
called correctly. They are now called on shutdown with the `before/shutdown` Twisted
event.
With this event, it's still possible to call Deferred and do cleaning requiring async
calls.

rel 479
This commit is contained in:
Goffi 2026-07-17 17:17:10 +02:00
commit e05c69267d
2 changed files with 40 additions and 23 deletions

View file

@ -30,10 +30,7 @@ from libervia.backend.core.i18n import _, D_, language_switch
from libervia.backend.core import patches
from libervia.backend.models.tor import TorData
from libervia.backend.tools.config import (
BridgeSettings,
ComponentSettings,
DBusBridgeSettings,
PbBridgeSettings,
)
patches.apply()
@ -111,6 +108,14 @@ class LiberviaBackend(service.Service):
# reactor is run later.
reactor.callLater(0, self._post_init)
# before/shutdown event is called on backend shutdown, when it's still possible to
# call Deferred. it's used to call all cleaning methods (per-profile's
# "profile_disconnected", and per-plugin's "unload").
reactor.addSystemEventTrigger(
"before", "shutdown", lambda: defer.ensureDeferred(self._on_shutdown())
)
log.info("Shutdown trigger registered.")
@property
def version(self):
"""Return the short version of Libervia"""
@ -310,8 +315,7 @@ class LiberviaBackend(service.Service):
await async_process.run(str(init_script), verbose=True)
except RuntimeError as e:
log.error(f"Init script failed: {e}")
self.stopService()
sys.exit(C.EXIT_ERROR)
self.stop(C.EXIT_ERROR)
await self._sync_components_profiles()
@ -536,14 +540,17 @@ class LiberviaBackend(service.Service):
self.plugins[import_name]._info = plugin_info
# TODO: test xmppclient presence and register handler parent
def plugins_unload(self):
"""Call unload method on every loaded plugin, if exists
async def plugins_unload(self) -> None:
"""Call ``unload`` method (if any) on every loaded plugin.
@return (D): A deferred which return None when all method have been called
Unloaded plugins are removed them from self.plugins, so ``unload`` is not called
twice.
"""
# TODO: in the futur, it should be possible to hot unload a plugin
# pluging depending on the unloaded one should be unloaded too
# for now, just a basic call on plugin.unload is done
# TODO: in the futur, it should be possible to hot unload a plugin.
# Plugins depending on the unloaded one should be unloaded too; for now,
# just a basic call on plugin.unload is done.
if not self.plugins:
return
defers_list = []
for plugin in self.plugins.values():
try:
@ -552,7 +559,8 @@ class LiberviaBackend(service.Service):
continue
else:
defers_list.append(utils.as_deferred(unload))
return defer.DeferredList(defers_list)
await defer.DeferredList(defers_list)
self.plugins.clear()
async def _sync_components_profiles(self) -> None:
"""Synchronize components profiles data with settings.
@ -805,17 +813,25 @@ class LiberviaBackend(service.Service):
self._init()
log.info("Salut à toi ô mon frère !")
def stopService(self):
async def _on_shutdown(self) -> None:
"""Clean up resources on shutdown.
Disconnect all profiles first so profile_disconnected callbacks fire, then unload
plugins and storage.
"""
if self.local_shared_path is not None:
log.debug("Cleaning shared temporary dir.")
shutil.rmtree(self.local_shared_path, ignore_errors=True)
log.info("Salut aussi à Rantanplan.")
return defer.DeferredList([self.plugins_unload(), G.storage.unload()])
def run(self):
log.debug(_("running app"))
reactor.run()
disconnect_defers = [
client.entity_disconnect()
for client in self.profiles.values()
if client.is_connected()
]
if disconnect_defers:
await defer.DeferredList(disconnect_defers)
await self.plugins_unload()
await G.storage.unload()
def stop(self, exit_code: int | None = None):
log.debug(_("stopping app"))

View file

@ -546,7 +546,9 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
# the following Deferred is used to know when we are connected
# so we need to be set it to None when connection is lost
self._connected_d = defer.Deferred()
self._connected_d.addCallback(self._clean_connection)
self._connected_d.addCallback(
lambda __: defer.ensureDeferred(self._clean_connection())
)
self._connected_d.addCallback(self._disconnection_cb)
self._connected_d.addErrback(self._disconnection_eb)
@ -706,8 +708,7 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
return
self.disconnect_profile(reason)
@defer.inlineCallbacks
def _clean_connection(self, __):
async def _clean_connection(self):
"""method called on disconnection
used to call profile_disconnected* triggers
@ -716,7 +717,7 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
for plugin in self._get_plugins_list():
disconnected_cb = getattr(plugin, trigger_name, None)
if disconnected_cb is not None:
yield disconnected_cb(self)
await utils.as_deferred(disconnected_cb, self)
def is_connected(self) -> bool:
"""Return True is client is fully connected