[glib/gmain: 3/5] gmain: rewrite child and signal watch sources



commit 824277b57ea8dc6c9599bf3dd5a6909e59336376
Author: Ryan Lortie <desrt desrt ca>
Date:   Wed Jul 27 23:25:41 2011 -0400

    gmain: rewrite child and signal watch sources
    
    The new design no longer depends on an extra thread to coordinate the
    dispatches.  Signal watching is no longer UNIX-specific (since Windows
    has a few unix-style signals too, such as SIGINT).

 glib/glib-unix.c  |   83 -----
 glib/glib.symbols |    5 +-
 glib/gmain.c      |  965 +++++++++++++++++++++++------------------------------
 glib/tests/unix.c |   12 +-
 4 files changed, 418 insertions(+), 647 deletions(-)
---
diff --git a/glib/glib-unix.c b/glib/glib-unix.c
index 77bcea1..af02bfb 100644
--- a/glib/glib-unix.c
+++ b/glib/glib-unix.c
@@ -177,86 +177,3 @@ g_unix_set_fd_nonblocking (gint       fd,
   return g_unix_set_error_from_errno (error, EINVAL);
 #endif
 }
-
-
-/**
- * g_unix_signal_source_new:
- * @signum: A signal number
- *
- * Create a #GSource that will be dispatched upon delivery of the UNIX
- * signal @signum.  Currently only %SIGHUP, %SIGINT, and %SIGTERM can
- * be monitored.  Note that unlike the UNIX default, all sources which
- * have created a watch will be dispatched, regardless of which
- * underlying thread invoked g_unix_signal_source_new().
- *
- * For example, an effective use of this function is to handle SIGTERM
- * cleanly; flushing any outstanding files, and then calling
- * g_main_loop_quit ().  It is not safe to do any of this a regular
- * UNIX signal handler; your handler may be invoked while malloc() or
- * another library function is running, causing reentrancy if you
- * attempt to use it from the handler.  None of the GLib/GObject API
- * is safe against this kind of reentrancy.
- *
- * The interaction of this source when combined with native UNIX
- * functions like sigprocmask() is not defined.
- *
- * <note>For reliable behavior, if your program links to gthread
- * (either directly or indirectly via GObject, GIO, or a higher level
- * library), you should ensure g_thread_init() is called before using
- * this function.  For example, if your program uses GObject, call
- * g_type_init().</note>
- *
- * 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.
- *
- * Returns: A newly created #GSource
- *
- * Since: 2.30
- */
-GSource *
-g_unix_signal_source_new (int signum)
-{
-  g_return_val_if_fail (signum == SIGHUP || signum == SIGINT || signum == SIGTERM, NULL);
-
-  return _g_main_create_unix_signal_watch (signum);
-}
-
-/**
- * g_unix_signal_add_watch_full:
- * @signum: Signal number
- * @priority: the priority of the signal source. Typically this will be in
- *            the range between #G_PRIORITY_DEFAULT and #G_PRIORITY_HIGH.
- * @handler: Callback
- * @user_data: Data for @handler
- * @notify: #GDestroyNotify for @handler
- *
- * A convenience function for g_unix_signal_source_new(), which
- * attaches to the default #GMainContext.  You can remove the watch
- * using g_source_remove().
- *
- * Returns: An ID (greater than 0) for the event source
- *
- * Since: 2.30
- */
-guint
-g_unix_signal_add_watch_full (int            signum,
-                              int            priority,
-                              GSourceFunc    handler,
-                              gpointer       user_data,
-                              GDestroyNotify notify)
-{
-  guint id;
-  GSource *source;
-
-  source = g_unix_signal_source_new (signum);
-
-  if (priority != G_PRIORITY_DEFAULT)
-    g_source_set_priority (source, priority);
-
-  g_source_set_callback (source, handler, user_data, notify);
-  id = g_source_attach (source, NULL);
-  g_source_unref (source);
-
-  return id;
-}
diff --git a/glib/glib.symbols b/glib/glib.symbols
index ba24a19..db482e7 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -548,6 +548,9 @@ g_list_sort_with_data
 g_child_watch_add
 g_child_watch_add_full
 g_child_watch_source_new
+g_signal_watch_add
+g_signal_watch_add_full
+g_signal_watch_source_new
 g_get_current_time
 g_get_monotonic_time
 g_get_real_time
@@ -1564,8 +1567,6 @@ g_hostname_to_unicode
 g_unix_error_quark
 g_unix_open_pipe
 g_unix_set_fd_nonblocking
-g_unix_signal_source_new
-g_unix_signal_add_watch_full
 #endif
 g_ascii_table
 g_utf8_skip
diff --git a/glib/gmain.c b/glib/gmain.c
index 5680d66..a6c7f56 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -47,10 +47,6 @@
 #define G_MAIN_POLL_DEBUG
 #endif
 
-#ifdef G_OS_UNIX
-#include "glib-unix.h"
-#endif
-
 #include <signal.h>
 #include <sys/types.h>
 #include <time.h>
@@ -67,6 +63,8 @@
 #ifdef G_OS_WIN32
 #define STRICT
 #include <windows.h>
+#else
+#include <sched.h>
 #endif /* G_OS_WIN32 */
 
 #ifdef G_OS_BEOS
@@ -174,8 +172,6 @@
 /* Types */
 
 typedef struct _GTimeoutSource GTimeoutSource;
-typedef struct _GChildWatchSource GChildWatchSource;
-typedef struct _GUnixSignalWatchSource GUnixSignalWatchSource;
 typedef struct _GPollRec GPollRec;
 typedef struct _GSourceCallback GSourceCallback;
 
