networks: Fix crashing when accessing network manager D-Bus API

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>
This commit is contained in:
Sunil Mohan Adapa 2020-01-07 22:25:27 -08:00 committed by James Valleroy
parent 7695047606
commit 4ad3a9ee8f
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 52 additions and 2 deletions

View File

@ -23,7 +23,7 @@ import threading
from plinth.utils import import_from_gi
from . import setup
from . import network, setup
glib = import_from_gi('GLib', '2.0')
gio = import_from_gi('Gio', '2.0')
@ -149,6 +149,10 @@ def _run():
_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()

View File

@ -33,6 +33,8 @@ nm = import_from_gi('NM', '1.0')
logger = logging.getLogger(__name__)
_client = None
CONNECTION_TYPE_NAMES = collections.OrderedDict([
('802-3-ethernet', _('Ethernet')),
('802-11-wireless', _('Wi-Fi')),
@ -61,9 +63,24 @@ def ipv4_int_to_string(address_int):
return socket.inet_ntoa(struct.pack('=I', address_int))
def init():
"""Create and keep a network manager client instance."""
def new_callback(source_object, result, user_data):
"""Called when new() operation is complete."""
global _client
_client = nm.Client.new_finish(result)
logger.info('Created Network manager client')
logger.info('Creating network manager client')
nm.Client.new_async(None, new_callback, None)
def get_nm_client():
"""Return a network manager client object."""
return nm.Client.new(None)
if _client:
return _client
raise Exception('Client not yet ready')
def _callback(source_object, result, user_data):

View File

@ -19,10 +19,13 @@ Test module for network configuration utilities.
"""
import copy
import threading
import time
import pytest
from plinth.utils import import_from_gi
ethernet_settings = {
'common': {
'type': '802-3-ethernet',
@ -83,6 +86,32 @@ pppoe_settings = {
}
@pytest.fixture(autouse=True, scope='module')
def fixture_network_module_init():
"""Initialize network module in a separate thread."""
from plinth import network as network_module
glib = import_from_gi('GLib', '2.0')
main_loop = glib.MainLoop()
def main_loop_runner():
"""Initialize the network module and run glib main loop until quit."""
network_module.init()
main_loop.run()
thread = threading.Thread(target=main_loop_runner)
thread.start()
while not network_module._client:
time.sleep(0.1)
yield
if main_loop:
main_loop.quit()
thread.join()
@pytest.fixture(name='network')
def fixture_network(needs_root):
"""Return the network module. Load it conservatively."""