Why does this strange 'x io error' occur when I fork() in my GTK+ app? [GTK 2.x]

GTK+ FAQ
<<< Previous | Development with GTK+: general questions | Next >>>

# Why does this strange 'x io error' occur when I `fork()` in my GTK+ app? [GTK 2.x]

This is not really a GTK+ problem, and the problem is not related to `fork()` either. If the 'x io error' occurs then you probably use the `exit()` function in order to exit from the child process.

When GDK opens an X display, it creates a socket file descriptor. When you use the `exit()` function, you implicitly close all the open file descriptors, and the underlying X library really doesn't like this.

The right function to use here is `_exit()`.

Erik Mouw contributed the following code example to illustrate handling fork() and exit().

    /*-------------------------------------------------------------------------
     * Filename:      gtk-fork.c
     * Version:       0.99.2
     * Copyright:     Copyright (C) 1999, Erik Mouw
     * Author:        Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
     * Description:   GTK+ fork example
     * Created at:    Thu Sep 23 21:37:55 1999
     * Modified by:   Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
     * Modified at:   Thu Sep 23 22:39:39 1999
     * Modified by:   Tony Gale <gale@gtk.org>
     * Modified at:   Wed Jan 14 12:38:00 2004
     *-----------------------------------------------------------------------*/
    /*
     * Compile with:
     *
     * cc -o gtk-fork gtk-fork.c `pkg-config gtk+-2.0 --cflags --libs`
     *
     */

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <gtk/gtk.h>

    void sigchld_handler(int num)
    {
      sigset_t set, oldset;
      pid_t pid;
      int status, exitstatus;

      /* block other incoming SIGCHLD signals */
      sigemptyset(&set);
      sigaddset(&set, SIGCHLD);
      sigprocmask(SIG_BLOCK, &set, &oldset);

      /* wait for child */
      while((pid = waitpid((pid_t)-1, &status, WNOHANG)) > 0)
        {
          if(WIFEXITED(status))
    	{
    	  exitstatus = WEXITSTATUS(status);

    	  fprintf(stderr,
    		  "Parent: child exited, pid = %d, exit status = %d\n",
    		  (int)pid, exitstatus);
    	}
          else if(WIFSIGNALED(status))
    	{
    	  exitstatus = WTERMSIG(status);

    	  fprintf(stderr,
    		  "Parent: child terminated by signal %d, pid = %d\n",
    		  exitstatus, (int)pid);
    	}
          else if(WIFSTOPPED(status))
    	{
    	  exitstatus = WSTOPSIG(status);

    	  fprintf(stderr,
    		  "Parent: child stopped by signal %d, pid = %d\n",
    		  exitstatus, (int)pid);
    	}
          else
    	{
    	  fprintf(stderr,
    		  "Parent: child exited magically, pid = %d\n",
    		  (int)pid);
    	}
        }

      /* re-install the signal handler (some systems need this) */
      signal(SIGCHLD, sigchld_handler);

      /* and unblock it */
      sigemptyset(&set);
      sigaddset(&set, SIGCHLD);
      sigprocmask(SIG_UNBLOCK, &set, &oldset);
    }

    gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
    {
      return(FALSE);
    }

    void destroy(GtkWidget *widget, gpointer data)
    {
      gtk_main_quit();
    }

    void fork_me(GtkWidget *widget, gpointer data)
    {
      pid_t pid;

      pid = fork();

      if(pid == -1)
        {
          /* ouch, fork() failed */
          perror("fork");
          exit(-1);
        }
      else if(pid == 0)
        {
          /* child */
          fprintf(stderr, "Child: pid = %d\n", (int)getpid());

          execlp("ls", "ls", "-CF", "/", NULL);

          /* if exec() returns, there is something wrong */
          perror("execlp");

          /* exit child. note the use of _exit() instead of exit() */
          _exit(-1);
        }
      else
        {
          /* parent */
          fprintf(stderr, "Parent: forked a child with pid = %d\n", (int)pid);
        }
    }

    int main(int argc, char *argv[])
    {
      GtkWidget *window;
      GtkWidget *button;

      gtk_init(&argc, &argv);

      /* the basic stuff: make a window and set callbacks for destroy and
       * delete events
       */
      window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

      g_signal_connect(G_OBJECT (window), "delete_event",
    		     G_CALLBACK(delete_event), NULL);

      g_signal_connect(G_OBJECT (window), "destroy",
    		     G_CALLBACK(destroy), NULL);

    #if (GTK_MAJOR_VERSION == 1) && (GTK_MINOR_VERSION == 0)
      gtk_container_border_width(GTK_CONTAINER (window), 10);
    #else
      gtk_container_set_border_width(GTK_CONTAINER (window), 10);
    #endif

      /* add a button to do something useful */
      button = gtk_button_new_with_label("Fork me!");

      g_signal_connect(G_OBJECT (button), "clicked",
    		     G_CALLBACK(fork_me), NULL);

      gtk_container_add(GTK_CONTAINER(window), button);

      /* show everything */
      gtk_widget_show (button);
      gtk_widget_show (window);

      /* install a signal handler for SIGCHLD signals */
      signal(SIGCHLD, sigchld_handler);

      /* main loop */
      gtk_main ();

      exit(0);
    }

<<< Previous | Home | Next >>>
What's an easy way to run a function in the thread with the main loop? [GTK 2.x] | Up | Why don't the contents of a button move when the button is pressed? Here's a patch to make it work that way... [GTK 2.x]