@@ -269,26 +265,6 @@ struct _GTimeoutSource
   gboolean    seconds;
 };
 
-struct _GChildWatchSource
-{
-  GSource     source;
-  GPid        pid;
-  gint        child_status;
-#ifdef G_OS_WIN32
-  GPollFD     poll;
-#else /* G_OS_WIN32 */
-  gint        count;
-  gboolean    child_exited;
-#endif /* G_OS_WIN32 */
-};
-
-struct _GUnixSignalWatchSource
-{
-  GSource     source;
-  int         signum;
-  gboolean    pending;
-};
-
 struct _GPollRec
 {
   GPollFD *fd;
@@ -343,31 +319,12 @@ static void g_main_context_remove_poll_unlocked (GMainContext *context,
 						 GPollFD      *fd);
 static void g_main_context_wakeup_unlocked      (GMainContext *context);
 
-static void _g_main_wake_up_all_contexts        (void);
-
 static gboolean g_timeout_prepare  (GSource     *source,
 				    gint        *timeout);
 static gboolean g_timeout_check    (GSource     *source);
 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);
-#ifdef G_OS_UNIX
-static gpointer unix_signal_helper_thread (gpointer data) G_GNUC_NORETURN;
-static void g_unix_signal_handler (int signum);
-static gboolean g_unix_signal_watch_prepare  (GSource     *source,
-					      gint        *timeout);
-static gboolean g_unix_signal_watch_check    (GSource     *source);
-static gboolean g_unix_signal_watch_dispatch (GSource     *source,
-					      GSourceFunc  callback,
-					      gpointer     user_data);
-static void     g_unix_signal_watch_finalize  (GSource     *source);
-#endif
 static gboolean g_idle_prepare     (GSource     *source,
 				    gint        *timeout);
 static gboolean g_idle_check       (GSource     *source);
@@ -379,48 +336,6 @@ G_LOCK_DEFINE_STATIC (main_loop);
 static GMainContext *default_main_context;
 static GSList *main_contexts_without_pipe = NULL;
 
-#ifndef G_OS_WIN32
-
-/* The UNIX signal pipe contains a single byte specifying which
- * signal was received.
- */ 
-#define _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR 'C'
-#define _UNIX_SIGNAL_PIPE_SIGHUP_CHAR  'H'
-#define _UNIX_SIGNAL_PIPE_SIGINT_CHAR  'I'
-#define _UNIX_SIGNAL_PIPE_SIGTERM_CHAR 'T'
-/* Guards all the data below */ 
-G_LOCK_DEFINE_STATIC (unix_signal_lock);
-enum {
-  UNIX_SIGNAL_UNINITIALIZED = 0,
-  UNIX_SIGNAL_INITIALIZED_SINGLE,
-  UNIX_SIGNAL_INITIALIZED_THREADED
-};
-static gint unix_signal_init_state = UNIX_SIGNAL_UNINITIALIZED;
-typedef struct {
-  /* These are only used in the UNIX_SIGNAL_INITIALIZED_SINGLE case */
-  gboolean sighup_delivered : 1;
-  gboolean sigint_delivered : 1;
-  gboolean sigterm_delivered : 1;
-} UnixSignalState;
-static sigset_t unix_signal_mask;
-static UnixSignalState unix_signal_state;
-static gint unix_signal_wake_up_pipe[2];
-GSList *unix_signal_watches;
-
-/* Not guarded ( FIXME should it be? ) */
-static gint child_watch_count = 1;
-
-static GSourceFuncs g_unix_signal_funcs =
-{
-  g_unix_signal_watch_prepare,
-  g_unix_signal_watch_check,
-  g_unix_signal_watch_dispatch,
-  g_unix_signal_watch_finalize
-};
-#endif /* !G_OS_WIN32 */
-G_LOCK_DEFINE_STATIC (main_context_list);
-static GSList *main_context_list = NULL;
-
 GSourceFuncs g_timeout_funcs =
 {
   g_timeout_prepare,
@@ -429,14 +344,6 @@ GSourceFuncs g_timeout_funcs =
   NULL
 };
 
-GSourceFuncs g_child_watch_funcs =
-{
-  g_child_watch_prepare,
-  g_child_watch_check,
-  g_child_watch_dispatch,
-  NULL
-};
-
 GSourceFuncs g_idle_funcs =
 {
   g_idle_prepare,
@@ -488,10 +395,6 @@ g_main_context_unref (GMainContext *context)
   if (!g_atomic_int_dec_and_test (&context->ref_count))
     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)
     {
@@ -595,16 +498,11 @@ g_main_context_new (void)
     main_contexts_without_pipe = g_slist_prepend (main_contexts_without_pipe, 
 						  context);
 
-  G_LOCK (main_context_list);
-  main_context_list = g_slist_append (main_context_list, context);
-
 #ifdef G_MAIN_POLL_DEBUG
   if (_g_main_poll_debug)
     g_print ("created context=%p\n", context);
 #endif
 
-  G_UNLOCK (main_context_list);
-
   return context;
 }
 
@@ -3670,25 +3568,6 @@ g_main_context_get_poll_func (GMainContext *context)
   return result;
 }
 
-static void
-_g_main_wake_up_all_contexts (void)
-{
-  GSList *list;
-
-  /* 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 = list->data;
-
-      LOCK_CONTEXT (context);
-      g_main_context_wakeup_unlocked (context);
-      UNLOCK_CONTEXT (context);
-    }
-  G_UNLOCK (main_context_list);
-}
-
-
 /* HOLDS: context's lock */
 /* Wake the main loop up from a poll() */
 static void
@@ -4098,472 +3977,271 @@ g_timeout_add_seconds (guint       interval,
   return g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, interval, function, data, NULL);
 }
 
