hi! I’m trying to understand how one would go about integrating a GMainContext with an existing event loop. I have read about g_main_context_prepare() / <<...>_query(), etc. but I have a couple of doubts:
I don’t think the docs are super explicit but I imagine that the idea is that between g_main_context_query() and g_main_context_check(), one must poll() the GPollFD array as is, right?
Regarding thread safety, I know the event loop should not be driven outside of the main thread. However, would it be OK to perform all iteration steps except for g_main_context_dispatch()?
For context, I’m trying to wrap my head around integrating Node’s libuv and GTK. (I am aware of the existing previous art, just pondering options).
I’ve not checked if it answers your questions, but wanted to provide the link nevertheless.
If the GLib docs are not enough, there is also the implementation. In the past I’ve contributed a little to the GLib API docs because some bits of information were missing.
The first thing you need to decide is which event loop is driving the other. Is the libuv event loop going to be running the GLib one as one of its sources; or is the GLib one going to be running the libuv one as one of its sources?
Yes, see what g_main_context_poll_unlocked() does in glib.git.
Not a great idea, as the GSourceFuncs of all the sources in the GMainContext will end up getting run in the wrong thread, and there are probably GSource implementations which don’t expect that.
A GMainContext can be run in a worker thread, it doesn’t have to be run in the main thread. The important thing is that it’s only run in one thread and doesn’t change between threads.
I think it makes more sense to drive GLib from libuv, since the latter is a (not so secret) implementation detail of Node. I’ve seen it done the other way around, but I’d prefer to not even touch libuv at all, since Node’s FFI exposes it only as a last recourse (and other JS engines don’t have it).
Hm, ok, that’s disappointing to hear, I don’t quite see a “right way ™️” of doing things. I’m thinking you could poll from libuv using uv_poll_t but I’d have to check how compatible that is with GPollFD. And I’m guessing you’d have to rebuild the list of fds to poll on every iteration, since g_main_context_query() doesn’t give you a “diff”.
In any case, this is highly Node-specific and coupled to libuv. Their preferred way of integrating with the event loop (similar to the only way for Deno) is to awaken it from another thread. If you drive GLib in a separate thread, then… you can’t have “native” async I/O in that thread, which feels a bit limiting
Sure, sorry for rambling, I’m now thinking in something like this:
GPollFD* fds;
int timeout;
int nfds;
// call this once from Node's main thread
void init_glib_event_loop() {
prepare_loop();
}
void prepare_loop() {
g_main_context_prepare(context, &priority);
g_main_context_query(context, priority, &timeout, fds, &nfds);
// details omitted, but basically we execute poll_in_thread in a worker pool
// and once finished, drain_loop is called in Node's main thread
napi_create_async_work(poll_in_thread, drain_loop);
}
void drain_loop() {
g_main_context_check (context, fds, nfds);
g_main_context_dispatch (context);
// we probably want to drain the loop here
prepare_loop();
}
void poll_in_thread() {
poll(fds, nfds, timeout);
}
I can’t immediately see why that wouldn’t work, but it doesn’t look very pretty to me.
Is there really no way to pass the FDs to poll from g_main_context_query() to libuv and get libuv to include them in its poll() call? That’s the normal way to integrate main loops: get them to share a single poll() call (in the main thread) but do their own preparation for it and their own post-processing of the results.
It’s not pretty indeed, I agree, it’s the price for not “coupling” to libuv.
There is (uv_poll) and I have considered it as well. I had a bunch of objections to using it, but re-reading the docs I see now a possible way of making it work (prepare + check, it’s almost obvious ). I was mostly concerned with upholding the invariant of “there must be a singlepoll() call between g_xxx_query() and g_xxx_check()", which is another constraint that I think more or less transpires from GMainContext docs.