mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
Network Manager client fetches the basic information from Network Manager, caches it and updates the cache whenever it receives the signal. So, create only a single instance of it and reuse it. Reusing from different threads is apparently fine because the underlying DBusConnection is a singleton[1] that is meant to be used from multiple threads. The glib main loop context that is related to the client must run even after the network manager client object goes away[2]. So, create the network manager client instance from a thread that continues to run the glib main thread. This fixes an infrequent crash when accessing network manager page. The problem was reproducible from very early version of Network Manager and FreedomBox. However, in more recent versions of NetworkManager 1.20 with recent DBus changes in FreedomBox, the problem is more prominent. The problem reduces to a simple warning in NetworkManager 1.22. Closes: #1724 Closes: #382 Tests: - Reproduce the problem by accessing the networks app index page repeatedly. - Create a simple test case file to reproduce the problem and ensure that the fix works there. - On Network Manager 1.20 repeatedly access the networks app index page and create/delete/activate/deactivate/show connections. - On Network Manager 1.22 repeatedly access the networks app index page and create/delete/activate/deactivate/show connections. Links: 1) https://developer.gnome.org/gio/unstable/GDBusConnection.html#g-bus-get-sync 2) https://developer.gnome.org/libnm/stable/NMClient.html#nm-client-get-main-context Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
162 lines
5.2 KiB
Python
Executable File
162 lines
5.2 KiB
Python
Executable File
#
|
|
# This file is part of FreedomBox.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
"""
|
|
Expose some API over D-Bus.
|
|
"""
|
|
|
|
import logging
|
|
import threading
|
|
|
|
from plinth.utils import import_from_gi
|
|
|
|
from . import network, setup
|
|
|
|
glib = import_from_gi('GLib', '2.0')
|
|
gio = import_from_gi('Gio', '2.0')
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_thread = None
|
|
_server = None
|
|
_main_loop = None
|
|
|
|
|
|
class PackageHandler():
|
|
"""D-Bus service to listen for messages when apt cache is updated."""
|
|
|
|
introspection_xml = '''
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
|
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
|
<node name="/org/freedombox/Service/PackageHandler">
|
|
<interface name="org.freedombox.Service.PackageHandler">
|
|
<method name="CacheUpdated"/>
|
|
</interface>
|
|
</node>
|
|
'''
|
|
|
|
def register(self, connection):
|
|
"""Register the object in D-Bus connection."""
|
|
introspection_data = gio.DBusNodeInfo.new_for_xml(
|
|
self.introspection_xml)
|
|
interface_info = gio.DBusNodeInfo.lookup_interface(
|
|
introspection_data, 'org.freedombox.Service.PackageHandler')
|
|
connection.register_object('/org/freedombox/Service/PackageHandler',
|
|
interface_info, self.on_method_call, None,
|
|
None)
|
|
|
|
def on_method_call(self, _connection, _sender, _object_path,
|
|
_interface_name, method_name, _parameters, invocation):
|
|
"""Handle method being called.
|
|
|
|
No need to check all the incoming parameters as D-Bus will validate all
|
|
the incoming parameters using introspection data.
|
|
|
|
"""
|
|
if method_name == 'CacheUpdated':
|
|
self.on_cache_updated()
|
|
invocation.return_value()
|
|
|
|
@staticmethod
|
|
def on_cache_updated():
|
|
"""Called when system package cache is updated."""
|
|
logger.info('Apt package cache updated.')
|
|
|
|
# Run in a new thread because we don't want to block the thread running
|
|
# Glib main loop.
|
|
threading.Thread(target=setup.on_package_cache_updated).start()
|
|
|
|
|
|
class DBusServer():
|
|
"""Abstraction over a connection to D-Bus."""
|
|
def __init__(self):
|
|
"""Initialize the server object."""
|
|
self.package_handler = None
|
|
|
|
def connect(self):
|
|
"""Connect to bus with well-known name."""
|
|
gio.bus_own_name(gio.BusType.SYSTEM, 'org.freedombox.Service',
|
|
gio.BusNameOwnerFlags.NONE, self.on_bus_acquired,
|
|
self.on_name_acquired, self.on_name_lost)
|
|
|
|
def on_bus_acquired(self, connection, name):
|
|
"""Callback when connection to D-Bus has been acquired."""
|
|
logger.info('D-Bus connection acquired: %s', name)
|
|
self.package_handler = PackageHandler()
|
|
self.package_handler.register(connection)
|
|
|
|
from plinth.modules.letsencrypt.dbus import LetsEncrypt
|
|
lets_encrypt = LetsEncrypt()
|
|
lets_encrypt.register(connection)
|
|
|
|
@staticmethod
|
|
def on_name_acquired(_connection, name):
|
|
"""Callback when service name on D-Bus has been acquired."""
|
|
logger.info('D-Bus name acquired: %s', name)
|
|
|
|
@staticmethod
|
|
def on_name_lost(_connection, name):
|
|
"""Callback when service name or DBus connection is closed."""
|
|
logger.info('D-Bus connection lost: %s', name)
|
|
|
|
# XXX: Reconnect after a while
|
|
#
|
|
# Such as by doing:
|
|
# connection.set_exit_on_close(False)
|
|
# gio.timeout_add(10000, self.connect)
|
|
#
|
|
# However, perhaps due to some cleanup issues, reconnection is not
|
|
# happening if it is does after an incoming method call.
|
|
#
|
|
# If D-Bus connection is lost due to daemon restart, FreedomBox service
|
|
# will receive a SIGTERM and exit. systemd should then restart the
|
|
# service again.
|
|
|
|
|
|
def run():
|
|
"""Run a glib main loop forever in a thread."""
|
|
global _thread
|
|
_thread = threading.Thread(target=_run)
|
|
_thread.start()
|
|
|
|
|
|
def stop():
|
|
"""Exit glib main loop and end the thread."""
|
|
if _main_loop:
|
|
logger.info('Exiting main loop for D-Bus services')
|
|
_main_loop.quit()
|
|
|
|
|
|
def _run():
|
|
"""Connect to D-Bus and run main loop."""
|
|
logger.info('Started new thread for D-Bus services')
|
|
|
|
global _server
|
|
_server = DBusServer()
|
|
_server.connect()
|
|
|
|
# Initialize all other modules that glib main loop
|
|
# XXX: Refactor this code into separate 'glib' module later
|
|
network.init()
|
|
|
|
global _main_loop
|
|
_main_loop = glib.MainLoop()
|
|
_main_loop.run()
|
|
_main_loop = None
|
|
|
|
logger.info('D-Bus services thread exited.')
|