-/* Child watch functions */
-
-#ifdef G_OS_WIN32
-
-static gboolean
-g_child_watch_prepare (GSource *source,
-		       gint    *timeout)
-{
-  *timeout = -1;
-  return FALSE;
-}
-
 
-static gboolean 
-g_child_watch_check (GSource  *source)
+typedef struct _GChildWatchSource GChildWatchSource;
+struct _GChildWatchSource
 {
-  GChildWatchSource *child_watch_source;
-  gboolean child_exited;
-
-  child_watch_source = (GChildWatchSource *) source;
+  GSource  source;
 
-  child_exited = child_watch_source->poll.revents & G_IO_IN;
+  GPid     pid;
+  gboolean exited;
+  gint     child_status;
 
-  if (child_exited)
-    {
-      DWORD child_status;
-
-      /*
-       * Note: We do _not_ check for the special value of STILL_ACTIVE
-       * since we know that the process has exited and doing so runs into
-       * problems if the child process "happens to return STILL_ACTIVE(259)"
-       * as Microsoft's Platform SDK puts it.
-       */
-      if (!GetExitCodeProcess (child_watch_source->pid, &child_status))
-        {
-	  gchar *emsg = g_win32_error_message (GetLastError ());
-	  g_warning (G_STRLOC ": GetExitCodeProcess() failed: %s", emsg);
-	  g_free (emsg);
+#ifdef G_OS_WIN32
+  GPollFD  pollfd;
+#endif
 
-	  child_watch_source->child_status = -1;
-	}
-      else
-	child_watch_source->child_status = child_status;
-    }
+  volatile GChildWatchSource *next;
+};
 
-  return child_exited;
-}
+G_LOCK_DEFINE_STATIC (g_child_watch_sources);
+static volatile GChildWatchSource *g_child_watch_sources;
 
-#else /* G_OS_WIN32 */
+#ifndef G_OS_WIN32
+static gint g_child_watch_signal_handler_running;
 
-static gboolean
-check_for_child_exited (GSource *source)
+static void
+g_child_watch_signal_handler (int        signum,
+                            siginfo_t *info,
+                            void      *context)
 {
-  GChildWatchSource *child_watch_source;
-  gint count;
+  volatile GChildWatchSource *source;
 
-  /* protect against another SIGCHLD in the middle of this call */
-  count = child_watch_count;
+  if (signum != SIGCHLD)
+    return;
 
-  child_watch_source = (GChildWatchSource *) source;
+  g_atomic_int_add (&g_child_watch_signal_handler_running, +1);
 
-  if (child_watch_source->child_exited)
-    return TRUE;
+  for (source = g_child_watch_sources; source; source = source->next)
+    if (!source->exited && source->pid == info->si_pid)
+      {
+        GMainContext *to_wake;
 
-  if (child_watch_source->count < count)
-    {
-      gint child_status;
+        waitpid (info->si_pid, NULL, 0);
 
-      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;
-    }
+        source->child_status = info->si_status;
+        source->exited = TRUE;
 
-  return child_watch_source->child_exited;
-}
+        to_wake = source->source.context;
 
-static gboolean
-g_child_watch_prepare (GSource *source,
-		       gint    *timeout)
-{
-  *timeout = -1;
+        if (to_wake && to_wake->wakeup)
+          g_wakeup_signal (to_wake->wakeup);
 
-  return check_for_child_exited (source);
-}
+        break;
+      }
 
-static gboolean 
-g_child_watch_check (GSource  *source)
-{
-  return check_for_child_exited (source);
+  g_atomic_int_add (&g_child_watch_signal_handler_running, -1);
 }
 
-static gboolean
-check_for_signal_delivery (GSource *source)
+static void
+g_child_watch_install_signal_handler (void)
 {
-  GUnixSignalWatchSource *unix_signal_source = (GUnixSignalWatchSource*) source;
-  gboolean delivered;
+  static gboolean installed;
 
-  G_LOCK (unix_signal_lock);
-  if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_SINGLE)
-    {
-      switch (unix_signal_source->signum)
-	{
-	case SIGHUP:
-	  delivered = unix_signal_state.sighup_delivered;
-	  break;
-	case SIGINT:
-	  delivered = unix_signal_state.sigint_delivered;
-	  break;
-	case SIGTERM:
-	  delivered = unix_signal_state.sigterm_delivered;
-	  break;
-	default:
-	  g_assert_not_reached ();
-	  delivered = FALSE;
-	  break;
-	}
-    }
-  else
+  if (!installed)
     {
-      g_assert (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED);
-      delivered = unix_signal_source->pending;
-    }
-  G_UNLOCK (unix_signal_lock);
+      struct sigaction action;
 
-  return delivered;
-}
+      action.sa_sigaction = g_child_watch_signal_handler;
+      action.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
+      sigfillset (&action.sa_mask);
+      sigaction (SIGCHLD, &action, NULL);
 
-static gboolean
-g_unix_signal_watch_prepare (GSource *source,
-			     gint    *timeout)
-{
-  *timeout = -1;
-
-  return check_for_signal_delivery (source);
-}
-
-static gboolean 
-g_unix_signal_watch_check (GSource  *source)
-{
-  return check_for_signal_delivery (source);
+      installed = TRUE;
+    }
 }
+#endif /* ndef G_OS_WIN32 */
 
 static gboolean
