G_subprocess_new behavior

I am working with GTK under Raspbian on a Pi4. I need to spawn a new task that will control a serial port and subsequently communicate with the GTK UI through named pipes. The intent is to make the GUI independent of any external timing issues and hopefully become more responsive.

So, my question is: When a new task is kicked off with the use of the g_subprocess_new() function, is that task just another task under Linux? Or, are the two tasks still dependent on one another in some way and thus introduce blocking?

The coding is done in ‘C’ and the intent is to monitor activity on named pipes using the select() function.

Thanks.

Hi, welcome. That’s a great question. Yes, g_subprocess_new will spawn a new process, you will be able to see it appear if you run ps or top, etc. The two processes will not block each other unless you call a function on it that says it blocks, like g_subprocess_wait or g_subprocess_communicate. Since you intend this for asynchronous communication, you probably don’t want to call those blocking functions. You can just let the process run, and if you want to monitor it to see if it exits then you can use g_subprocess_wait_async.

Here are some other notes that might be of help too:

  • You cannot use select in the same thread that GTK is running in because it will block the GUI. To communicate with a pipe from the GTK thread, you’ll probably have to use GUnixInputStream, and then either use the normal async stream operations or use g_pollable_input_stream_create_source and g_pollable_input_stream_read_nonblocking if you have some other requirements around buffering. Same goes for an output pipe which will have to use GUnixOutputStream. You can also use select from its own thread and then use another thread-safe method to communicate back to the main thread, more about that below.
  • select is somewhat of an obsolete method on Linux and poll or epoll are usually suggested in its place.
  • You can use GSubprocessLauncher along with g_subprocess_launcher_take_fd to pass an anonymous pipe fd directly to the subprocess. This way you can avoid using named pipes, if needed.
  • Subprocesses are only useful if this really needs to be in another process. If you can get away with doing the serial I/O in another thread, that might be preferable. Data can be sent to the main thread either using GAsyncQueue, or you can use g_idle_add to post work back to the main thread.

Hi Jason,

Thanks for the detailed response. I will have to play with the various options and see what works for this application. But, at least I’m headed in the right direction.

Thanks, Dave

HI Jason (again),

You said that I cannot use select() in the same thread as GTK because of blocking. However, if I use (from the Linux man page):

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

with the timeout fields set to zero, wouldn’t that result in non-blocking behavior?

Thanks, Dave

If you are only waiting for events on one fd then there is no point to that really, to do a periodic read on some fd representing an I/O port you can set O_NONBLOCK on it and then just do a read on a timer, using g_timeout_add. But I forget exactly how GPIO pins from userspace work so I’m not sure the absolute best way to do it.

Don’t ever use select(): your application will misbehave as soon as you get a large file descriptor. Libraries might opt in to large file descriptors without you knowing about it. They might do so in the future even if they don’t today. If you were not using GLib, I would tell you to use poll() or epoll() instead, but since you are using GLib, you should create a GSource and let GMainContext poll for you.

What you want is g_pollable_input_stream_create_source() or g_pollable_output_stream_create_source(). Get your GSource, then use g_source_attach() to attach it to the default main contex. When you’re done with it later, use g_source_destroy() (very confusing: removes the source, does not unref it). Don’t forget to use g_source_unref() when you’re done with it.

You can do everything on the main thread without blocking using async functions: then you don’t need to think about the underlying threading, which is error-prone. Let GIO handle the threads for you.

Hi Michael and Jason,

When I talked about using the select with the timeout fields set to zero, I was confusing the GUI application with the serial handler. Let me back up and explain what I’m trying to do in the GUI application. One reason I’d like to get this right is that there are actually two different serial interfaces controlled by the GUI.

When the program starts, it opens two FIFOs, one read (/home/pi/up.fifo) and one write (/home/pi/down.fifo). When the user selects an appropriate function, a message will be sent to the serial handler to implement that function. Meanwhile the serial handler is doing it’s own thing - polling for responses to the GUI, transferring data to a file, etc. I have the serial handler pretty well complete as I did it for a project some years ago. What’s new here is the GUI the previous program was command line based.

If someone can provide a simple example that would help me. (I’m an “old school” programmer and not used to the approach taken in the GIO/GUI world.) For example, do I open the file in the “usual” manner and obtain the file descriptor from the OS, then use that in setting up g_pollable_input_stream_create_source()? Once the communication paths are set up, they will remain active until the user closes the application.

Thanks for any pointers. Dave

