I am using GLib for the first time. So, to understand how to use GLib APIs, I wrote following dummy program.
#include<glib.h>
#include<iostream>
#include<thread>
#include<chrono>
using namespace std;
using namespace std::chrono_literals;
class executor
{
private:
GMainLoop* main_loop;
GMainContext* worker_context;
thread worker_thread;
void worker_loop()
{
g_main_context_push_thread_default(worker_context);
cout << "Starting main loop" << endl;
g_main_loop_run(main_loop);
cout << "Finished main loop" << endl;
g_main_context_pop_thread_default(worker_context);
}
public:
executor()
{
worker_context = g_main_context_new();
main_loop = g_main_loop_new(worker_context, false);
worker_thread = thread(&executor::worker_loop, this);
}
~executor()
{
g_main_loop_quit(main_loop);
cout << "Stopped main loop from running" << endl;
g_main_loop_unref(main_loop);
g_main_context_unref(worker_context);
if (worker_thread.joinable())
{
worker_thread.join();
}
}
void queue_callback(int (*callback)(void))
{
GSource* idle_source = g_idle_source_new();
g_source_set_callback(idle_source, (GSourceFunc)callback, NULL, NULL);
g_source_attach(idle_source, worker_context);
g_source_unref(idle_source);
}
};
int func1()
{
cout << "func1 started" << endl;
this_thread::sleep_for(5s);
cout << "func1 finished waiting" << endl;
return 0;
}
int func2()
{
cout << "func2 started" << endl;
this_thread::sleep_for(1s);
cout << "func2 finished waiting" << endl;
return 0;
}
int main()
{
executor e;
e.queue_callback(func1);
e.queue_callback(func2);
return 0;
}
When I run the program, it does not finish executing, it gets stuck. This is the output it prints.
Stopped main loop from running
Starting main loop
func1 started
func1 finished waiting
func2 started
func2 finished waiting
Sometimes it just fails with following error
Stopped main loop from running
Starting main loop
(process:16343): GLib-CRITICAL **: 10:58:54.405: g_main_loop_run: assertion 'g_atomic_int_get (&loop->ref_count) > 0' failed
Finished main loop
My guess is these both problems are happening because g_main_loop_quit
and g_main_loop_unref
run before g_main_loop_run
. How do I fix these issues?
EDIT after user7860670's comment: Thanks to user7860670 for the suggestion. Here is the working code now.
#include<glib.h>
#include<iostream>
#include<thread>
#include<chrono>
using namespace std;
using namespace std::chrono_literals;
class executor
{
private:
GMainLoop* main_loop;
GMainContext* worker_context;
thread worker_thread;
void worker_loop()
{
g_main_context_push_thread_default(worker_context);
cout << "Starting main loop" << endl;
g_main_loop_run(main_loop);
cout << "Finished main loop" << endl;
g_main_context_pop_thread_default(worker_context);
}
public:
executor()
{
worker_context = g_main_context_new();
main_loop = g_main_loop_new(worker_context, false);
worker_thread = thread(&executor::worker_loop, this);
}
~executor()
{
cout << "Stopping main loop" << endl;
GSource* idle_source = g_idle_source_new();
g_source_set_callback(idle_source, (GSourceFunc)g_main_loop_quit, main_loop, NULL);
g_source_attach(idle_source, worker_context);
g_source_unref(idle_source);
if (worker_thread.joinable())
{
worker_thread.join();
}
cout << "Removing references to main loop and context" << endl;
g_main_loop_unref(main_loop);
g_main_context_unref(worker_context);
}
void queue_callback(int (*callback)(void))
{
GSource* idle_source = g_idle_source_new();
g_source_set_callback(idle_source, (GSourceFunc)callback, NULL, NULL);
g_source_attach(idle_source, worker_context);
g_source_unref(idle_source);
}
};
int func1()
{
cout << "func1 started" << endl;
this_thread::sleep_for(5s);
cout << "func1 finished waiting" << endl;
return 0;
}
int func2()
{
cout << "func2 started" << endl;
this_thread::sleep_for(1s);
cout << "func2 finished waiting" << endl;
return 0;
}
int main()
{
executor e;
e.queue_callback(func1);
e.queue_callback(func2);
return 0;
}
It looks like executor e;
may go out of scope prior to worker thread starting to work and since at the ~executor()
you are unrefing loop and context objects prior to waiting for worker thread their reference count drops to 0 and they are getting destroyed prior to working thread working. You should do it other way around: wait for a worker thread to finish and only then unref loop and context objects.