-g_unix_signal_watch_dispatch (GSource    *source, 
-			      GSourceFunc callback,
-			      gpointer    user_data)
+g_child_watch_list_add (GChildWatchSource *source,
+                      GPid              pid)
 {
-  GUnixSignalWatchSource *unix_signal_source;
+  volatile GChildWatchSource *tmp;
+  gboolean unique;
 
-  unix_signal_source = (GUnixSignalWatchSource *) source;
+  G_LOCK(g_child_watch_sources);
 
-  if (!callback)
-    {
-      g_warning ("Unix signal source dispatched without callback\n"
-		 "You must call g_source_set_callback().");
-      return FALSE;
-    }
+  unique = TRUE;
+  for (tmp = g_child_watch_sources; tmp; tmp = tmp->next)
+    if (tmp->pid == pid)
+      {
+        unique = FALSE;
+        break;
+      }
 
-  (callback) (user_data);
-  
-  G_LOCK (unix_signal_lock);
-  if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_SINGLE)
+  if G_LIKELY (unique)
     {
-      switch (unix_signal_source->signum)
-	{
-	case SIGHUP:
-	  unix_signal_state.sighup_delivered = FALSE;
-	  break;
-	case SIGINT:
-	  unix_signal_state.sigint_delivered = FALSE;
-	  break;
-	case SIGTERM:
-	  unix_signal_state.sigterm_delivered = FALSE;
-	  break;
-	}
+      source->next = g_child_watch_sources;
+      g_child_watch_sources = source;
+      source->pid = pid;
     }
+
   else
-    {
-      g_assert (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED);
-      unix_signal_source->pending = FALSE;
-    }
-  G_UNLOCK (unix_signal_lock);
+    g_critical ("Ignoring attempt to add child watch for already-watched GPid");
 
-  return TRUE;
+  G_UNLOCK(g_child_watch_sources);
+
+  return unique;
 }
 
 static void
-ensure_unix_signal_handler_installed_unlocked (int signum)
+g_child_watch_list_remove (GChildWatchSource *source)
 {
-  struct sigaction action;
-  GError *error = NULL;
+  volatile GChildWatchSource * volatile *tmp;
 
-  if (unix_signal_init_state == UNIX_SIGNAL_UNINITIALIZED)
-    {
-      sigemptyset (&unix_signal_mask);
-    }
+  G_LOCK(g_child_watch_sources);
 
-  if (unix_signal_init_state == UNIX_SIGNAL_UNINITIALIZED
-      || unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_SINGLE)
-    {
-      if (!g_thread_supported ())
-	{
-	  /* There is nothing to do for initializing in the non-threaded
-	   * case.
-	   */
-	  if (unix_signal_init_state == UNIX_SIGNAL_UNINITIALIZED)
-	    unix_signal_init_state = UNIX_SIGNAL_INITIALIZED_SINGLE;
-	}
-      else
-	{
-	  if (!g_unix_open_pipe (unix_signal_wake_up_pipe, FD_CLOEXEC, &error))
-	    g_error ("Cannot create UNIX signal wake up pipe: %s\n", error->message);
-	  g_unix_set_fd_nonblocking (unix_signal_wake_up_pipe[1], TRUE, NULL);
-	  
-	  /* We create a helper thread that polls on the wakeup pipe indefinitely */
-	  if (g_thread_create (unix_signal_helper_thread, NULL, FALSE, &error) == NULL)
-	    g_error ("Cannot create a thread to monitor UNIX signals: %s\n", error->message);
-	  
-	  unix_signal_init_state = UNIX_SIGNAL_INITIALIZED_THREADED;
-	}
-    }
-
-  if (sigismember (&unix_signal_mask, signum))
-    return;
+  for (tmp = &g_child_watch_sources; *tmp; tmp = &(*tmp)->next)
+    if (*tmp == source)
+      {
+        *tmp = source->next;
+        break;
+      }
 
-  sigaddset (&unix_signal_mask, signum);
+  G_UNLOCK(g_child_watch_sources);
 
-  action.sa_handler = g_unix_signal_handler;
-  sigemptyset (&action.sa_mask);
-  action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
-  sigaction (signum, &action, NULL);
+#ifndef G_OS_WIN32
+  /* This delays the finalization of a GChildWatchSource in the unlikely
+   * case that a SIGCHLD signal handler is running in another thread at
+   * this moment.  If it is, we must wait for it to finish.
+   *
+   * The signal handler is unable to acquire locks, so it accesses the
+   * list without one.  We must ensure that any GGChildWatchSource that
+   * the handler may have seen remains valid for the duration of the run
+   * of the handler.
+   *
+   * This ensures that the handler doesn't crash when iterating the
+   * list, but it also ensures that the GMainContext of the source is
+   * still valid for waking up.  In fact, it's quite possible that the
+   * reason the source is being finalized is because the GMainContext is
+   * being freed -- this blocks that from happening until it is safe, as
+   * well.
+   *
+   * As a consequence, the g_wakeup_free() in GMainContext's unref
+   * function must follow the source frees.
+   *
+   * If sched_yield() here ever becomes problematic, a somewhat more
+   * complicated alternative to this would be creation of a utility
+   * struct (separate from the GSource) and adding that to the linked
+   * list.  GWakeup could be made to support refcounts, and the struct
+   * could hold a reference on the GWakeup of the appropriate
+   * GMainContext.  In the event that the signal handler was found to be
+   * running at the time the source was being destroyed then the utility
+   * struct could be added to a list of structs to be freed later.  This
+   * would allow the GWakeup to potentially exist after the GMainContext
+   * was already destroyed (and to be safely called).  In many ways this
+   * would be cleaner, but the sched_yield() approach is much easier,
+   * and somewhat unlikely to actually be hit.
+   */
+  while G_UNLIKELY (g_child_watch_signal_handler_running > 0)
+    sched_yield ();
+#endif /* ndef G_OS_WIN32 */
 }
 
