Singe-instance daemon to be interacted with, using GLib

,

Hi everyone,

I would like to write a single-instance daemon using GLib.

From Google searches I have found 4 ways of doing it : PID file, lock file, lock socket, D-Bus. The best options IMO are lock file and D-Bus; but I would like to use D-Bus since I would also like to interact with the daemon once launched.

I have first tried a bit to use GDBus, but I have encountered several problems. I then read about GApplication, which seems to gather all the functionality I need, but I also encountered issues while using it. So far, only the lock file option has worked flawlessly, but not allowing me to use D-Bus messaging.

Before asking you what I’m doing wrong with GDBus or GApplication, I would like to know if I’m going down the right path. Am I trying to use the right tools ?

Thanks.

GApplication is the way to go. Unless you have some niche-case not to use it, simplifying application writing as well as single-instance usage and DBus is basically what it’s for.

1 Like

Thanks for your answer Andy. Let’s dive into GApplication then.

I have some trouble wrapping my mind around how it works, despite having read the docs over and over. I couldn’t find an example code actually doing what I’m trying to achieve, so I have taken and modified code from this page. Here it is :

#include <glib.h>
#include <gio/gio.h>
#include <stdlib.h>

void countdown()
{
    for(gsize i = 5 ; i >= 1 ; i--)
    {
        g_print("%" G_GSIZE_FORMAT "\n", i);
        g_usleep(1 * G_USEC_PER_SEC);
    }
}

void dieIfRemote(GApplication *App)
{
    if(g_application_get_is_remote(App))
    {
        g_printerr("Already running.\n");
        exit(EXIT_FAILURE);
    }
}

void activate(GApplication *App)
{
    dieIfRemote(App);

    g_print("I'm activated !\n");
    countdown();
}

void startup(GApplication *App)
{
    dieIfRemote(App);

    g_print("I'm started !\n");
    countdown();
}

int main(int argc, char *argv[])
{
    GApplication *App;
    gint Status;

    App = g_application_new("io.myapp.test", 0);
    g_signal_connect(App, "activate", G_CALLBACK(activate), NULL);
    g_signal_connect(App, "startup", G_CALLBACK(startup), NULL);

    Status = g_application_run(G_APPLICATION(App), argc, argv);
    g_object_unref(App);

    return Status;
}

When I compile and run this code, I get the expected output :

$ gcc gapplication.c $(pkg-config --libs --cflags glib-2.0 gio-2.0)
$ ./a.out 
I'm started !
5
4
3
2
1
I'm activated !
5
4
3
2
1
$ 

However, if I run a second instance of this program while the first one is counting down, it hangs until the first one exits, then yields an error :

$ ./a.out 
Failed to register: GDBus.Error:org.freedesktop.DBus.Error.NoReply: Message recipient disconnected from message bus without replying
$ 

I guess dieIfRemote is never called because the second instance never shoots the startup or activate signals since it detects that it’s not the primary instance. How can I make it exit immediately then ? I have tried this but it does not work either :

int main(int argc, char *argv[])
{
    GApplication *App;
    gint Status;

    App = g_application_new("io.myapp.test", 0);
    g_signal_connect(App, "activate", G_CALLBACK(activate), NULL);
    g_signal_connect(App, "startup", G_CALLBACK(startup), NULL);

    g_application_register(App, NULL, NULL);

    dieIfRemote(App);

    Status = g_application_run(G_APPLICATION(App), argc, argv);
    g_object_unref(App);

    return Status;
} 

The second instance still hangs until the primary exits, but yields another error and runs as if it was the primary :

$ ./a.out 

(process:3552): GLib-GIO-CRITICAL **: g_application_get_is_remote: assertion 'application->priv->is_registered' failed
I'm started !
5
4
3
2
1
I'm activated !
5
4
3
2
1
$ 

What should I do to make the non-primary instances exit immediately ?

You shouldn’t need anything like dieIfRemote() since GApplication does this for you. The basic premise, IIRC, goes something like this:

  1. Init the GApplication (so init(), constructed(), etc get called)
  2. ::dbus-register is emitted and the default handler will try to register your application on DBus
  3. ::startup is possibly emitted
  4. ::activate is emitted

The second step is how GApplication decides whether it is the primary instance, because it will either fail or succeed in acquiring the DBus name. If it succeeds in acquiring the name, then ::startup is emitted and your ::startup handler should setup the “actual” application.

If it fails to acquire the DBus name, then it will just skip ::startup but still emit ::activate in the primary instance. It’s common for the ::activate signal to open an application’s main window, for example, so that if a user “launches” the application the main window will open whether the app is already running or not.

IIRC, g_application_is_remote() is only reliable after ::dbus-register has been run, and I don’t usually find it very useful for that reason. You could use this signal to register other paths on your DBus name like a GDBusObjectManager or something else.

