Patch to add g_child_watch_source



Here's a patch partially implementing a fix for bug #50296 (UNIX signal
main loop source.)  It adds a GSource that notifies the user that a
particular pid exited.  It allows libraries (such as libvte) to monitor
children's exit status without having a custom solution or fighting over
who installs the signal handlers.

Comments are appreciated,
-Jonathan

? testgdate
? testgdateparser
? testglib
? timeloop
? docs/glib-config.1
? gmodule/testgmodule
? gobject/testgruntime
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/glib/ChangeLog,v
retrieving revision 1.1345
diff -u -p -r1.1345 ChangeLog
--- ChangeLog	1 Jul 2003 22:15:19 -0000	1.1345
+++ ChangeLog	4 Jul 2003 05:15:49 -0000
@@ -1,3 +1,16 @@
+Fri Jul  4 00:40:35 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.
+
 2003-07-02  Matthias Clasen  <maclas gmx de>
 
 	* glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
Index: glib/gmain.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gmain.c,v
retrieving revision 1.95
diff -u -p -r1.95 gmain.c
--- glib/gmain.c	6 Jun 2003 03:54:03 -0000	1.95
+++ glib/gmain.c	4 Jul 2003 05:15:53 -0000
@@ -39,7 +39,9 @@
 #include "glib.h"
 #include "gthreadinit.h"
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <time.h>
+#include <fcntl.h>
 #ifdef HAVE_SYS_TIME_H
 #include <sys/time.h>
 #endif /* HAVE_SYS_TIME_H */
@@ -65,6 +67,7 @@
 /* Types */
 
 typedef struct _GTimeoutSource GTimeoutSource;
+typedef struct _GChildWatchSource GChildWatchSource;
 typedef struct _GPollRec GPollRec;
 typedef struct _GSourceCallback GSourceCallback;
 
@@ -157,6 +160,15 @@ struct _GTimeoutSource
   guint       interval;
 };
 
+struct _GChildWatchSource
+{
+  GSource     source;
+  gint        pid;
+  gint        child_status;
+  gint        count;
+  gboolean    child_exited;
+};
+
 struct _GPollRec
 {
   gint priority;
@@ -213,6 +225,12 @@ 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);
@@ -221,9 +239,21 @@ static gboolean g_idle_dispatch    (GSou
 				    gpointer     user_data);
 
 G_LOCK_DEFINE_STATIC (main_loop);
+G_LOCK_DEFINE_STATIC (main_context_list);
 static GMainContext *default_main_context;
 static GSList *main_contexts_without_pipe = 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 gint child_watch_count = 0;
+static gint child_watch_wake_up_pipe[2] = {0, 0};
+static GSList *main_context_list = NULL;
+
 #if defined(G_PLATFORM_WIN32) && defined(__GNUC__)
 __declspec(dllexport)
 #endif
@@ -235,6 +265,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
@@ -561,6 +599,10 @@ g_main_context_unref_and_unlock (GMainCo
       return;
     }
 