-GSource *
-_g_main_create_unix_signal_watch (int signum)
+static gboolean
+g_child_watch_prepare (GSource *gsource,
+                     gint    *timeout)
 {
-  GSource *source;
-  GUnixSignalWatchSource *unix_signal_source;
+  GChildWatchSource *source = (GChildWatchSource *) gsource;
 
-  source = g_source_new (&g_unix_signal_funcs, sizeof (GUnixSignalWatchSource));
-  unix_signal_source = (GUnixSignalWatchSource *) source;
-
-  unix_signal_source->signum = signum;
-  unix_signal_source->pending = FALSE;
-
-  G_LOCK (unix_signal_lock);
-  ensure_unix_signal_handler_installed_unlocked (signum);
-  unix_signal_watches = g_slist_prepend (unix_signal_watches, unix_signal_source);
-  G_UNLOCK (unix_signal_lock);
+  *timeout = -1;
 
-  return source;
+  return source->exited;
 }
 
-static void 
-g_unix_signal_watch_finalize (GSource    *source)
+static gboolean
+g_child_watch_check (GSource *gsource)
 {
-  G_LOCK (unix_signal_lock);
-  unix_signal_watches = g_slist_remove (unix_signal_watches, source);
-  G_UNLOCK (unix_signal_lock);
-}
+  GChildWatchSource *source = (GChildWatchSource *) gsource;
 
-#endif /* G_OS_WIN32 */
+#ifdef G_OS_WIN32
+  if (!source->exited && source->pollfd.revents == G_IO_IN)
+    {
+      DWORD child_status;
+
+      /*
+       * Note: We do _not_ check for the special value of STILL_ACTIVE
+       * since we know that the process has exited and doing so runs into
+       * problems if the child process "happens to return STILL_ACTIVE(259)"
+       * as Microsoft's Platform SDK puts it.
+       */
+      if (!GetExitCodeProcess (source->pid, &child_status))
+        {
+          gchar *emsg = g_win32_error_message (GetLastError ());
+          g_warning (G_STRLOC ": GetExitCodeProcess() failed: %s", emsg);
+          g_free (emsg);
+
+          source->child_status = -1;
+        }
+      else
+        source->child_status = child_status;
+    }
+#endif /* def G_OS_WIN32 */
+
+  return source->exited;
+}
 
 static gboolean
-g_child_watch_dispatch (GSource    *source, 
-			GSourceFunc callback,
-			gpointer    user_data)
+g_child_watch_dispatch (GSource     *gsource,
+                      GSourceFunc  callback,
+                      gpointer     user_data)
 {
-  GChildWatchSource *child_watch_source;
-  GChildWatchFunc child_watch_callback = (GChildWatchFunc) callback;
-
-  child_watch_source = (GChildWatchSource *) source;
+  GChildWatchSource *source = (GChildWatchSource *) gsource;
+  GChildWatchFunc g_child_watch_callback = (GChildWatchFunc) callback;
 
   if (!callback)
     {
       g_warning ("Child watch source dispatched without callback\n"
-		 "You must call g_source_set_callback().");
+                 "You must call g_source_set_callback().");
       return FALSE;
     }
 
-  (child_watch_callback) (child_watch_source->pid, child_watch_source->child_status, user_data);
+  (* g_child_watch_callback) (source->pid, source->child_status, user_data);
 
   /* We never keep a child watch source around as the child is gone */
   return FALSE;
 }
 
-#ifndef G_OS_WIN32
-
 static void
-g_unix_signal_handler (int signum)
+g_child_watch_finalize (GSource *gsource)
 {
-  if (signum == SIGCHLD)
-    child_watch_count ++;
+  GChildWatchSource *source = (GChildWatchSource *) gsource;
 
-  if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED)
-    {
-      char buf[1];
-      switch (signum)
-	{
-	case SIGCHLD:
-	  buf[0] = _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR;
-	  break;
-	case SIGHUP:
-	  buf[0] = _UNIX_SIGNAL_PIPE_SIGHUP_CHAR;
-	  break;
-	case SIGINT:
-	  buf[0] = _UNIX_SIGNAL_PIPE_SIGINT_CHAR;
-	  break;
-	case SIGTERM:
-	  buf[0] = _UNIX_SIGNAL_PIPE_SIGTERM_CHAR;
-	  break;
-	default:
-	  /* Shouldn't happen */
-	  return;
-	}
-      write (unix_signal_wake_up_pipe[1], buf, 1);
-    }
-  else
-    {
-      /* We count on the signal interrupting the poll in the same thread. */
-      switch (signum)
-	{
-	case SIGCHLD:
-	  /* Nothing to do - the handler will call waitpid() */
-	  break;
-	case SIGHUP:
-	  unix_signal_state.sighup_delivered = TRUE;
-	  break;
-	case SIGINT:
-	  unix_signal_state.sigint_delivered = TRUE;
-	  break;
-	case SIGTERM:
-	  unix_signal_state.sigterm_delivered = TRUE;
-	  break;
-	default:
-	  g_assert_not_reached ();
-	  break;
-	}
-    }
-}
- 
-static void
-deliver_unix_signal (int signum)
-{
-  GSList *iter;
-  g_assert (signum == SIGHUP || signum == SIGINT || signum == SIGTERM);
-
-  G_LOCK (unix_signal_lock);
-  for (iter = unix_signal_watches; iter; iter = iter->next)
-    {
-      GUnixSignalWatchSource *source = iter->data;
-
-      if (source->signum != signum)
-	continue;
-      
-      source->pending = TRUE;
-    }
-  G_UNLOCK (unix_signal_lock);
+  g_child_watch_list_remove (source);
 }
 