Yes, that would indeed work. But remember that opening the file is a blocking operation, so it’s better to use GIO to do that: g_file_new(), then g_file_read_async(), then g_pollable_input_stream_create_source(). Actually, creating the pollable source is optional: it’s probably easier to simply call g_input_stream_read_async() until it returns 0 (EOF).

If you run into trouble, I might cook up an example, but I think you’ll probably find it easy enough if you give it a try. The GIO APIs might take some getting used to (especially if you’re working directly with GSources or GMainContext), but they’re still much easier than the old school APIs that you are already familiar with.

One more note: you can use g_unix_input_stream_new() to convert a fd into an input stream. This is especially useful if you need more control when opening the fd.

Internally the way a GUI toolkit works is not that different from any ordinary Unix service, there is a main loop that internally polls on some sockets (X11, Wayland, DBus, etc) and dispatches events based on that. In GTK you can hook into that loop and add your own fds, but the async functions will do it all for you.

Here is a simple example using g_input_stream_read_async for a fixed packet size:

#include <gio/gio.h>

#define PACKET_SIZE 32

static void
on_fifo_input (GInputStream *stream, GAsyncResult *result, gpointer user_data)
{
  g_autoptr(GError) error = NULL;
  g_autofree char *buf = user_data;
  gssize nread;

  nread = g_input_stream_read_finish (stream, result, &error);
  if (nread <= 0) {
    if (nread < 0) {
      g_warning ("Read failed: %s", error->message);
    }
    return;
  }
  /* TODO parse the packet from buf and dispatch */

  /* loop and read another packet */
  g_input_stream_read_async (stream, buf, PACKET_SIZE,
                             G_PRIORITY_DEFAULT, NULL,
                             (GAsyncReadyCallback) on_fifo_input, buf);
  /* don't free the buffer */
  g_steal_pointer (&buf);
}

static void
fifo_opened (GFile *file, GAsyncResult *result, gpointer unused)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(GFileInputStream) stream = NULL;
  char *buf = NULL;

  stream = g_file_read_finish (file, result, &error);
  if (!stream) {
    g_warning ("Open failed: %s", error->message);
    return;
  }

  buf = g_malloc (PACKET_SIZE);
  g_input_stream_read_async (G_INPUT_STREAM (stream), buf, PACKET_SIZE,
                             G_PRIORITY_DEFAULT, NULL,
                             (GAsyncReadyCallback) on_fifo_input, buf);
}

void
listen_fifo (const char *path)
{
  g_autoptr(GFile) file = g_file_new_for_path (path);
  g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback) fifo_opened, NULL);
}
1 Like

Yeah that looks good. Don’t forget to call g_object_unref() in fife_opened() to free the GFile that you allocated in listen_fifo().

Hi Jason (and Michael),

Your example is a real help! I had wandered through the documentation for the functions that Michael had mentioned and saw some that looked interesting to me. The key ones being that all of the I/O to the serial handler is line oriented. So, if it works, this would be a boon for me.

I’ve taken Jason’s code and modified it as best I could (shows how a little knowledge is a dangerous thing) and came up with the following: (Couldn’t figure out how to paste a code section. Sorry.)

#include <gtk/gtk.h>
#include <gio/gio.h>

// Copied from Jason Francis
// With attemmpts to modify for line oriented input

#define PACKET_SIZE 128

static void
on_fifo_input (GInputStream *stream, GAsyncResult *result, gpointer user_data)
{
  g_autoptr(GError) error = NULL;
  g_autofree char *buf = user_data;
  gssize nread;

  nread = g_input_stream_read_finish (stream, result, &error);
  if (nread <= 0) {
    if (nread < 0) {
      g_warning ("Read failed: %s", error->message);
    }
    return;
  }
  /* TODO parse the packet from buf and dispatch */

  //DJP - OK Got that

  /* loop and read another packet */
  g_data_input_stream_read_line_async(stream,
                             G_PRIORITY_DEFAULT, NULL,
                             (GAsyncReadyCallback) on_fifo_input, buf);
  /* don't free the buffer */
  g_steal_pointer (&buf);
}

static void
fifo_opened (GFile *file, GAsyncResult *result, gpointer unused)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(GFileInputStream) stream = NULL;
  char *buf = NULL;

  stream = g_file_read_finish (file, result, &error);
  if (!stream) {
    g_warning ("Open failed: %s", error->message);
    return;
  }

void
listen_fifo (const char *path)
{
  GFile *file = g_file_new_for_path (path);
  g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback) fifo_opened, NULL);
}

// End of Jason Francis code



GFileIOStream *OutPut;
GError *FileError;