+  G_LOCK (main_context_list);
+  main_context_list = g_slist_remove (main_context_list, context);
+  G_UNLOCK (main_context_list);
+
   source = context->source_list;
   while (source)
     {
@@ -665,6 +707,7 @@ g_main_context_new ()
 {
   GMainContext *context = g_new0 (GMainContext, 1);
 
+  g_print ("in g_main_context_new\n");
 #ifdef G_THREADS_ENABLED
   g_static_mutex_init (&context->mutex);
 
@@ -699,7 +742,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;
 }
 
 /**
@@ -1512,11 +1559,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().
  *
@@ -2172,8 +2219,8 @@ g_main_context_check (GMainContext *cont
   if (!context->poll_waiting)
     {
 #ifndef G_OS_WIN32
-      gchar c;
-      read (context->wake_up_pipe[0], &c, 1);
+      gchar a;
+      read (context->wake_up_pipe[0], &a, 1);
 #endif
     }
   else
@@ -3210,6 +3257,273 @@ g_timeout_add (guint32        interval,
 			     interval, function, data, NULL);
 }
 
+/* Child watch functions */
+
+static void
+check_for_child_exited (GSource *source)
+{
+  GChildWatchSource *child_watch_source;
+  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;
+
+      if (waitpid (child_watch_source->pid, &child_status, WNOHANG) > 0)
+	{
+	  child_watch_source->child_status = child_status;
+	  child_watch_source->child_exited = TRUE;
+	}
+      child_watch_source->count = count;
+    }
+}
+
+static gboolean
+g_child_watch_prepare (GSource *source,
+		       gint    *timeout)
+{
+  GChildWatchSource *child_watch_source;
+  *timeout = -1;
+
+  child_watch_source = (GChildWatchSource *) source;
+
+  check_for_child_exited (source);
+  return child_watch_source->child_exited;
+}
+
+
+static gboolean 
+g_child_watch_check (GSource  *source)
+{
+  GChildWatchSource *child_watch_source;
+
+  child_watch_source = (GChildWatchSource *) source;
+
+  return child_watch_source->child_exited;
+}
+
+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)
+{
+  child_watch_count ++;
+  if (child_watch_init_state == CHILD_WATCH_INITIALIZED_THREADED)
+    {
+      write (child_watch_wake_up_pipe[1], "B", 1);
+    }
+  else
+    {
+      /* We count on the signal interrupting the poll in the same thread.
+       */
+    }
+}
+ 
+static void
+g_child_watch_source_init_single (void)
+{
+  g_assert (! g_thread_supported());
+  g_assert (child_watch_init_state == CHILD_WATCH_UNINITIALIZED);
+
+  child_watch_init_state = CHILD_WATCH_INITIALIZED_SINGLE;
+
+  signal (SIGCHLD, g_child_watch_signal_handler);
+}
+
+static gpointer
+child_watch_helper_thread (gpointer data)
+{
+  GPollFD fds;
+  GPollFunc poll_func;
+
+#ifdef HAVE_POLL
+      poll_func = (GPollFunc)poll;
+#else
+      poll_func = g_poll;
+#endif
+
+  fds.fd = child_watch_wake_up_pipe[0];
+  fds.events = G_IO_IN;
+
+  while (1)
+    {
+      gchar b[20];
+      GSList *list;
+
+      read (child_watch_wake_up_pipe[0], b, 20);
+
+      /* We were woken up.  Wake up all other contexts in all other threads */
+      G_UNLOCK (main_context_list);
+      for (list = main_context_list; list; list = list->next)
+	{
+	  GMainContext *context;
+
+	  context = list->data;
+	  g_main_context_wakeup (context);
+	}
+      G_LOCK (main_context_list);
+    }
+  return NULL;
+}
+
+static void
+g_child_watch_source_init_multi_threaded (void)
+{
+  GError *error = NULL;
+
+  g_assert (g_thread_supported());
+
+  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 */
+  /* FIXME: Think this through for races */
+  if (g_thread_create (child_watch_helper_thread, NULL, FALSE, &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;
+  signal (SIGCHLD, g_child_watch_signal_handler);
+}
+
+static void
+g_child_watch_source_init_promote_single_to_threaded (void)
+{
+  g_child_watch_source_init_multi_threaded ();
+}
+
+static void
+g_child_watch_source_init (void)
+{
+  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
+    {
+      if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
+	g_child_watch_source_init_single ();
+    }
+}
+
+/**
+ * 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
+ * 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.
+ *
+ * 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.
+ * 
+ * Return value: the newly-created child watch source
+ **/
+GSource *
+g_child_watch_source_new (gint pid)
+{
+  GSource *source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+  GChildWatchSource *child_watch_source = (GChildWatchSource *)source;
+
+  g_child_watch_source_init ();
+
+  child_watch_source->pid = pid;
+  child_watch_source->count = child_watch_count;
+
+  return source;
+}
+
+/**
+ * g_child_watch_add_full:
+ * @priority: the priority of the idle 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.  See g_child_watch_source_new() for
+ * additional comments regarding this source.
+ * 
+ * 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	4 Jul 2003 05:15:54 -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,9 @@ 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);
+GSource *g_child_watch_source_new (gint  pid);
+GSource *g_timeout_source_new     (guint interval);
 
 /* Miscellaneous functions
  */
@@ -278,25 +282,34 @@ 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 watchers 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_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);
+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);
 
 /* Hook for GClosure / GSource integration. Don't touch */
 GLIB_VAR GSourceFuncs g_timeout_funcs;
+GLIB_VAR GSourceFuncs g_child_watch_funcs;
 GLIB_VAR GSourceFuncs g_idle_funcs;
 
 G_END_DECLS


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