-/*
- * This thread is created whenever anything in GLib needs
- * to deal with UNIX signals; at present, just SIGCHLD
- * from g_child_watch_source_new().
- *
- * Note: We could eventually make this thread a more public interface
- * and allow e.g. GDBus to use it instead of its own worker thread.
- */
-static gpointer
-unix_signal_helper_thread (gpointer data) 
-{
-  while (1)
-    {
-      gchar b[128];
-      ssize_t i, bytes_read;
-      gboolean sigterm_received = FALSE;
-      gboolean sigint_received = FALSE;
-      gboolean sighup_received = FALSE;
-
-      bytes_read = read (unix_signal_wake_up_pipe[0], b, sizeof (b));
-      if (bytes_read < 0)
-	{
-	  g_warning ("Failed to read from child watch wake up pipe: %s",
-		     strerror (errno));
-	  /* Not much we can do here sanely; just wait a second and hope
-	   * it was transient.
-	   */
-	  g_usleep (G_USEC_PER_SEC);
-	  continue;
-	}
-      for (i = 0; i < bytes_read; i++)
-	{
-	  switch (b[i])
-	    {
-	    case _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR:
-	      /* The child watch source will call waitpid() in its
-	       * prepare() and check() methods; however, we don't
-	       * know which pid exited, so we need to wake up
-	       * all contexts.  Note: actually we could get the pid
-	       * from the "siginfo_t" via the handler, but to pass
-	       * that info down the pipe would require a more structured
-	       * data stream (as opposed to a single byte).
-	       */
-	      break;
-	    case _UNIX_SIGNAL_PIPE_SIGTERM_CHAR:
-	      sigterm_received = TRUE;
-	      break;
-	    case _UNIX_SIGNAL_PIPE_SIGHUP_CHAR:
-	      sighup_received = TRUE;
-	      break;
-	    case _UNIX_SIGNAL_PIPE_SIGINT_CHAR:
-	      sigint_received = TRUE;
-	      break;
-	    default:
-	      g_warning ("Invalid char '%c' read from child watch pipe", b[i]);
-	      break;
-	    }
-	}
-      if (sigterm_received)
-	deliver_unix_signal (SIGTERM);
-      if (sigint_received)
-	deliver_unix_signal (SIGINT);
-      if (sighup_received)
-	deliver_unix_signal (SIGHUP);
-      _g_main_wake_up_all_contexts ();
-    }
-}
-
-static void
-g_child_watch_source_init (void)
+GSourceFuncs g_child_watch_funcs =
 {
-  G_LOCK (unix_signal_lock);
-  ensure_unix_signal_handler_installed_unlocked (SIGCHLD);
-  G_UNLOCK (unix_signal_lock);
-}
-
-#endif /* !G_OS_WIN32 */
+  g_child_watch_prepare,
+  g_child_watch_check,
+  g_child_watch_dispatch,
+  g_child_watch_finalize
+};
 
 /**
  * g_child_watch_source_new:
  * @pid: process to watch. On POSIX the pid of a child process. On
  * Windows a handle for a process (which doesn't have to be a child).
- * 
+ *
  * Creates a new child_watch source.
  *
  * 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.
- * 
+ *
  * Note that child watch sources can only be used in conjunction with
  * <literal>g_spawn...</literal> when the %G_SPAWN_DO_NOT_REAP_CHILD
  * flag is used.
  *
- * Note that on platforms where #GPid must be explicitly closed
- * (see g_spawn_close_pid()) @pid must not be closed while the
- * source is still active. Typically, you will want to call
- * g_spawn_close_pid() in the callback function for the source.
+ * Note that on platforms where #GPid must be explicitly closed (see
+ * g_spawn_close_pid()) @pid must not be closed while the source is
+ * still active. Typically, you will want to call g_spawn_close_pid() in
+ * the callback function for the source.
+ *
+ * Note further that using g_child_watch_source_new() is not compatible
+ * with calling <literal>waitpid(-1)</literal> in the application.
+ * Calling waitpid() for individual pids will still work fine.
  *
- * Note further that using g_child_watch_source_new() is not 
- * compatible with calling <literal>waitpid(-1)</literal> in 
- * the application. Calling waitpid() for individual pids will
- * still work fine. 
- * 
  * Return value: the newly-created child watch source
  *
  * Since: 2.4
@@ -4571,21 +4249,38 @@ g_child_watch_source_init (void)
 GSource *
 g_child_watch_source_new (GPid pid)
 {
-  GSource *source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
-  GChildWatchSource *child_watch_source = (GChildWatchSource *)source;
+  GChildWatchSource *source;
+  GSource *gsource;
+
+  gsource = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+  source = (GChildWatchSource *) gsource;
 
+  if (g_child_watch_list_add (source, pid))
+    {
 #ifdef G_OS_WIN32
-  child_watch_source->poll.fd = (gintptr) pid;
-  child_watch_source->poll.events = G_IO_IN;
+      source->pollfd.fd = (gintptr) pid;
+      source->pollfd.events = G_IO_IN;
 
-  g_source_add_poll (source, &child_watch_source->poll);
-#else /* G_OS_WIN32 */
-  g_child_watch_source_init ();
-#endif /* G_OS_WIN32 */
+      g_source_add_poll (gsource, &source->pollfd);
+#else /* def G_OS_WIN32 */
+      pid_t ret;
 
