0

I am trying to use the libnm library to call NetworkManager calls from one thread while another thread takes care of iterating the context and handling the callbacks of the asynchronous methods. Yet whatever I do, I cannot seem to shake these three critical glib warnings:

GLib-CRITICAL **: 15:55:38.490: g_main_context_push_thread_default: assertion 'acquired_context' failed

GLib-CRITICAL **: 15:55:38.490: g_main_context_pop_thread_default: assertion 'g_queue_peek_head (stack) == context' failed

libnm-CRITICAL **: 15:55:38.491: ((libnm/nm-client.c:5925)): assertion '<dropped>' failed

I have made a minimally reproducible example seen below

#include <thread>
#include <glib.h>
#include <gio/gio.h>
#include <iostream>
#include <NetworkManager.h>

class NetworkManager
{
public:
    NetworkManager()
    {
        std::cout << "Network manager creation" << std::endl;
        GError* error;
        m_client = nm_client_new(nullptr, &error);
        m_context = nm_client_get_main_context(m_client);
        m_thread = std::thread([this]() {
            g_main_context_push_thread_default(m_context);
            while (true)
            {
                g_main_context_iteration(m_context, true);
            }
            g_main_context_pop_thread_default(m_context);
        });
        std::this_thread::sleep_for(std::chrono::seconds(1));
        nm_client_reload_connections_async(m_client, nullptr, (GAsyncReadyCallback)callback, this);
    }
    ~NetworkManager() {};
    static void callback(GObject*, GAsyncResult*, gpointer data)
    {
        auto nm_ptr = static_cast<NetworkManager*>(data);
        bool success = nm_client_reload_connections_finish(nm_ptr->m_client, nullptr, nullptr);
        std::cout << success << std::endl;
    }
    std::thread m_thread;
    GMainContext* m_context;
    NMClient* m_client;
};


int main() {
    NetworkManager nm{};
    int i = 0;
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << i++ << std::endl;
    }
}

Have I misunderstood how the main context thread pushing and popping works? I have read the Main Context tutorial but I still do not completely understand how to correctly handle the main context from another thread.

1
  • 1
    I don't know this API, but you missed an important detail from their example, and the first assertion failure supports that: they are creating a worker context in the thread, instead of just trying to push the global one: worker_context = g_main_context_new ();. The first assertion basically says that the context you are trying to use is already owned somewhere else. Commented Apr 29, 2024 at 16:23

1 Answer 1

2

libnm provides you with NMClient. Which is a client-side cache of the data you see on the D-Bus API.

That cache is not thread-safe per-se. ... well, it is thread-safe in the sense that every access through it must go through the associated GMainContext.

You cannot access the data from multiple threads. Unless, you make sure that they don't access the data at the same time. The suggested way to ensure that is to acquire the GMainContext before access.

Usually, we have one GMainContext per thread. But you could also pass one GMainContext between threads. But then still, you must acquire the context before accessing the data. "Acquire" here means to take exclusive access. If you do non-trivial things, you would need to beware of deadlocks.

Maybe you should only have one thread run the GMainContext and access NMClient. And then pass data between threads (for example, by attaching callbacks in other threads with g_idle_source_new()).

You get the assertion failure because g_main_context_iteration() will acqire the GMainContext. Another thread cannot aquire it again, and trying to use NMClient that way fails.

I suggest to read the tutorial again. It's IMO very good.


The reasons why this doesn't work, is because nm_client_reload_connections_async() will call g_dbus_connection_call(). Before doing that, NMClient will g_main_context_push_thread_default() it's nm_client_get_main_context(). This is necessary, because the callback of the async request must be invoked while iterating nm_client_get_main_context(). That way, the callback will be able to access the data, and it's ordered with the events that come in.

All events towards the NMClient cache, must come in by iterating nm_client_get_main_context(). They are ordered that way.

g_main_context_push_thread_default() acquires the context, but it's already acquired on the other thread. Hence, it doesn't work and you get the assertion failures.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.