metz·log

GKrellM, GIO and Event Loops

·

While maintaining GKrellM for Windows I have found several things in the code that I wanted to improve. Apart from the Preferences Dialog which needs quite a bit of cleanup, the biggest offender both in terms of portability as well as readability is the socket code.

Currently, the socket handling in gkrellm and gkrellmd is really low-level, it uses plain socket API, i.e. raw calls to accept(), send(), select() etc. GLib and GIO in particular have support for abstracting most of this away. They also feature asynchronous versions of most of the blocking system-calls around socket handling which is a good thing especially for GUI-applications.

So I started to get this into GKrellM…

Starting with an event loop

I began porting with gkrellmd first because this is presumably the simpler case. The biggest change needed for gkrellmd was to make it use a GLib mainloop instead of its own event loop, that one was limited to socket events.

The old loop looked a bit like the following, the complete version is available at server/main.c in git.

while (1) {
    /* Block until at least one filedescriptor needs attention or
       maximum wait time was reached */
    result = select(max_fd + 1, &test_fds, NULL, NULL, &tv);

    /* Iterate over filedescriptors and check for activity */
    for (fd = 0; fd <= max_fd; ++fd) {
        if ( is_server_fd ) {
            /* activity on listening socket */
            client_fd = accept(...);
            /* host lookup, reverse lookup etc. */
        } else {
            /* activity on client socket, try reading */
            ioctl( fd, FIONREAD, &nbytes );
            gkrellmd_client_read( fd, nbytes );
        }
    }

    /* Update krells, send updated data to clients */
    gkrellmd_update_monitors();
}

This loop works remarkably well most of the time, but it has a few shortcomings:

  • updating monitors has to wait for all socket-work to finish
  • hostname lookups while accepting new clients block the whole loop until they finish or a system-dependent timeout is reached
  • errors of ioctl() are not checked for (also nbytes is never initialized, oops)
  • the time that select() waits also defines the update frequency for the monitors

Especially the possible blocking while accepting a new client may affect monitor update timing and keep already connected clients from receiving updates. This is usually not a big problem if clients are on the same network and name lookup for the network is working reliable but that doesn’t mean things can never go wrong.

The GLib way of creating, running and deleting an event loop that already updates gkrellm monitors regularly looks like this:

g_timeout_add(1000 / _GK.update_HZ, gkrellmd_update_monitors, NULL);
gk_main_loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(gk_main_loop);
g_main_loop_unref(gk_main_loop);

Of course this does not yet have all the features of the old gkrellmd event loop but I’ll tackle that in my next post about me diving into socket-communication using GIO.