Re: GChildWatchSource take three



Owen Taylor <otaylor redhat com> writes:

> On Wed, 2003-07-30 at 14:16, Jonathan Blandford wrote:
> > Hi Owen,
> > 
> > Here's the latest version of my patch.  I added more to the docs as well
> > as fixing other things discussed with Alex.  The big question I have is
> > how best to handle ECHILD situations.  Currently, I'm passing -1 as the
> > status.  As an alternative, I can just not dispatch those sources as
> > well.
> 
> Commented on below with a few other things; despite the length of
> the comments, it looks basically pretty solid; most of this stuff
> is details, and the stuff that isn't details shouldn't be hard
> to fix.

I made most of the changes you suggested.  There were two I had problems
with.  Also, I'm assuming this won't work with win32.  Should I go ahead
and add #ifdef G_OS_UNIX guards to gmain.[ch]?

Some comments on the comments.
> ====
> +void
> +_g_main_thread_init ()
> +{
> +  add_pipes_to_main_contexts ();
> +}
> ====
> 
> Don't you need to do the SINGLE => THREADED initialization
> here? If you don't do it here, then a child watched
> added before g_thread_init() won't fire correctly 
> after it.

I don't do SINGLE => THREADED initialization here as we may not want to
use the child_watch stuff.  

> ECHILD indicates a programmatic error, and you should g_warning()
> and probably avoid dispatching. Anyways, I believe a status of -1 
> could be a legitimate exit status, so you can't use it as a flag value.

I wonder if there are ever legitimate reasons to want to get ECHILD?

> This doens't work on several aspects:
> 
>  A) g_main_context_wakeup_unlocked() is a noop
>     ifndef G_THREADS_ENABLED
> 
>  B) g_main_context_wakeup_unlocked() checks
>      if (g_thread_supported() && context->poll_waiting)
>     so it's a no-op when you call it here in the
>     single threaded case.
> 
>  C) g_main_context_wakeup_unlocked() isn't signal safe. In the
>     sequence
> 
> ===
> ifdef G_THREADS_ENABLED
>   if (!context->poll_waiting)         <= A
>     {
> #ifndef G_OS_WIN32
>       gchar c;
>       read (context->wake_up_pipe[0], &c, 1);
> #endif
>     }
>   else
>     context->poll_waiting = FALSE;   <= B
> ===
>     
>    if your signal handler runs between A and B, you
>    leak a byte into the pipe, and your main loop will
>    spin at 100% cpu because it gets left there. I think you 
>    can fix this by doing:
> 
>   if (context->wake_up_rec.revents & G_IO_IN)
>     {
>       /* Read a bunch of bytes from the pipe */
>     }
>   context->poll_waiting = FALSE;
> 
>   And then use a custom wake up function in your
>   signal handler that doesn't look at g_thread_supported().

I added the custom wake up function, but I'm not sure what you were
proposing for g_main_context_check.  I tried replacing the 
(!context->poll_waiting) test but that seemed to break timeouts.

Thanks,
-Jonathan

? testgdate
? testgdateparser
? testglib
? timeloop
? docs/glib-config.1
? gmodule/testgmodule
? gobject/testgruntime
? gobject/testoverride
? tests/child-test
? tests/core.10338
? tests/core.10389
? tests/core.1095
? tests/core.24701
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/glib/ChangeLog,v
retrieving revision 1.1404
diff -u -p -r1.1404 ChangeLog
--- ChangeLog	12 Sep 2003 20:37:07 -0000	1.1404
+++ ChangeLog	16 Sep 2003 05:03:19 -0000
@@ -1,3 +1,19 @@
+Mon Sep 15 14:10:58 2003  Jonathan Blandford  <jrb gnome org>
+
+	* glib/gmain.c (g_child_watch_source_new): Source to handle
+	SIGCHLD messages.  This way multiple libraries can monitor their
+	children's exit status without fighting over who installs the
+ 	signal handler.
+
+	* glib/gmain.c (g_child_watch_add_full): function to create a
+ 	GChildWatch source and attach it to a loop.
+
+	* glib/gmain.c (g_child_watch_add): simple function to create a
+ 	GChildWatch source and attach it to a GMainLoop.
+
+	* glib/gmain.c (g_child_watch_source_init): initialization
+ 	function for GChildWatchSource
+
 Fri Sep 12 16:26:21 2003  Owen Taylor  <otaylor redhat com>
 
 	* tests/gobject/ configure.in: New directory, framework 