Depending on the GApplicationFlags you pass, you may get other signals like those for the command line arguments or opening files. Some of these are commonly handled without attempting to register the DBus name, such as a --version flag which is probably statically defined in your application. Others you can pass through to the primary instance or handle in a remote instance somehow.

Does that help?

Also, you will find good examples of the various GApplication hooks here:

https://gitlab.gnome.org/GNOME/glib/tree/master/gio/tests

All the examples starting with gapplication- are good and Gtk may have similar examples that demonstrate GtkApplication which will run gtk_init() for you and generally make it easier to write GUI GApplication programs.

Hi Andy, thanks for your answers.

I have looked a bit at the gapplication- files you pointed out. The command line handling examples will indeed be useful to me in the future. Apart from that, I still don’t see an example of what I am trying to achieve.

I forgot earlier to point out that I am willing to write a daemon as part of a CLI program. So I will not be using GTK.

Following what you have said I have tried to comment out all the dieIfRemote calls, the behaviour is now that the second instance hangs until the primary instance exits, then runs as if it is the primary.

Then I have tried using the G_APPLICATION_IS_SERVICE flag. This seems to be the way to go, as any second instance exits immediately with the following error :

$ ./a.out 
Failed to register: Unable to acquire bus name 'io.myapp.test'

But now I can’t use the communication features with the primary instance.

What am I doing wrong ?

The primary instance cannot respond to the secondary instance because you are blocking the main loop for five seconds (twice).

You should at least allow GApplication to do its setup correctly, so don’t block in the startup and activated signal handlers.

For the example at hand, one options is to defer the countdown to a timeout function that is run every second:

#include <glib.h>
#include <gio/gio.h>
#include <stdlib.h>

static gboolean
countdown_func(gpointer data)
{
  GApplication *App = data;
  int counter = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(App), "counter"));

  g_print("%i\n", counter);

  if (counter == 0)
    {
      g_application_release (g_application_get_default ());
      return G_SOURCE_REMOVE;
    }

  g_object_set_data(G_OBJECT(App), "counter", GINT_TO_POINTER(--counter));

  return G_SOURCE_CONTINUE;
}

void countdown()
{
  GApplication *App = g_application_get_default();

  g_object_set_data(G_OBJECT(App), "counter", GINT_TO_POINTER(5));
  g_timeout_add_seconds(1, countdown_func, App);
  g_application_hold(App);
}

void activate(GApplication *App)
{
  g_print("I'm activated !\n");

  countdown();
}

void startup(GApplication *App)
{
  g_print("I'm started !\n");
}

int main(int argc, char *argv[])
{
  GApplication *App;
  gint Status;

  App = g_application_new("io.myapp.test", 0);

  g_signal_connect(App, "activate", G_CALLBACK(activate), NULL);
  g_signal_connect(App, "startup", G_CALLBACK(startup), NULL);

  Status = g_application_run(G_APPLICATION(App), argc, argv);
  g_object_unref(App);

  return Status;
}
2 Likes

Hi Florian,

Thanks for your answer. I’m starting to realize that I did not understand how GApplication works at all. I think it’s a bit clearer now thanks to your example.

By running your code, I see that any second instance of the program exits immediately, while sending the activate signal to the primary instance.

What I understand is that I should set up my mainloop in the startup callback, because after this signal the mainloop runs. Then, I can use the “activate” signal sent by all other instances to communicate with the primary instance. But then I don’t see how I can send data along this signal to be processed.

Am I correct ? Or should I keep reading the docs ?

Thanks for your time.

You don’t “set up a mainloop”: GApplication has its own main loop—started when you call g_application_run() in your main function, and you should never run your own loops.

You probably should.

You don’t send data over the wire directly. You have two options:

  • the secondary instance will send its command line—in part, if you need to do some processing on the secondary instance before it tries to acquire its singleton status; or whole—to the primary instance, and you can handle that using GApplication::command-line and GApplication::handle-local-options.
  • you can define actions to the primary instance

If you want to create a bidirectional channel between instances then you don’t need GApplication: use GDBus to acquire a name of the session bus and define an DBus interface that you can use to communicate between instances.

I meant setting up the event sources that will be used by the GApplication’s mainloop. Poor choice of words, sorry.

Will do now ; at least it’s way more clear in my mind now to know what to look for. Thanks to you three for your time.

Hi Robin,

Are you trying to set up communication between two processes? Maybe the second process does some file locking? If so, then you could try driver1.c and worker1.c to see if it is helpful. There is also gnuplot5.c that uses a GIO subprocess to communicate with gnuplot for graphing.

Eric

Hi Eric

Not really in this particular program, however I may need it for another one. What you linked is interesting, but I may use D-Bus messaging for that. I’m not there yet though !

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.