-  child_watch_source->pid = pid;
+      g_child_watch_install_signal_handler ();
 
-  return source;
+      ret = waitpid (pid, &source->child_status, WNOHANG);
+
+      if (ret == -1)
+        {
+          g_critical ("Checking for child process %d: %s", pid, g_strerror (errno));
+          g_child_watch_list_remove (source);
+        }
+
+      else if (ret == pid)
+        source->exited = TRUE;
+#endif /* ndef G_OS_WIN32 */
+  }
+
+  return gsource;
 }
 
 /**
@@ -4593,29 +4288,30 @@ g_child_watch_source_new (GPid pid)
  * @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 to watch. On POSIX the pid of a child process. On
- * Windows a handle for a process (which doesn't have to be a child).
+ *            Windows a handle for a process (which doesn't have to be a
+ *            child).
  * @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 the priority @priority.
  *
- * If you obtain @pid from g_spawn_async() or g_spawn_async_with_pipes() 
- * you will need to pass #G_SPAWN_DO_NOT_REAP_CHILD as flag to 
- * the spawn function for the child watching to work.
- * 
- * Note that on platforms where #GPid must be explicitly closed
- * (see g_spawn_close_pid()) @pid must not be closed while the
- * source is still active. Typically, you will want to call
- * g_spawn_close_pid() in the callback function for the source.
- * 
+ * Sets a function to be called when the child indicated by @pid exits,
+ * at the priority @priority.
+ *
+ * If you obtain @pid from g_spawn_async() or g_spawn_async_with_pipes()
+ * you will need to pass #G_SPAWN_DO_NOT_REAP_CHILD as flag to the spawn
+ * function for the child watching to work.
+ *
+ * Note that on platforms where #GPid must be explicitly closed (see
+ * g_spawn_close_pid()) @pid must not be closed while the source is
+ * still active. Typically, you will want to call g_spawn_close_pid() in
+ * the callback function for the source.
+ *
  * GLib supports only a single callback per process id.
  *
- * This internally creates a main loop source using 
- * g_child_watch_source_new() and attaches it to the main loop context 
- * using g_source_attach(). You can do these steps manually if you 
- * need greater control.
+ * This internally creates a main loop source using
+ * g_child_watch_source_new() and attaches it to the main loop context
+ * using g_source_attach(). You can do these steps manually if you need
+ * greater control.
  *
  * Return value: the ID (greater than 0) of the event source.
  *
@@ -4624,14 +4320,14 @@ g_child_watch_source_new (GPid pid)
  **/
 guint
 g_child_watch_add_full (gint            priority,
-			GPid            pid,
-			GChildWatchFunc function,
-			gpointer        data,
-			GDestroyNotify  notify)
+                        GPid            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);
@@ -4649,41 +4345,206 @@ g_child_watch_add_full (gint            priority,
 /**
  * g_child_watch_add:
  * @pid:      process id to watch. On POSIX the pid of a child process. On
- * Windows a handle for a process (which doesn't have to be a child).
+ *            Windows a handle for a process (which doesn't have to be a
+ *            child).
  * @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.
- * 
- * If you obtain @pid from g_spawn_async() or g_spawn_async_with_pipes() 
- * you will need to pass #G_SPAWN_DO_NOT_REAP_CHILD as flag to 
- * the spawn function for the child watching to work.
- * 
- * Note that on platforms where #GPid must be explicitly closed
- * (see g_spawn_close_pid()) @pid must not be closed while the
- * source is still active. Typically, you will want to call
- * g_spawn_close_pid() in the callback function for the source.
+ *
+ * Sets a function to be called when the child indicated by @pid exits,
+ * at a default priority, #G_PRIORITY_DEFAULT.
+ *
+ * If you obtain @pid from g_spawn_async() or g_spawn_async_with_pipes()
+ * you will need to pass #G_SPAWN_DO_NOT_REAP_CHILD as flag to the spawn
+ * function for the child watching to work.
+ *
+ * Note that on platforms where #GPid must be explicitly closed (see
+ * g_spawn_close_pid()) @pid must not be closed while the source is
+ * still active. Typically, you will want to call g_spawn_close_pid() in
+ * the callback function for the source.
  *
  * GLib supports only a single callback per process id.
  *
- * This internally creates a main loop source using 
- * g_child_watch_source_new() and attaches it to the main loop context 
- * using g_source_attach(). You can do these steps manually if you 
- * need greater control.
+ * This internally creates a main loop source using
+ * g_child_watch_source_new() and attaches it to the main loop context
+ * using g_source_attach(). You can do these steps manually if you need
+ * greater control.
  *
  * Return value: the ID (greater than 0) of the event source.
  *
  * Since: 2.4
  **/
-guint 
+guint
 g_child_watch_add (GPid            pid,
-		   GChildWatchFunc function,
-		   gpointer        data)
+                   GChildWatchFunc function,
+                   gpointer        data)
 {
   return g_child_watch_add_full (G_PRIORITY_DEFAULT, pid, function, data, NULL);
 }
 