Index: glib/gmain.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gmain.c,v
retrieving revision 1.100
diff -u -p -r1.100 gmain.c
--- glib/gmain.c	25 Aug 2003 16:20:41 -0000	1.100
+++ glib/gmain.c	16 Sep 2003 05:03:28 -0000
@@ -62,9 +62,14 @@
 #include <net/socket.h>
 #endif /* G_OS_BEOS */
 
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#include <fcntl.h>
+#endif
 /* Types */
 
 typedef struct _GTimeoutSource GTimeoutSource;
+typedef struct _GChildWatchSource GChildWatchSource;
 typedef struct _GPollRec GPollRec;
 typedef struct _GSourceCallback GSourceCallback;
 
@@ -157,6 +162,14 @@ struct _GTimeoutSource
   guint       interval;
 };
 
+struct _GChildWatchSource
+{
+  GSource     source;
+  gint        pid;
+  gint        child_status;
+  gint        count;
+};
+
 struct _GPollRec
 {
   gint priority;
@@ -213,16 +226,46 @@ static gboolean g_timeout_check    (GSou
 static gboolean g_timeout_dispatch (GSource     *source,
 				    GSourceFunc  callback,
 				    gpointer     user_data);
+static gboolean g_child_watch_prepare  (GSource     *source,
+				        gint        *timeout);
+static gboolean g_child_watch_check    (GSource     *source);
+static gboolean g_child_watch_dispatch (GSource     *source,
+					GSourceFunc  callback,
+					gpointer     user_data);
 static gboolean g_idle_prepare     (GSource     *source,
 				    gint        *timeout);
 static gboolean g_idle_check       (GSource     *source);
 static gboolean g_idle_dispatch    (GSource     *source,
 				    GSourceFunc  callback,
 				    gpointer     user_data);
+static void     g_child_watch_source_init_promote_single_to_threaded (void);
+
 
+/* The main_loop lock cannot be acquired while either the
+ * main_context_list lock is held, or the mutex for any
+ * individual main loop is held.
+ */
 G_LOCK_DEFINE_STATIC (main_loop);
+
 static GMainContext *default_main_context;
 static GSList *main_contexts_without_pipe = NULL;
+static GMainContext *main_context_about_to_poll = NULL;
+
+/* Child status monitoring code */
+enum {
+  CHILD_WATCH_UNINITIALIZED,
+  CHILD_WATCH_INITIALIZED_SINGLE,
+  CHILD_WATCH_INITIALIZED_THREADED
+};
+static gint child_watch_init_state = CHILD_WATCH_UNINITIALIZED;
+static volatile sig_atomic_t child_watch_count = 1;
+static gint child_watch_wake_up_pipe[2] = {0, 0};
+
+/* The main_context_list lock cannot be acquired while
+ * the mutex for any individual main loop is held.
+ */
+G_LOCK_DEFINE_STATIC (main_context_list);
+static GSList *main_context_list = NULL;
 
 #if defined(G_PLATFORM_WIN32) && defined(__GNUC__)
 __declspec(dllexport)
@@ -235,6 +278,14 @@ GSourceFuncs g_timeout_funcs =
   NULL
 };
 
+GSourceFuncs g_child_watch_funcs =
+{
+  g_child_watch_prepare,
+  g_child_watch_check,
+  g_child_watch_dispatch,
+  NULL
+};
+
 #if defined(G_PLATFORM_WIN32) && defined(__GNUC__)
 __declspec(dllexport)
 #endif
@@ -585,7 +636,19 @@ g_main_context_unref_and_unlock (GMainCo
       UNLOCK_CONTEXT (context);
       return;
     }
+  UNLOCK_CONTEXT (context);
 
+  /* The only other outstanding reference to the context at this point is main
+   * context_list, so we can unlock the context now.  It means that those who
+   * hold the lock on main_context_list (currently only the
+   * child_watch_helper_thread) have to explicitly check for a context with a
+   * ref-count of zero.
+   */
+  G_LOCK (main_context_list);
+  main_context_list = g_slist_remove (main_context_list, context);
+  G_UNLOCK (main_context_list);
+
+  LOCK_CONTEXT (context);
   source = context->source_list;
   while (source)
     {
@@ -645,7 +708,6 @@ g_main_context_unref (GMainContext *cont
   g_main_context_unref_and_unlock (context);
 }
 
-#ifdef G_THREADS_ENABLED
 static void 
 g_main_context_init_pipe (GMainContext *context)
 {
@@ -670,8 +732,12 @@ g_main_context_init_pipe (GMainContext *
   g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec);
 }
 
-void
-_g_main_thread_init ()
+
+/* called when initializing threads and the child_watch source, and
+ * when intializing the child_watch source when single threaded
+ */
+static void
+add_pipes_to_main_contexts (void)
 {
   GSList *curr = main_contexts_without_pipe;
   while (curr)
@@ -682,6 +748,17 @@ _g_main_thread_init ()
   g_slist_free (main_contexts_without_pipe);
   main_contexts_without_pipe = NULL;  
 }
+
+#ifdef G_THREADS_ENABLED
+
+void
+_g_main_thread_init ()
+{
+  add_pipes_to_main_contexts ();
+
+  if (child_watch_init_state == CHILD_WATCH_INITIALIZED_SINGLE)
+    g_child_watch_source_init_promote_single_to_threaded ();
+}
 #endif /* G_THREADS_ENABLED */
 
 /**
@@ -730,7 +807,11 @@ g_main_context_new ()
 						  context);
 #endif
 
-  return context;
+  G_LOCK (main_context_list);
+  main_context_list = g_slist_append (main_context_list, context);
+  G_UNLOCK (main_context_list);
+
+ return context;
 }
 
 /**
@@ -1543,11 +1624,11 @@ g_main_context_find_source_by_user_data 
  * g_source_remove:
  * @tag: the id of the source to remove.
  * 
- * Removes the source with the given id from the default main
- * context. The id of a #GSource is given by g_source_get_id(),
- * or will be returned by the functions g_source_attach(),
- * g_idle_add(), g_idle_add_full(), g_timeout_add(),
- * g_timeout_add_full(), g_io_add_watch, and g_io_add_watch_full().
+ * Removes the source with the given id from the default main context. The id of
+ * a #GSource is given by g_source_get_id(), or will be returned by the
+ * functions g_source_attach(), g_idle_add(), g_idle_add_full(),
+ * g_timeout_add(), g_timeout_add_full(), g_child_watch_add(),
+ * g_child_watch_add_full(), g_io_add_watch(), and g_io_add_watch_full().
  *
  * See also g_source_destroy().
  *
@@ -2358,6 +2439,17 @@ g_main_context_iterate (GMainContext *co
   
   UNLOCK_CONTEXT (context);
 
+  /* main_context_about_to_poll is entirely for the SIGCHLD source.  We normally
+   * count on SIGCHLD waking up the poll when it occurs during that time.
+   * However, if the SIGCHLD occurs between prepare and poll, we can miss it and
+   * block indefinitely, waiting for the child to exit.  To avoid this, we store
+   * the context in main_context_about_to_poll in the expectation that the
+   * signal handler can deal with it.  We expect that no code in prepare or
+   * query tries to send data to the wake up pipe, and that we can safely write
+   * a byte down it.
+   */
+
+  main_context_about_to_poll = context;
   some_ready = g_main_context_prepare (context, &max_priority); 
   
   while ((nfds = g_main_context_query (context, max_priority, &timeout, fds, 
@@ -2374,7 +2466,8 @@ g_main_context_iterate (GMainContext *co
     timeout = 0;
   
   g_main_context_poll (context, timeout, max_priority, fds, nfds);
-  
+  main_context_about_to_poll = NULL;
+
   g_main_context_check (context, max_priority, fds, nfds);
   
   if (dispatch)
@@ -3241,6 +3334,367 @@ g_timeout_add (guint32        interval,
 			     interval, function, data, NULL);
 }
 
+/* Child watch functions */
+
+static gboolean
+check_for_child_exited (GSource *source)
+{
+  GChildWatchSource *child_watch_source;
+  gboolean child_exited = FALSE;
+  gint count;
+
+  /* protect against another SIGCHLD in the middle of this call */
+  count = child_watch_count;
+
+  child_watch_source = (GChildWatchSource *) source;
+
+  if (child_watch_source->count < count)
+    {
+      gint child_status;
+      gint waitpid_status;
+
+      do {
+	   waitpid_status = waitpid (child_watch_source->pid, &child_status, WNOHANG);
+      } while (waitpid_status == (pid_t)-1 && errno == EINTR);
+      if (waitpid_status == child_watch_source->pid)
+	{
+	  child_watch_source->child_status = child_status;
+	  child_exited = TRUE;
+	}
+      else if (waitpid_status == -1)
+	{
+	  /* errno can be EINVAL or ECHILD.  I'm not sure what EINVAL could mean
+	   * here, so we assert that it isn't that. */
+	  g_assert (errno != EINVAL);
+	  g_warning ("a GChildSource was created with invalid pid (%d): %s\n",
+		     child_watch_source->pid, g_strerror (errno));
+	  /* set child_status arbitrarily to 0 */
+	  child_watch_source->child_status = 0;
+	  child_exited = TRUE;
+	}
+      child_watch_source->count = count;
+    }
+
+  return child_exited;
+}
+
+static gboolean
+g_child_watch_prepare (GSource *source,
+		       gint    *timeout)
+{
+  *timeout = -1;
+
+  return check_for_child_exited (source);
+}
+
+
+static gboolean 
+g_child_watch_check (GSource  *source)
+{
+  return check_for_child_exited (source);
+}
+
+static gboolean
+g_child_watch_dispatch (GSource    *source, 
+			GSourceFunc callback,
+			gpointer    user_data)
+{
+  GChildWatchSource *child_watch_source;
+  GChildWatchFunc child_watch_callback = (GChildWatchFunc) callback;
+
+  child_watch_source = (GChildWatchSource *) source;
+
+  if (!callback)
+    {
+      g_warning ("Child watch source dispatched without callback\n"
+		 "You must call g_source_set_callback().");
+      return FALSE;
+    }
+
+  (child_watch_callback) (child_watch_source->pid, child_watch_source->child_status, user_data);
+
+  /* We never keep a child watch source around as the child is gone */
+  return FALSE;
+}
+
+static void
+g_child_watch_signal_handler (int signum)
+{
+  /* guard against us modifying errno during the write */
+  int old_errno = errno;
+
+  child_watch_count ++;
+  
+  if (child_watch_init_state == CHILD_WATCH_INITIALIZED_THREADED)
+    {
+      write (child_watch_wake_up_pipe[1], "B", 1);
+    }
+  else
+    {
+      if (main_context_about_to_poll)
+	{
+	  if (main_context_about_to_poll->poll_waiting)
+	    {
+	      main_context_about_to_poll->poll_waiting = FALSE;
+	      write (main_context_about_to_poll->wake_up_pipe[1], "B", 1);
+	    }
+	}
+    }
+  errno = old_errno;
+}
+ 
+static void
+g_child_watch_set_up_signal_handler (void)
+{
+  sigset_t newset;
+
+  signal (SIGCHLD, g_child_watch_signal_handler);
+
+  sigemptyset (&newset);
+  sigaddset (&newset, SIGCHLD);
+  sigprocmask (SIG_UNBLOCK, &newset, NULL);
+}
+
+static void
+g_child_watch_source_init_single (void)
+{
+  g_assert (!g_thread_supported());
+  g_assert (child_watch_init_state == CHILD_WATCH_UNINITIALIZED);
+
+  add_pipes_to_main_contexts ();
+  
+  child_watch_init_state = CHILD_WATCH_INITIALIZED_SINGLE;
+
+  g_child_watch_set_up_signal_handler ();
+}
+
+/* Separate thread that sits around waiting for messages from the signal
+ * handler.
+ */
+static gpointer
+child_watch_helper_thread (gpointer data)
+{
+  while (1)
+    {
+      gchar b[128];
+      GSList *list;
+
+      read (child_watch_wake_up_pipe[0], b, sizeof(b));
+
+      /* We were woken up.  Wake up all other contexts in all other threads */
+      G_LOCK (main_context_list);
+      for (list = main_context_list; list; list = list->next)
+	{
+	  GMainContext *context;
+
+	  context = list->data;
+	  /* See the comment on locks in g_main_context_unref_and_unlock */
+	  if (context->ref_count > 0)
+	    g_main_context_wakeup (context);
+	}
+      G_UNLOCK (main_context_list);
+    }
+  return NULL;
+}
+
+#ifdef G_THREADS_ENABLED
+
+static void
+g_child_watch_source_init_multi_threaded (void)
+{
+  GError *error = NULL;
+
+  if (pipe (child_watch_wake_up_pipe) < 0)
+    g_error ("Cannot create wake up pipe: %s\n", g_strerror (errno));
+  fcntl (child_watch_wake_up_pipe[1], F_SETFL,
+	 O_NONBLOCK | fcntl (child_watch_wake_up_pipe[1], F_GETFL));
+
+  /* We create a helper thread that polls on the wakeup pipe indefinitely */
+  if (g_thread_create_full (child_watch_helper_thread,
+			    NULL, 0,
+			    FALSE, FALSE,
+			    G_THREAD_PRIORITY_NORMAL, &error) == NULL)
+    g_error ("Cannot create a thread to monitor child exit status: %s\n", error->message);
+  child_watch_init_state = CHILD_WATCH_INITIALIZED_THREADED;
+
+  g_child_watch_set_up_signal_handler ();
+}
+
+static void
+g_child_watch_source_init_promote_single_to_threaded (void)
+{
+  g_child_watch_source_init_multi_threaded ();
+}
+
+#endif
+
+/**
+ * g_child_watch_source_init:
+ * @void: 
+ * 
+ * Initializes a signal handler so that #GChildWatchSource can monitor children.
+ * In most programs it is not necessary to call this function, as the sources
+ * will initialize themselves appropriately.  The one time it can be useful is
+ * when an external SIGCHLD handler that calls waitpid() is already installed.
+ * In this case, there is a potential race between the child exiting and the
+ * source installing its own handler.
+ * 
+ **/
+void
+g_child_watch_source_init (void)
+{
+  G_LOCK_DEFINE_STATIC (child_watch_source_init_lock);
+
+  G_LOCK (child_watch_source_init_lock);
+
+#ifdef G_THREADS_ENABLED
+  if (g_thread_supported())
+    {
+      if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
+	g_child_watch_source_init_multi_threaded ();
+      else if (child_watch_init_state == CHILD_WATCH_INITIALIZED_SINGLE)
+	g_child_watch_source_init_promote_single_to_threaded ();
+    }
+  else
+    {
+#endif
+      if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
+	g_child_watch_source_init_single ();
+#ifdef G_THREADS_ENABLED
+    }
+#endif
+
+  G_UNLOCK (child_watch_source_init_lock);
+}
+
+/**
+ * g_child_watch_source_new:
+ * @pid: process id of a child process to watch
+ * 
+ * Creates a new child_watch source.  This source is used to monitor a
+ * child process.  It will be triggered once when a SIGCHLD occurs and
+ * the process specified by pid has exited.  Any application using
+ * this source should not modify the SIGCHILD signal handler.
+ * Instead, they should use a child watch source to keep track of the
+ * exit status of any children that they care about.  Additionally, as
+ * glib installs a SIGCHLD signal handler, system calls can be
+ * interrupted, and should be checked for EINTR.
+ * 
+ * The process id passed in must be a positive pid, and must point to
+ * an existing pid.  Also, when multiple sources of the same pid are
+ * added, only one of them will be called.
+ *
+ * The source will not initially be associated with any #GMainContext and must
+ * be added to one with g_source_attach() before it will be executed.  Unlike
+ * other sources, any callback associated with it will only be called at most
+ * once and no return value is expected.
+ * 
+ * <note><para> Some thread implementations are buggy and one thread
+ * cannot call waitpid() on a child created in a different thread; if
+ * you are using this functionality in a threaded program, you may
+ * need to structure your program so that child watches are always
+ * added to a GMainContext running in the same thread as where the
+ * child was created. </note></para>
+ * Return value: the newly-created child watch source
+ **/
+GSource *
+g_child_watch_source_new (gint pid)
+{
+  GSource *source;
+  GChildWatchSource *child_watch_source;
+
+  g_return_val_if_fail (pid > 0, NULL);
+
+  source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+  child_watch_source = (GChildWatchSource *)source;
+
+  g_child_watch_source_init ();
+
+  child_watch_source->pid = pid;
+  /* We want to run on the first pass no matter what, to catch the case where
+   * the child has exited before the signal handler has been installed. */
+  child_watch_source->count = child_watch_count - 1;
+
+  return source;
+}
+
+/**
+ * g_child_watch_add_full:
+ * @priority: the priority of the child watch source. Typically this will be in the
+ *            range between #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE.
+ * @pid:      process id of a child process to watch
+ * @function: function to call
+ * @data:     data to pass to @function
+ * @notify:   function to call when the idle is removed, or %NULL
+ * 
+ * Sets a function to be called when the child indicated by pid exits, at a
+ * default priority, #G_PRIORITY_DEFAULT.  See g_child_watch_source_new() for
+ * additional comments regarding this source.
+ * 
+ * Return value: the id of event source.
+ **/
+guint
+g_child_watch_add_full (gint            priority,
+			gint            pid,
+			GChildWatchFunc function,
+			gpointer        data,
+			GDestroyNotify  notify)
+{
+  GSource *source;
+  guint id;
+  
+  g_return_val_if_fail (function != NULL, 0);
+
+  source = g_child_watch_source_new (pid);
+
+  if (priority != G_PRIORITY_DEFAULT)
+    g_source_set_priority (source, priority);
+
+  g_source_set_callback (source, (GSourceFunc) function, data, notify);
+  id = g_source_attach (source, NULL);
+  g_source_unref (source);
+
+  return id;
+}
+
+/**
+ * g_child_watch_add:
+ * @pid:      process id of a child process to watch
+ * @function: function to call
+ * @data:     data to pass to @function
+ * 
+ * Sets a function to be called when the child indicated by pid exits, at a
+ * default priority, #G_PRIORITY_DEFAULT.
+ * 
+ * <informalexample><programlisting>
+ *  gint pid = fork ();
+ *  if (pid > 0)
+ *    {
+ *      g_child_watch_add (pid, function, data);
+ *    }
+ *  else if (pid == 0) /&ast; child &ast;/
+ *    {
+ *      /&ast; The child process  &ast;/
+ *      sleep (1);
+ *      _exit (0);
+ *    }
+ *  else
+ *    /&ast; error &ast;/
+ * </programlisting></informalexample>
+ *
+ * See g_child_watch_source_new() for additional inforamtion.
+ *
+ * Return value: the id of event source.
+ **/
+guint 
+g_child_watch_add (gint            pid,
+		   GChildWatchFunc function,
+		   gpointer        data)
+{
+  return g_child_watch_add_full (G_PRIORITY_DEFAULT, pid, function, data, NULL);
+}
+
+ 
 /* Idle functions */
 
 static gboolean 
Index: glib/gmain.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gmain.h,v
retrieving revision 1.16
diff -u -p -r1.16 gmain.h
--- glib/gmain.h	5 Jun 2003 22:18:27 -0000	1.16
+++ glib/gmain.h	16 Sep 2003 05:03:29 -0000
@@ -32,6 +32,9 @@ typedef struct _GSourceCallbackFuncs	GSo
 typedef struct _GSourceFuncs	        GSourceFuncs;
 
 typedef gboolean (*GSourceFunc)       (gpointer data);
+typedef void     (*GChildWatchFunc)   (gint     pid,
+				       gint     status,
+				       gpointer data);
 
 struct _GSource
 {
@@ -243,8 +246,10 @@ void     g_source_get_current_time (GSou
 
 /* Specific source types
  */
-GSource *g_idle_source_new    (void);
-GSource *g_timeout_source_new (guint         interval);
+GSource *g_idle_source_new         (void);
+void     g_child_watch_source_init (void);
+GSource *g_child_watch_source_new  (gint  pid);
+GSource *g_timeout_source_new      (guint interval);
 
 /* Miscellaneous functions
  */
@@ -278,26 +283,35 @@ gboolean g_source_remove_by_user_data   
 gboolean g_source_remove_by_funcs_user_data  (GSourceFuncs  *funcs,
 					      gpointer       user_data);
 
-/* Idles and timeouts */
-guint		g_timeout_add_full	(gint           priority,
-					 guint          interval, 
-					 GSourceFunc    function,
-					 gpointer       data,
-					 GDestroyNotify notify);
-guint		g_timeout_add		(guint          interval,
-					 GSourceFunc    function,
-					 gpointer       data);
-guint		g_idle_add	   	(GSourceFunc	function,
-					 gpointer	data);
-guint	   	g_idle_add_full		(gint   	priority,
-					 GSourceFunc	function,
-					 gpointer	data,
-					 GDestroyNotify notify);
-gboolean	g_idle_remove_by_data	(gpointer	data);
+/* Idles, child watches and timeouts */
+guint    g_timeout_add_full     (gint            priority,
+				 guint           interval,
+				 GSourceFunc     function,
+				 gpointer        data,
+				 GDestroyNotify  notify);
+guint    g_timeout_add          (guint           interval,
+				 GSourceFunc     function,
+				 gpointer        data);
+guint    g_idle_add             (GSourceFunc     function,
+				 gpointer        data);
+guint    g_idle_add_full        (gint            priority,
+				 GSourceFunc     function,
+				 gpointer        data,
+				 GDestroyNotify  notify);
+gboolean g_idle_remove_by_data  (gpointer        data);
+guint    g_child_watch_add_full (gint            priority,
+				 gint            pid,
+				 GChildWatchFunc function,
+				 gpointer        data,
+				 GDestroyNotify  notify);
+guint    g_child_watch_add      (gint            pid,
+				 GChildWatchFunc function,
+				 gpointer        data);
 
 /* Hook for GClosure / GSource integration. Don't touch */
 GLIB_VAR GSourceFuncs g_timeout_funcs;
 GLIB_VAR GSourceFuncs g_idle_funcs;
+GLIB_VAR GSourceFuncs g_child_watch_funcs;
 
 G_END_DECLS
 
Index: gobject/gsourceclosure.c
===================================================================
RCS file: /cvs/gnome/glib/gobject/gsourceclosure.c,v
retrieving revision 1.6
diff -u -p -r1.6 gsourceclosure.c
--- gobject/gsourceclosure.c	7 Feb 2003 22:04:24 -0000	1.6
+++ gobject/gsourceclosure.c	16 Sep 2003 05:03:29 -0000
@@ -140,7 +140,8 @@ closure_callback_get (gpointer     cb_da
       if (source->source_funcs == &g_io_watch_funcs)
 	closure_callback = (GSourceFunc)io_watch_closure_callback;
       else if (source->source_funcs == &g_timeout_funcs ||
-	       source->source_funcs == &g_idle_funcs)
+	       source->source_funcs == &g_idle_funcs ||
+	       source->source_funcs == &g_child_watch_funcs)
 	closure_callback = source_closure_callback;
     }
 
Index: tests/Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/tests/Makefile.am,v
retrieving revision 1.59
diff -u -p -r1.59 Makefile.am
--- tests/Makefile.am	12 Sep 2003 00:17:02 -0000	1.59
+++ tests/Makefile.am	16 Sep 2003 05:03:29 -0000
@@ -64,6 +64,7 @@ endif
 test_programs =					\
 	array-test				\
 	$(CXX_TEST)				\
+	child-test				\
 	date-test				\
 	dirname-test				\
 	file-test				\
@@ -112,6 +113,7 @@ thread_ldadd = $(libgthread) $(G_THREAD_
 module_ldadd = $(libgmodule) $(G_MODULE_LIBS) $(progs_ldadd)
 
 array_test_LDADD = $(progs_ldadd)
+child_test_LDADD = $(thread_ldadd) 
 date_test_LDADD = $(progs_ldadd)
 dirname_test_LDADD = $(progs_ldadd)
 file_test_LDADD = $(progs_ldadd)
Index: tests/child-test.c
===================================================================
RCS file: tests/child-test.c
diff -N tests/child-test.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/child-test.c	16 Sep 2003 05:03:29 -0000
@@ -0,0 +1,132 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include "glib.h"
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+extern int errno;
+static volatile sig_atomic_t outstanding_pid_count = 0;
+
+static int child_status_return = 0;
+
+#ifdef G_THREADS_ENABLED
+G_LOCK_DEFINE_STATIC (child_status_return);
+#endif
+
+gint
+get_a_child (void)
+{
+  gint pid;
+
+  outstanding_pid_count ++;
+  G_LOCK (child_status_return);
+
+  pid = fork ();
+
+  if (pid > 0) /* Parent */
+    {
+      g_print ("created pid: %d, it should exit with status %d\n", pid, child_status_return);
+      child_status_return++;
+      G_UNLOCK (child_status_return);
+      return pid;
+    }
+  else if (pid == 0) /* Child */
+    {
+      sleep (1);
+      _exit (child_status_return);
+    }
+  else /* Error */
+    {
+      g_warning ("Unable to fork: %s\n", g_strerror (errno));
+      exit (1);
+    }
+
+}
+
+static void
+child_watch_callback (gint pid, gint status, gpointer data)
+{
+  GMainLoop *loop = data;
+
+  outstanding_pid_count --;
+  g_print ("child %d exited, status %d\n", pid, WEXITSTATUS (status));
+
+  g_main_loop_quit (loop);
+}
+
+#ifdef G_THREADS_ENABLED
+static gpointer
+test_thread (gpointer data)
+{
+  GMainContext *context;
+  GMainLoop *new_main_loop;
+  GSource *source;
+  gint pid;
+
+  context = g_main_context_new ();
+  new_main_loop = g_main_loop_new (context, FALSE);
+
+  pid = get_a_child ();
+  source = g_child_watch_source_new (pid);
+  g_source_set_callback (source, (GSourceFunc) child_watch_callback, new_main_loop, NULL);
+  g_source_attach (source, g_main_loop_get_context (new_main_loop));
+  g_source_unref (source);
+
+  g_main_loop_run (new_main_loop);
+
+}
+#endif
+
+static gboolean
+check_outstanding_and_quit (gpointer data)
+{
+  if (outstanding_pid_count == 0)
+    {
+      g_main_loop_quit (data);
+      return FALSE;
+    }
+  else
+    {
+      g_warning ("We didn't get the expected SIGCHLD after waiting two seconds");
+      _exit (1);
+    }
+}
+
+int
+main (int argc, char *argv[])
+{
+  gint pid;
+  GMainLoop *main_loop;
+  guint timeout_id;
+
+  g_print ("Testing non-threaded GChildWatchSource\n");
+  main_loop = g_main_loop_new (NULL, FALSE);
+
+  pid = get_a_child ();
+  g_child_watch_add (pid, child_watch_callback, main_loop);
+
+  /* add a timeout to confirm that we get our SIGCHILD within two seconds. */
+  timeout_id = g_timeout_add (2000, check_outstanding_and_quit, main_loop);
+  g_main_loop_run (main_loop);
+
+  g_source_remove (timeout_id);
+  
+#ifdef G_THREADS_ENABLED
+  g_print ("Testing threaded GChildWatchSource\n");
+  g_thread_init (NULL);
+
+
+  g_thread_create (test_thread, NULL, FALSE, NULL);
+  g_thread_create (test_thread, NULL, FALSE, NULL);
+
+  /* add a timeout to confirm that we got both SIGCHILDs within two seconds */
+  g_timeout_add (2000, check_outstanding_and_quit, main_loop);
+  g_main_loop_run (main_loop);
+#endif
+  
+  return 0;
+}
+
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]