int main (int argc, char *argv[]) {

// Setup of windows as per Glade here

    listen_fifo("/home/pi/Dacq2GUI.fifo");

    OutPut = g_file_open_readwrite(g_file_new_build_filename ("/home/pi/GUI2Dacq.fifo", NULL),
                        NULL,
                        &FileError);
    //  Check for errors, etc.

//    g_idle_add((GSourceFunc)Background, widgets);

//	gtk_window_set_default_size(GTK_WINDOW(window), 800, 400);

//    gtk_widget_show(window);
    gtk_main();
    return 0;
}


// These define what happens hen an active button is pressed
void on_btn_run_clicked(GtkButton *button, app_widgets *app_wdgts) {
    int Count;
    GError Error;

    Count = g_output_stream_write (OutPut, "Command\r", 8, NULL, &Error);

    NewState = BTN_RUN;
}

The two things of note:

When I compile, I get a type error on the first parameter to the g_data_input_stream_read_line_async() function. I just don’t know how to stitch all the right pieces together.)

Am I handling the sending of data correctly? In this case, in on_btn_run_clicked() I just want to send the string “Command\r” down to the serial handler.

Thanks for all tour help, Dave

Good catch, I edited it to a g_autoptr.

Before you read from the stream you must wrap it in a GDataInputStream using data_input_stream_new.

That will probably work for simple uses but could block if the fifo is full, so you’ll want to use g_output_stream_write_async if you want to handle that. With async writes (and reads) you cannot do more than one async operation at a single time so you may have to come up with a buffering strategy too.

Hi Jason and Michael,

You guys have been a tremendous help in getting me over the learning curve for this approach. (It literally gave me a headache, but I like the result and hope it works.) Anyway, I have worked through the various types and callbacks to get a clean compile. I’d appreciate your quick review to see if anything stands out.

Also, the messages sent out are short (20 characters or less) and only occur when a button is pressed. I doubt that the FIFO will ever get full. (But, then…)

So, here’s the code:

#include <gtk/gtk.h>
#include <gio/gio.h>

// Copied from Jason Francis
// ... modified for line oriented input

#define PACKET_SIZE 128
guint8 InBuffer[PACKET_SIZE];

static void
on_fifo_input (GDataInputStream *stream, GAsyncResult *result, gpointer user_data)
{
  g_autoptr(GError) error = NULL;
  g_autofree char *buf = user_data;
  gsize length;

  user_data = g_data_input_stream_read_line_finish (stream, result, &length, &error);
  if (length <= 0) {
    if (length < 0) {
      g_warning ("Read failed: %s", error->message);
    }
    return;
  }
  /* TODO parse the packet from buf and dispatch */

  //DJP - OK Got that

  /* loop and read another packet */
  g_data_input_stream_read_line_async(stream,
                             G_PRIORITY_DEFAULT, NULL,
                             (GAsyncReadyCallback) on_fifo_input, buf);
  /* don't free the buffer */
  g_steal_pointer (&buf);
}

static void
fifo_opened (GFile *file, GAsyncResult *result, gpointer unused)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(GFileInputStream) stream = NULL;
  char *buf = NULL;

  stream = g_file_read_finish(file, result, &error);
  if (!stream) {
    g_warning ("Open failed: %s", error->message);
    return;
  }

  buf = g_malloc (PACKET_SIZE);
  g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (stream),
                                  G_PRIORITY_DEFAULT, NULL,
                                  (GAsyncReadyCallback) on_fifo_input, buf);
}

void
listen_fifo (const char *path)
{
  GFile *file = g_file_new_for_path (path);
  g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback) fifo_opened, NULL);
}

// End of Jason Francis code



GFileIOStream *OutPut;
GError *FileError;

void GotLine(gchar *Buffer);

int main (int argc, char *argv[]) {

// Setup of windows as per Glade here

    listen_fifo("/home/pi/Dacq2GUI.fifo");

    OutPut = g_file_open_readwrite(g_file_new_build_filename ("/home/pi/GUI2Dacq.fifo", NULL),
                        NULL,
                        &FileError);
    //  Check for errors, etc.

//    g_idle_add((GSourceFunc)Background, widgets);

//	gtk_window_set_default_size(GTK_WINDOW(window), 800, 400);

//    gtk_widget_show(window);
    gtk_main();
    return 0;
}


// These define what happens hen an active button is pressed
void on_btn_run_clicked(GtkButton *button, app_widgets *app_wdgts) {
    int Count;
    GError Error;

    Count = g_output_stream_write (OutPut, "Command\r", 8, NULL, &Error);

    NewState = BTN_RUN;
}

Thanks again for your help and patience! Dave

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