+typedef struct _GSignalWatchSource GSignalWatchSource;
+struct _GSignalWatchSource
+{
+  GSource  source;
+
+  gint     signum;
+  gint     triggered;
+};
+
+G_LOCK_DEFINE_STATIC (g_signal_watch_sources);
+static volatile GSignalWatchSource *g_signal_watch_sources[NSIG];
+static gint g_signal_watch_signal_handler_running;
+
+static void
+g_signal_watch_signal_handler (int signum)
+{
+  volatile GSignalWatchSource *source;
+
+  g_atomic_int_add (&g_signal_watch_signal_handler_running, +1);
+
+  if (signum >= NSIG)
+    return;
+
+  source = g_signal_watch_sources[signum];
+
+  if (source != NULL)
+    {
+      GMainContext *to_wake;
+
+      g_atomic_int_inc (&source->triggered);
+      to_wake = source->source.context;
+
+      if (to_wake && to_wake->wakeup)
+        g_wakeup_signal (to_wake->wakeup);
+    }
+
+  g_atomic_int_add (&g_signal_watch_signal_handler_running, -1);
+}
+
+static gboolean
+g_signal_watch_prepare (GSource *gsource,
+                     gint    *timeout)
+{
+  GSignalWatchSource *source = (GSignalWatchSource *) gsource;
+
+  *timeout = -1;
+
+  return source->triggered > 0;
+}
+
+static gboolean
+g_signal_watch_check (GSource *gsource)
+{
+  GSignalWatchSource *source = (GSignalWatchSource *) gsource;
+
+  return source->triggered > 0;
+}
+
+static gboolean
+g_signal_watch_dispatch (GSource     *gsource,
+                         GSourceFunc  callback,
+                         gpointer     user_data)
+{
+  GSignalWatchSource *source = (GSignalWatchSource *) gsource;
+
+  source->triggered--;
+
+  if (!callback)
+    {
+      g_warning ("Signal watch source dispatched without callback\n"
+                 "You must call g_source_set_callback().");
+      return FALSE;
+    }
+
+  return (* callback) (user_data);
+}
+
+static void
+g_signal_watch_finalize (GSource *gsource)
+{
+  GSignalWatchSource *source = (GSignalWatchSource *) gsource;
+
+  g_assert (source->signum < NSIG);
+
+  G_LOCK (g_signal_watch_sources);
+  if (g_signal_watch_sources[source->signum] == source)
+    g_signal_watch_sources[source->signum] = NULL;
+  G_UNLOCK (g_signal_watch_sources);
+
+  /* See the comment in g_child_watch_finalize() about why we do this. */
+  while G_UNLIKELY (g_signal_watch_signal_handler_running)
+    {
+#ifdef G_OS_WIN32
+      SwitchToThread ();
+#else
+      sched_yield ();
+#endif
+    }
+}
+
+static GSourceFuncs g_signal_watch_funcs =
+{
+  g_signal_watch_prepare,
+  g_signal_watch_check,
+  g_signal_watch_dispatch,
+  g_signal_watch_finalize
+};
+
+GSource *
+g_signal_watch_source_new (gint signum)
+{
+  GSignalWatchSource *source;
+  GSource *gsource;
+
+  g_return_val_if_fail (signum < NSIG, NULL);
+
+  gsource = g_source_new (&g_signal_watch_funcs, sizeof (GSignalWatchSource));
+  source = (GSignalWatchSource *) gsource;
+  source->signum = signum;
+
+  G_LOCK (g_signal_watch_sources);
+  if (g_signal_watch_sources[signum] != NULL)
+    g_critical ("Signal watch source for signal %d already exists", signum);
+  else
+    g_signal_watch_sources[signum] = source;
+  G_UNLOCK (g_signal_watch_sources);
+
+  signal (signum, g_signal_watch_signal_handler);
+
+  return gsource;
+}
+
+guint
+g_signal_watch_add_full (gint           priority,
+                         gint           signum,
+                         GSourceFunc    function,
+                         gpointer       data,
+                         GDestroyNotify notify)
+{
+  GSource *source;
+  guint id;
+
+  g_return_val_if_fail (function != NULL, 0);
+
+  source = g_signal_watch_source_new (signum);
+
+  if (priority != G_PRIORITY_DEFAULT)
+    g_source_set_priority (source, priority);
+
+  g_source_set_callback (source, function, data, notify);
+  id = g_source_attach (source, NULL);
+  g_source_unref (source);
+
+  return id;
+}
+
+guint
+g_signal_watch_add (gint        signum,
+                    GSourceFunc function,
+                    gpointer    data)
+{
+  return g_signal_watch_add_full (G_PRIORITY_DEFAULT, signum, function, data, NULL);
+}
+
 
 /* Idle functions */
 
diff --git a/glib/tests/unix.c b/glib/tests/unix.c
index 453ad64..29ac7ab 100644
--- a/glib/tests/unix.c
+++ b/glib/tests/unix.c
@@ -98,11 +98,7 @@ test_signal (int signum)
   mainloop = g_main_loop_new (NULL, FALSE);
 
   sig_received = FALSE;
-  g_unix_signal_add_watch_full (signum,
-				G_PRIORITY_DEFAULT,
-				on_sig_received,
-				mainloop,
-				NULL);
+  g_signal_watch_add (signum, on_sig_received, mainloop);
   kill (getpid (), signum);
   g_assert (!sig_received);
   g_timeout_add (5000, sig_not_received, mainloop);
@@ -138,11 +134,7 @@ test_sighup_add_remove (void)
   mainloop = g_main_loop_new (NULL, FALSE);
 
   sig_received = FALSE;
-  id = g_unix_signal_add_watch_full (SIGHUP,
-				     G_PRIORITY_DEFAULT,
-				     on_sig_received,
-				     mainloop,
-				     NULL);
+  id = g_signal_watch_add (SIGHUP, on_sig_received, mainloop);
   g_source_remove (id);
   kill (getpid (), SIGHUP);
   g_assert (!sig_received);



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